diff --git a/.htaccess b/.htaccess
index 298d461..8bf7c0c 100755
--- a/.htaccess
+++ b/.htaccess
@@ -8,10 +8,16 @@
Header set X-Content-Type-Options nosniff
Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Headers "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With, X-Acai-Token"
+
+ Header append Vary Accept-Encoding
+
+
+ Header set Content-Type "text/css"
+
ExpiresActive Off
@@ -27,6 +33,39 @@
+# ──────────── 2. CACHÉ PARA FUENTES ────────────
+
+ Header set Cache-Control "public, max-age=31536000"
+
+
+# ──────────── 3. CACHÉ PARA IMÁGENES (sin hash) ────────────
+
+ Header set Cache-Control "public, max-age=2592000"
+
+
+# ──────────── 1. CACHÉ AGRESIVO PARA ASSETS CON HASH (INMUTABLES) ────────────
+# El RewriteRule de-hashea la URI (-hshXXXX → fichero real) ANTES de que se
+# evalúen Request_URI y , que ven el nombre YA resuelto (sin hash).
+# Por eso detectamos el hash en THE_REQUEST (la línea de petición literal, que
+# NO cambia con el rewrite interno) vía expr=. El FilesMatch casa por extensión
+# (sí sobrevive al rewrite) y va tras el bloque de imágenes para ganar en
+# precedencia sobre la regla de 30 días en imágenes hasheadas.
+
+
+ Header set Cache-Control "public, max-age=31536000, immutable" "expr=%{THE_REQUEST} =~ m#-hsh[A-Za-z0-9]+\.#"
+
+
+
+# ──────────── 4. CACHÉ PARA BUILDER CSS/JS DINÁMICO ────────────
+
+ Header set Cache-Control "public, max-age=86400"
+
+
+# ──────────── 5. CORREGIR MIME TYPE DE .VUE ────────────
+AddType text/css .vue
+
+
+
#php_flag opcache.enable Off
ExpiresActive On
diff --git a/cms/lib/plugins/cms_api/v3/libraries/composer.json b/cms/lib/plugins/cms_api/v3/libraries/composer.json
new file mode 100644
index 0000000..a67d319
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/composer.json
@@ -0,0 +1,5 @@
+{
+ "require": {
+ "maennchen/zipstream-php": "^3.1"
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/composer.lock b/cms/lib/plugins/cms_api/v3/libraries/composer.lock
new file mode 100644
index 0000000..1b5ee98
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/composer.lock
@@ -0,0 +1,96 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "2bc185209b7e8bf1f9871ca24f2c61b7",
+ "packages": [
+ {
+ "name": "maennchen/zipstream-php",
+ "version": "3.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/maennchen/ZipStream-PHP.git",
+ "reference": "6187e9cc4493da94b9b63eb2315821552015fca9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/6187e9cc4493da94b9b63eb2315821552015fca9",
+ "reference": "6187e9cc4493da94b9b63eb2315821552015fca9",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "ext-zlib": "*",
+ "php-64bit": "^8.1"
+ },
+ "require-dev": {
+ "ext-zip": "*",
+ "friendsofphp/php-cs-fixer": "^3.16",
+ "guzzlehttp/guzzle": "^7.5",
+ "mikey179/vfsstream": "^1.6",
+ "php-coveralls/php-coveralls": "^2.5",
+ "phpunit/phpunit": "^10.0",
+ "vimeo/psalm": "^5.0"
+ },
+ "suggest": {
+ "guzzlehttp/psr7": "^2.4",
+ "psr/http-message": "^2.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "ZipStream\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Paul Duncan",
+ "email": "pabs@pablotron.org"
+ },
+ {
+ "name": "Jonatan Männchen",
+ "email": "jonatan@maennchen.ch"
+ },
+ {
+ "name": "Jesse Donat",
+ "email": "donatj@gmail.com"
+ },
+ {
+ "name": "András Kolesár",
+ "email": "kolesar@kolesar.hu"
+ }
+ ],
+ "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.",
+ "keywords": [
+ "stream",
+ "zip"
+ ],
+ "support": {
+ "issues": "https://github.com/maennchen/ZipStream-PHP/issues",
+ "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/maennchen",
+ "type": "github"
+ }
+ ],
+ "time": "2024-10-10T12:33:01+00:00"
+ }
+ ],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": {},
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {},
+ "platform-dev": {},
+ "plugin-api-version": "2.6.0"
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/autoload.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/autoload.php
new file mode 100644
index 0000000..67eed89
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/autoload.php
@@ -0,0 +1,25 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ * $loader = new \Composer\Autoload\ClassLoader();
+ *
+ * // register classes with namespaces
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * // activate the autoloader
+ * $loader->register();
+ *
+ * // to enable searching the include path (eg. for PEAR packages)
+ * $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier
+ * @author Jordi Boggiano
+ * @see https://www.php-fig.org/psr/psr-0/
+ * @see https://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+ /** @var \Closure(string):void */
+ private static $includeFile;
+
+ /** @var string|null */
+ private $vendorDir;
+
+ // PSR-4
+ /**
+ * @var array>
+ */
+ private $prefixLengthsPsr4 = array();
+ /**
+ * @var array>
+ */
+ private $prefixDirsPsr4 = array();
+ /**
+ * @var list
+ */
+ private $fallbackDirsPsr4 = array();
+
+ // PSR-0
+ /**
+ * List of PSR-0 prefixes
+ *
+ * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
+ *
+ * @var array>>
+ */
+ private $prefixesPsr0 = array();
+ /**
+ * @var list
+ */
+ private $fallbackDirsPsr0 = array();
+
+ /** @var bool */
+ private $useIncludePath = false;
+
+ /**
+ * @var array
+ */
+ private $classMap = array();
+
+ /** @var bool */
+ private $classMapAuthoritative = false;
+
+ /**
+ * @var array
+ */
+ private $missingClasses = array();
+
+ /** @var string|null */
+ private $apcuPrefix;
+
+ /**
+ * @var array
+ */
+ private static $registeredLoaders = array();
+
+ /**
+ * @param string|null $vendorDir
+ */
+ public function __construct($vendorDir = null)
+ {
+ $this->vendorDir = $vendorDir;
+ self::initializeIncludeClosure();
+ }
+
+ /**
+ * @return array>
+ */
+ public function getPrefixes()
+ {
+ if (!empty($this->prefixesPsr0)) {
+ return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
+ }
+
+ return array();
+ }
+
+ /**
+ * @return array>
+ */
+ public function getPrefixesPsr4()
+ {
+ return $this->prefixDirsPsr4;
+ }
+
+ /**
+ * @return list
+ */
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirsPsr0;
+ }
+
+ /**
+ * @return list
+ */
+ public function getFallbackDirsPsr4()
+ {
+ return $this->fallbackDirsPsr4;
+ }
+
+ /**
+ * @return array Array of classname => path
+ */
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param array $classMap Class to filename map
+ *
+ * @return void
+ */
+ public function addClassMap(array $classMap)
+ {
+ if ($this->classMap) {
+ $this->classMap = array_merge($this->classMap, $classMap);
+ } else {
+ $this->classMap = $classMap;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix, either
+ * appending or prepending to the ones previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param list|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @return void
+ */
+ public function add($prefix, $paths, $prepend = false)
+ {
+ $paths = (array) $paths;
+ if (!$prefix) {
+ if ($prepend) {
+ $this->fallbackDirsPsr0 = array_merge(
+ $paths,
+ $this->fallbackDirsPsr0
+ );
+ } else {
+ $this->fallbackDirsPsr0 = array_merge(
+ $this->fallbackDirsPsr0,
+ $paths
+ );
+ }
+
+ return;
+ }
+
+ $first = $prefix[0];
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
+ $this->prefixesPsr0[$first][$prefix] = $paths;
+
+ return;
+ }
+ if ($prepend) {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $paths,
+ $this->prefixesPsr0[$first][$prefix]
+ );
+ } else {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $this->prefixesPsr0[$first][$prefix],
+ $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace, either
+ * appending or prepending to the ones previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param list|string $paths The PSR-4 base directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return void
+ */
+ public function addPsr4($prefix, $paths, $prepend = false)
+ {
+ $paths = (array) $paths;
+ if (!$prefix) {
+ // Register directories for the root namespace.
+ if ($prepend) {
+ $this->fallbackDirsPsr4 = array_merge(
+ $paths,
+ $this->fallbackDirsPsr4
+ );
+ } else {
+ $this->fallbackDirsPsr4 = array_merge(
+ $this->fallbackDirsPsr4,
+ $paths
+ );
+ }
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+ // Register directories for a new namespace.
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = $paths;
+ } elseif ($prepend) {
+ // Prepend directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $paths,
+ $this->prefixDirsPsr4[$prefix]
+ );
+ } else {
+ // Append directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $this->prefixDirsPsr4[$prefix],
+ $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix,
+ * replacing any others previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param list|string $paths The PSR-0 base directories
+ *
+ * @return void
+ */
+ public function set($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr0 = (array) $paths;
+ } else {
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace,
+ * replacing any others previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param list|string $paths The PSR-4 base directories
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return void
+ */
+ public function setPsr4($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr4 = (array) $paths;
+ } else {
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Turns on searching the include path for class files.
+ *
+ * @param bool $useIncludePath
+ *
+ * @return void
+ */
+ public function setUseIncludePath($useIncludePath)
+ {
+ $this->useIncludePath = $useIncludePath;
+ }
+
+ /**
+ * Can be used to check if the autoloader uses the include path to check
+ * for classes.
+ *
+ * @return bool
+ */
+ public function getUseIncludePath()
+ {
+ return $this->useIncludePath;
+ }
+
+ /**
+ * Turns off searching the prefix and fallback directories for classes
+ * that have not been registered with the class map.
+ *
+ * @param bool $classMapAuthoritative
+ *
+ * @return void
+ */
+ public function setClassMapAuthoritative($classMapAuthoritative)
+ {
+ $this->classMapAuthoritative = $classMapAuthoritative;
+ }
+
+ /**
+ * Should class lookup fail if not found in the current class map?
+ *
+ * @return bool
+ */
+ public function isClassMapAuthoritative()
+ {
+ return $this->classMapAuthoritative;
+ }
+
+ /**
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+ *
+ * @param string|null $apcuPrefix
+ *
+ * @return void
+ */
+ public function setApcuPrefix($apcuPrefix)
+ {
+ $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
+ }
+
+ /**
+ * The APCu prefix in use, or null if APCu caching is not enabled.
+ *
+ * @return string|null
+ */
+ public function getApcuPrefix()
+ {
+ return $this->apcuPrefix;
+ }
+
+ /**
+ * Registers this instance as an autoloader.
+ *
+ * @param bool $prepend Whether to prepend the autoloader or not
+ *
+ * @return void
+ */
+ public function register($prepend = false)
+ {
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+
+ if (null === $this->vendorDir) {
+ return;
+ }
+
+ if ($prepend) {
+ self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
+ } else {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ self::$registeredLoaders[$this->vendorDir] = $this;
+ }
+ }
+
+ /**
+ * Unregisters this instance as an autoloader.
+ *
+ * @return void
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+
+ if (null !== $this->vendorDir) {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ }
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ * @return true|null True if loaded, null otherwise
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ $includeFile = self::$includeFile;
+ $includeFile($file);
+
+ return true;
+ }
+
+ return null;
+ }
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ *
+ * @return string|false The path if found, false otherwise
+ */
+ public function findFile($class)
+ {
+ // class map lookup
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+ return false;
+ }
+ if (null !== $this->apcuPrefix) {
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+ if ($hit) {
+ return $file;
+ }
+ }
+
+ $file = $this->findFileWithExtension($class, '.php');
+
+ // Search for Hack files if we are running on HHVM
+ if (false === $file && defined('HHVM_VERSION')) {
+ $file = $this->findFileWithExtension($class, '.hh');
+ }
+
+ if (null !== $this->apcuPrefix) {
+ apcu_add($this->apcuPrefix.$class, $file);
+ }
+
+ if (false === $file) {
+ // Remember that this class does not exist.
+ $this->missingClasses[$class] = true;
+ }
+
+ return $file;
+ }
+
+ /**
+ * Returns the currently registered loaders keyed by their corresponding vendor directories.
+ *
+ * @return array
+ */
+ public static function getRegisteredLoaders()
+ {
+ return self::$registeredLoaders;
+ }
+
+ /**
+ * @param string $class
+ * @param string $ext
+ * @return string|false
+ */
+ private function findFileWithExtension($class, $ext)
+ {
+ // PSR-4 lookup
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+ $first = $class[0];
+ if (isset($this->prefixLengthsPsr4[$first])) {
+ $subPath = $class;
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
+ $subPath = substr($subPath, 0, $lastPos);
+ $search = $subPath . '\\';
+ if (isset($this->prefixDirsPsr4[$search])) {
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
+ if (file_exists($file = $dir . $pathEnd)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-4 fallback dirs
+ foreach ($this->fallbackDirsPsr4 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 lookup
+ if (false !== $pos = strrpos($class, '\\')) {
+ // namespaced class name
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+ } else {
+ // PEAR-like class name
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+ }
+
+ if (isset($this->prefixesPsr0[$first])) {
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($dirs as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-0 fallback dirs
+ foreach ($this->fallbackDirsPsr0 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 include paths.
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+ return $file;
+ }
+
+ return false;
+ }
+
+ /**
+ * @return void
+ */
+ private static function initializeIncludeClosure()
+ {
+ if (self::$includeFile !== null) {
+ return;
+ }
+
+ /**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ *
+ * @param string $file
+ * @return void
+ */
+ self::$includeFile = \Closure::bind(static function($file) {
+ include $file;
+ }, null, null);
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/InstalledVersions.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/InstalledVersions.php
new file mode 100644
index 0000000..6d29bff
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/InstalledVersions.php
@@ -0,0 +1,378 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer;
+
+use Composer\Autoload\ClassLoader;
+use Composer\Semver\VersionParser;
+
+/**
+ * This class is copied in every Composer installed project and available to all
+ *
+ * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
+ *
+ * To require its presence, you can require `composer-runtime-api ^2.0`
+ *
+ * @final
+ */
+class InstalledVersions
+{
+ /**
+ * @var mixed[]|null
+ * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null
+ */
+ private static $installed;
+
+ /**
+ * @var bool
+ */
+ private static $installedIsLocalDir;
+
+ /**
+ * @var bool|null
+ */
+ private static $canGetVendors;
+
+ /**
+ * @var array[]
+ * @psalm-var array}>
+ */
+ private static $installedByVendor = array();
+
+ /**
+ * Returns a list of all package names which are present, either by being installed, replaced or provided
+ *
+ * @return string[]
+ * @psalm-return list
+ */
+ public static function getInstalledPackages()
+ {
+ $packages = array();
+ foreach (self::getInstalled() as $installed) {
+ $packages[] = array_keys($installed['versions']);
+ }
+
+ if (1 === \count($packages)) {
+ return $packages[0];
+ }
+
+ return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
+ }
+
+ /**
+ * Returns a list of all package names with a specific type e.g. 'library'
+ *
+ * @param string $type
+ * @return string[]
+ * @psalm-return list
+ */
+ public static function getInstalledPackagesByType($type)
+ {
+ $packagesByType = array();
+
+ foreach (self::getInstalled() as $installed) {
+ foreach ($installed['versions'] as $name => $package) {
+ if (isset($package['type']) && $package['type'] === $type) {
+ $packagesByType[] = $name;
+ }
+ }
+ }
+
+ return $packagesByType;
+ }
+
+ /**
+ * Checks whether the given package is installed
+ *
+ * This also returns true if the package name is provided or replaced by another package
+ *
+ * @param string $packageName
+ * @param bool $includeDevRequirements
+ * @return bool
+ */
+ public static function isInstalled($packageName, $includeDevRequirements = true)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (isset($installed['versions'][$packageName])) {
+ return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether the given package satisfies a version constraint
+ *
+ * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
+ *
+ * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
+ *
+ * @param VersionParser $parser Install composer/semver to have access to this class and functionality
+ * @param string $packageName
+ * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
+ * @return bool
+ */
+ public static function satisfies(VersionParser $parser, $packageName, $constraint)
+ {
+ $constraint = $parser->parseConstraints((string) $constraint);
+ $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
+
+ return $provided->matches($constraint);
+ }
+
+ /**
+ * Returns a version constraint representing all the range(s) which are installed for a given package
+ *
+ * It is easier to use this via isInstalled() with the $constraint argument if you need to check
+ * whether a given version of a package is installed, and not just whether it exists
+ *
+ * @param string $packageName
+ * @return string Version constraint usable with composer/semver
+ */
+ public static function getVersionRanges($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ $ranges = array();
+ if (isset($installed['versions'][$packageName]['pretty_version'])) {
+ $ranges[] = $installed['versions'][$packageName]['pretty_version'];
+ }
+ if (array_key_exists('aliases', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
+ }
+ if (array_key_exists('replaced', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
+ }
+ if (array_key_exists('provided', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
+ }
+
+ return implode(' || ', $ranges);
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getPrettyVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['pretty_version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['pretty_version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
+ */
+ public static function getReference($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['reference'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['reference'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
+ */
+ public static function getInstallPath($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @return array
+ * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
+ */
+ public static function getRootPackage()
+ {
+ $installed = self::getInstalled();
+
+ return $installed[0]['root'];
+ }
+
+ /**
+ * Returns the raw installed.php data for custom implementations
+ *
+ * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
+ * @return array[]
+ * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}
+ */
+ public static function getRawData()
+ {
+ @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ self::$installed = include __DIR__ . '/installed.php';
+ } else {
+ self::$installed = array();
+ }
+ }
+
+ return self::$installed;
+ }
+
+ /**
+ * Returns the raw data of all installed.php which are currently loaded for custom implementations
+ *
+ * @return array[]
+ * @psalm-return list}>
+ */
+ public static function getAllRawData()
+ {
+ return self::getInstalled();
+ }
+
+ /**
+ * Lets you reload the static array from another file
+ *
+ * This is only useful for complex integrations in which a project needs to use
+ * this class but then also needs to execute another project's autoloader in process,
+ * and wants to ensure both projects have access to their version of installed.php.
+ *
+ * A typical case would be PHPUnit, where it would need to make sure it reads all
+ * the data it needs from this class, then call reload() with
+ * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
+ * the project in which it runs can then also use this class safely, without
+ * interference between PHPUnit's dependencies and the project's dependencies.
+ *
+ * @param array[] $data A vendor/composer/installed.php data set
+ * @return void
+ *
+ * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data
+ */
+ public static function reload($data)
+ {
+ self::$installed = $data;
+ self::$installedByVendor = array();
+
+ // when using reload, we disable the duplicate protection to ensure that self::$installed data is
+ // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
+ // so we have to assume it does not, and that may result in duplicate data being returned when listing
+ // all installed packages for example
+ self::$installedIsLocalDir = false;
+ }
+
+ /**
+ * @return array[]
+ * @psalm-return list}>
+ */
+ private static function getInstalled()
+ {
+ if (null === self::$canGetVendors) {
+ self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
+ }
+
+ $installed = array();
+ $copiedLocalDir = false;
+
+ if (self::$canGetVendors) {
+ $selfDir = strtr(__DIR__, '\\', '/');
+ foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
+ $vendorDir = strtr($vendorDir, '\\', '/');
+ if (isset(self::$installedByVendor[$vendorDir])) {
+ $installed[] = self::$installedByVendor[$vendorDir];
+ } elseif (is_file($vendorDir.'/composer/installed.php')) {
+ /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */
+ $required = require $vendorDir.'/composer/installed.php';
+ self::$installedByVendor[$vendorDir] = $required;
+ $installed[] = $required;
+ if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
+ self::$installed = $required;
+ self::$installedIsLocalDir = true;
+ }
+ }
+ if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
+ $copiedLocalDir = true;
+ }
+ }
+ }
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */
+ $required = require __DIR__ . '/installed.php';
+ self::$installed = $required;
+ } else {
+ self::$installed = array();
+ }
+ }
+
+ if (self::$installed !== array() && !$copiedLocalDir) {
+ $installed[] = self::$installed;
+ }
+
+ return $installed;
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/LICENSE b/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/LICENSE
new file mode 100644
index 0000000..f27399a
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/LICENSE
@@ -0,0 +1,21 @@
+
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+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/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/autoload_classmap.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/autoload_classmap.php
new file mode 100644
index 0000000..0fb0a2c
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/autoload_classmap.php
@@ -0,0 +1,10 @@
+ $vendorDir . '/composer/InstalledVersions.php',
+);
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/autoload_namespaces.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/autoload_namespaces.php
new file mode 100644
index 0000000..15a2ff3
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/autoload_namespaces.php
@@ -0,0 +1,9 @@
+ array($vendorDir . '/maennchen/zipstream-php/src'),
+);
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/autoload_real.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/autoload_real.php
new file mode 100644
index 0000000..a7378fb
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/autoload_real.php
@@ -0,0 +1,38 @@
+register(true);
+
+ return $loader;
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/autoload_static.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/autoload_static.php
new file mode 100644
index 0000000..e6909dd
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/autoload_static.php
@@ -0,0 +1,36 @@
+
+ array (
+ 'ZipStream\\' => 10,
+ ),
+ );
+
+ public static $prefixDirsPsr4 = array (
+ 'ZipStream\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/maennchen/zipstream-php/src',
+ ),
+ );
+
+ public static $classMap = array (
+ 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
+ );
+
+ public static function getInitializer(ClassLoader $loader)
+ {
+ return \Closure::bind(function () use ($loader) {
+ $loader->prefixLengthsPsr4 = ComposerStaticInit9f123c2b46f62580755f3f43dbef83a1::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInit9f123c2b46f62580755f3f43dbef83a1::$prefixDirsPsr4;
+ $loader->classMap = ComposerStaticInit9f123c2b46f62580755f3f43dbef83a1::$classMap;
+
+ }, null, ClassLoader::class);
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/installed.json b/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/installed.json
new file mode 100644
index 0000000..df6bfac
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/installed.json
@@ -0,0 +1,86 @@
+{
+ "packages": [
+ {
+ "name": "maennchen/zipstream-php",
+ "version": "3.1.1",
+ "version_normalized": "3.1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/maennchen/ZipStream-PHP.git",
+ "reference": "6187e9cc4493da94b9b63eb2315821552015fca9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/6187e9cc4493da94b9b63eb2315821552015fca9",
+ "reference": "6187e9cc4493da94b9b63eb2315821552015fca9",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "ext-zlib": "*",
+ "php-64bit": "^8.1"
+ },
+ "require-dev": {
+ "ext-zip": "*",
+ "friendsofphp/php-cs-fixer": "^3.16",
+ "guzzlehttp/guzzle": "^7.5",
+ "mikey179/vfsstream": "^1.6",
+ "php-coveralls/php-coveralls": "^2.5",
+ "phpunit/phpunit": "^10.0",
+ "vimeo/psalm": "^5.0"
+ },
+ "suggest": {
+ "guzzlehttp/psr7": "^2.4",
+ "psr/http-message": "^2.0"
+ },
+ "time": "2024-10-10T12:33:01+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "ZipStream\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Paul Duncan",
+ "email": "pabs@pablotron.org"
+ },
+ {
+ "name": "Jonatan Männchen",
+ "email": "jonatan@maennchen.ch"
+ },
+ {
+ "name": "Jesse Donat",
+ "email": "donatj@gmail.com"
+ },
+ {
+ "name": "András Kolesár",
+ "email": "kolesar@kolesar.hu"
+ }
+ ],
+ "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.",
+ "keywords": [
+ "stream",
+ "zip"
+ ],
+ "support": {
+ "issues": "https://github.com/maennchen/ZipStream-PHP/issues",
+ "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/maennchen",
+ "type": "github"
+ }
+ ],
+ "install-path": "../maennchen/zipstream-php"
+ }
+ ],
+ "dev": true,
+ "dev-package-names": []
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/installed.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/installed.php
new file mode 100644
index 0000000..b207f7e
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/installed.php
@@ -0,0 +1,32 @@
+ array(
+ 'name' => '__root__',
+ 'pretty_version' => 'dev-master',
+ 'version' => 'dev-master',
+ 'reference' => 'bd1fd778e9933ed14251657d3d0d82f32d5dc2a6',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../../',
+ 'aliases' => array(),
+ 'dev' => true,
+ ),
+ 'versions' => array(
+ '__root__' => array(
+ 'pretty_version' => 'dev-master',
+ 'version' => 'dev-master',
+ 'reference' => 'bd1fd778e9933ed14251657d3d0d82f32d5dc2a6',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../../',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'maennchen/zipstream-php' => array(
+ 'pretty_version' => '3.1.1',
+ 'version' => '3.1.1.0',
+ 'reference' => '6187e9cc4493da94b9b63eb2315821552015fca9',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../maennchen/zipstream-php',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ ),
+);
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/platform_check.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/platform_check.php
new file mode 100644
index 0000000..f71b2f8
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/composer/platform_check.php
@@ -0,0 +1,30 @@
+= 80100)) {
+ $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.';
+}
+
+if (PHP_INT_SIZE !== 8) {
+ $issues[] = 'Your Composer dependencies require a 64-bit build of PHP.';
+}
+
+if ($issues) {
+ if (!headers_sent()) {
+ header('HTTP/1.1 500 Internal Server Error');
+ }
+ if (!ini_get('display_errors')) {
+ if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
+ fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
+ } elseif (!headers_sent()) {
+ echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
+ }
+ }
+ trigger_error(
+ 'Composer detected issues in your platform: ' . implode(' ', $issues),
+ E_USER_ERROR
+ );
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.editorconfig b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.editorconfig
new file mode 100644
index 0000000..f7cd914
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.editorconfig
@@ -0,0 +1,22 @@
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+charset = utf-8
+
+[*.{yml,md,xml}]
+indent_style = space
+indent_size = 2
+
+[*.{rst,php}]
+indent_style = space
+indent_size = 4
+
+[composer.json]
+indent_style = space
+indent_size = 2
+
+[composer.lock]
+indent_style = space
+indent_size = 4
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.gitattributes b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.gitattributes
new file mode 100644
index 0000000..e058ebd
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.gitattributes
@@ -0,0 +1,6 @@
+.gitignore text eol=lf
+.gitattributes text eol=lf
+*.md text eol=lf
+*.php text eol=lf
+*.yml text eol=lf
+*.xml text eol=lf
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/CODE_OF_CONDUCT.md b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..9d75b87
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/CODE_OF_CONDUCT.md
@@ -0,0 +1,132 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, caste, color, religion, or sexual
+identity and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+- Demonstrating empathy and kindness toward other people
+- Being respectful of differing opinions, viewpoints, and experiences
+- Giving and gracefully accepting constructive feedback
+- Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+- Focusing on what is best not just for us as individuals, but for the overall
+ community
+
+Examples of unacceptable behavior include:
+
+- The use of sexualized language or imagery, and sexual attention or advances of
+ any kind
+- Trolling, insulting or derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or email address,
+ without their explicit permission
+- Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+jonatan@maennchen.ch.
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series of
+actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or permanent
+ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within the
+community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.1, available at
+[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
+
+Community Impact Guidelines were inspired by
+[Mozilla's code of conduct enforcement ladder][mozilla coc].
+
+For answers to common questions about this code of conduct, see the FAQ at
+[https://www.contributor-covenant.org/faq][faq]. Translations are available at
+[https://www.contributor-covenant.org/translations][translations].
+
+[homepage]: https://www.contributor-covenant.org
+[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
+[mozilla coc]: https://github.com/mozilla/diversity
+[faq]: https://www.contributor-covenant.org/faq
+[translations]: https://www.contributor-covenant.org/translations
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/CONTRIBUTING.md b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/CONTRIBUTING.md
new file mode 100644
index 0000000..d8caee0
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/CONTRIBUTING.md
@@ -0,0 +1,139 @@
+# Contributing to ZipStream-PHP
+
+## Welcome!
+
+We look forward to your contributions! Here are some examples how you can
+contribute:
+
+- [Report a bug](https://github.com/maennchen/ZipStream-PHP/issues/new?labels=bug&template=BUG.md)
+- [Propose a new feature](https://github.com/maennchen/ZipStream-PHP/issues/new?labels=enhancement&template=FEATURE.md)
+- [Send a pull request](https://github.com/maennchen/ZipStream-PHP/pulls)
+
+## We have a Code of Conduct
+
+Please note that this project is released with a
+[Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this
+project you agree to abide by its terms.
+
+## Any contributions you make will be under the MIT License
+
+When you submit code changes, your submissions are understood to be under the
+same [MIT License](https://github.com/maennchen/ZipStream-PHP/blob/main/LICENSE)
+that covers the project. By contributing to this project, you agree that your
+contributions will be licensed under its MIT License.
+
+## Write bug reports with detail, background, and sample code
+
+In your bug report, please provide the following:
+
+- A quick summary and/or background
+- Steps to reproduce
+ - Be specific!
+ - Give sample code if you can.
+- What you expected would happen
+- What actually happens
+- Notes (possibly including why you think this might be happening, or stuff you
+- tried that didn't work)
+
+Please do not report a bug for a version of ZIPStream-PHP that is no longer
+supported (`< 3.0.0`). Please do not report a bug if you are using a version of
+PHP that is not supported by the version of ZipStream-PHP you are using.
+
+Please post code and output as text
+([using proper markup](https://guides.github.com/features/mastering-markdown/)).
+Do not post screenshots of code or output.
+
+Please include the output of `composer info | sort`.
+
+## Workflow for Pull Requests
+
+1. Fork the repository.
+2. Create your branch from `main` if you plan to implement new functionality or
+ change existing code significantly; create your branch from the oldest branch
+ that is affected by the bug if you plan to fix a bug.
+3. Implement your change and add tests for it.
+4. Ensure the test suite passes.
+5. Ensure the code complies with our coding guidelines (see below).
+6. Send that pull request!
+
+Please make sure you have
+[set up your user name and email address](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup)
+for use with Git. Strings such as `silly nick name ` look really
+stupid in the commit history of a project.
+
+We encourage you to
+[sign your Git commits with your GPG key](https://docs.github.com/en/github/authenticating-to-github/signing-commits).
+
+Pull requests for new features must be based on the `main` branch.
+
+We are trying to keep backwards compatibility breaks in ZipStream-PHP to a
+minimum. Please take this into account when proposing changes.
+
+Due to time constraints, we are not always able to respond as quickly as we
+would like. Please do not take delays personal and feel free to remind us if you
+feel that we forgot to respond.
+
+## Coding Guidelines
+
+This project comes with a configuration file (located at `/psalm.yml` in the
+repository) that you can use to perform static analysis (with a focus on type
+checking):
+
+```bash
+$ .composer run test:lint
+```
+
+This project comes with a configuration file (located at
+`/.php-cs-fixer.dist.php` in the repository) that you can use to (re)format your
+source code for compliance with this project's coding guidelines:
+
+```bash
+$ composer run format
+```
+
+Please understand that we will not accept a pull request when its changes
+violate this project's coding guidelines.
+
+## Using ZipStream-PHP from a Git checkout
+
+The following commands can be used to perform the initial checkout of
+ZipStream-PHP:
+
+```bash
+$ git clone git@github.com:maennchen/ZipStream-PHP.git
+
+$ cd ZipStream-PHP
+```
+
+Install ZipStream-PHP's dependencies using [Composer](https://getcomposer.org/):
+
+```bash
+$ composer install
+$ composer run install:tools # Install phpDocumentor using phive
+```
+
+## Running ZipStream-PHP's test suite
+
+After following the steps shown above, ZipStream-PHP's test suite is run like
+this:
+
+```bash
+$ composer run test:unit
+```
+
+There's some slow tests in the test suite that test the handling of big files in
+the archives. To skip them use the following command instead:
+
+```bash
+$ composer run test:unit:fast
+```
+
+## Generating ZipStream-PHP Documentation
+
+To generate the documentation for the library, run:
+
+```bash
+$ composer run docs:generate
+```
+
+The guide documentation pages can be found in the `/guides/` directory.
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/FUNDING.yml b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/FUNDING.yml
new file mode 100644
index 0000000..5a46127
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: maennchen
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE/BUG.yml b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE/BUG.yml
new file mode 100644
index 0000000..0eb8cc7
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE/BUG.yml
@@ -0,0 +1,71 @@
+name: 🐞 Bug Report
+description: Something is broken?
+labels: ["bug"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ - Create a discussion instead if you are looking for support:
+ https://github.com/maennchen/ZipStream-PHP/discussions
+ - type: input
+ id: version
+ attributes:
+ label: ZipStream-PHP version
+ placeholder: x.y.z
+ validations:
+ required: true
+ - type: input
+ id: php-version
+ attributes:
+ label: PHP version
+ placeholder: x.y.z
+ validations:
+ required: true
+ - type: checkboxes
+ id: constraints
+ attributes:
+ label: Constraints for Bug Report
+ options:
+ - label: |
+ I'm using a version of ZipStream that is currently supported:
+ https://github.com/maennchen/ZipStream-PHP#version-support
+ required: true
+ - label: |
+ I'm using a version of PHP that has active support:
+ https://www.php.net/supported-versions.php
+ required: true
+ - label: |
+ I'm using a version of PHP that is compatible with your used
+ ZipStream version.
+ required: true
+ - label: |
+ I'm using the latest release of the used ZipStream major version.
+ required: true
+ - type: textarea
+ id: summary
+ attributes:
+ label: Summary
+ description: Provide a summary describing the problem you are experiencing.
+ validations:
+ required: true
+ - type: textarea
+ id: current-behaviour
+ attributes:
+ label: Current behavior
+ description: What is the current (buggy) behavior?
+ validations:
+ required: true
+ - type: textarea
+ id: reproduction
+ attributes:
+ label: How to reproduce
+ description: Provide steps to reproduce the bug.
+ validations:
+ required: true
+ - type: textarea
+ id: expected-behaviour
+ attributes:
+ label: Expected behavior
+ description: What was the expected (correct) behavior?
+ validations:
+ required: true
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE/FEATURE.yml b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE/FEATURE.yml
new file mode 100644
index 0000000..e5dec63
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE/FEATURE.yml
@@ -0,0 +1,11 @@
+name: 🎉 Feature Request
+description: You have a neat idea that should be implemented?
+labels: ["enhancement"]
+body:
+ - type: textarea
+ id: description
+ attributes:
+ label: Description
+ description: Provide a summary of the feature you would like to see implemented.
+ validations:
+ required: true
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE.md b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..6892c57
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,6 @@
+Please go the the `Preview` tab and select the appropriate sub-template:
+
+* [🐞 Failing Test](?expand=1&template=FAILING_TEST.md)
+* [🐞 Bug Fix](?expand=1&template=FIX.md)
+* [⚙ Improvement](?expand=1&template=IMPROVEMENT.md)
+* [🎉 New Feature](?expand=1&template=NEW_FEATURE.md)
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/FAILING_TEST.md b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/FAILING_TEST.md
new file mode 100644
index 0000000..24603cb
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/FAILING_TEST.md
@@ -0,0 +1,13 @@
+
+
+
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/FIX.md b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/FIX.md
new file mode 100644
index 0000000..77f65a0
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/FIX.md
@@ -0,0 +1,13 @@
+
+
+
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/IMPROVEMENT.md b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/IMPROVEMENT.md
new file mode 100644
index 0000000..3ac8e31
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/IMPROVEMENT.md
@@ -0,0 +1,9 @@
+
+
+
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/NEW_FEATURE.md b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/NEW_FEATURE.md
new file mode 100644
index 0000000..ca53939
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/NEW_FEATURE.md
@@ -0,0 +1,9 @@
+
+
+
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/SECURITY.md b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/SECURITY.md
new file mode 100644
index 0000000..3046c31
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/SECURITY.md
@@ -0,0 +1,22 @@
+# Security Policy
+
+[](https://github.com/ossf/oss-vulnerability-guide/blob/main/finder-guide.md)
+[](https://github.com/maennchen/ZipStream-PHP/security/advisories/new)
+[](mailto:jonatan@maennchen.ch)
+
+This repository follows the
+[OpenSSF Vulnerability Disclosure guide](https://github.com/ossf/oss-vulnerability-guide/tree/main).
+You can learn more about it in the
+[Finders Guide](https://github.com/ossf/oss-vulnerability-guide/blob/main/finder-guide.md).
+
+Please report vulnerabilities via the
+[GitHub Security Vulnerability Reporting](https://github.com/maennchen/ZipStream-PHP/security/advisories/new)
+or via email to [`jonatan@maennchen.ch`](mailto:jonatan@maennchen.ch) if this does
+not work for you.
+
+Our vulnerability management team will respond within 3 working days of your
+report. If the issue is confirmed as a vulnerability, we will open a Security
+Advisory. This project follows a 90 day disclosure timeline.
+
+If you have questions about reporting security issues, email the vulnerability
+management team: [`jonatan@maennchen.ch`](mailto:jonatan@maennchen.ch)
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/dependabot.yml b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/dependabot.yml
new file mode 100644
index 0000000..6056437
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/dependabot.yml
@@ -0,0 +1,13 @@
+version: 2
+updates:
+ - package-ecosystem: "composer"
+ directory: "/"
+ schedule:
+ interval: "daily"
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ groups:
+ github-actions:
+ applies-to: version-updates
\ No newline at end of file
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/scorecard.yml b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/scorecard.yml
new file mode 100644
index 0000000..219fc0b
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/scorecard.yml
@@ -0,0 +1,14 @@
+annotations:
+ - checks:
+ - fuzzing
+ reasons:
+ - reason: not-applicable # PHP is memory safe
+ - checks:
+ - packaging
+ reasons:
+ - reason: not-supported # Using Composer
+ - checks:
+ - signed-releases
+ reasons:
+ - reason: not-applicable # Releases are distributed via Composer
+
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/branch_main.yml b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/branch_main.yml
new file mode 100644
index 0000000..15ff278
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/branch_main.yml
@@ -0,0 +1,24 @@
+on:
+ push:
+ branches:
+ - "main"
+
+name: "Main Branch"
+
+permissions:
+ contents: read
+
+jobs:
+ test:
+ name: "Test"
+
+ permissions:
+ contents: read
+ security-events: write
+
+ uses: ./.github/workflows/part_test.yml
+
+ docs:
+ name: "Docs"
+
+ uses: ./.github/workflows/part_docs.yml
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/part_dependabot.yml b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/part_dependabot.yml
new file mode 100644
index 0000000..77e466b
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/part_dependabot.yml
@@ -0,0 +1,30 @@
+on:
+ workflow_call: {}
+
+name: "Dependabot"
+
+permissions:
+ contents: read
+
+jobs:
+ automerge_dependabot:
+ name: "Automerge PRs"
+
+ runs-on: ubuntu-latest
+
+ permissions:
+ pull-requests: write
+ contents: write
+
+ steps:
+ - name: Harden Runner
+ uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
+ with:
+ egress-policy: audit
+
+ - uses: fastify/github-action-merge-dependabot@3892334d1c649bb8119af3d22a3f3766bd5e593f # v3.10.2
+ with:
+ github-token: ${{ github.token }}
+ use-github-auto-merge: true
+ # Major Updates need to be merged manually
+ target: minor
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/part_docs.yml b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/part_docs.yml
new file mode 100644
index 0000000..7af16f3
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/part_docs.yml
@@ -0,0 +1,51 @@
+on:
+ workflow_call: {}
+
+name: "Documentation"
+
+permissions:
+ contents: read
+
+jobs:
+ generate:
+ name: "Generate"
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Harden Runner
+ uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
+ with:
+ egress-policy: audit
+
+ - name: Checkout Code
+ uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+ - name: SetUp PHP
+ id: setup-php
+ uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2
+ with:
+ php-version: "8.3"
+ tools: phive
+ - name: Cache Tools
+ uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
+ id: cache
+ with:
+ path: ~/.phive
+ key: tools-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}-${{ hashFiles('**/phars.xml') }}
+ restore-keys: |
+ tools-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}-
+ tools-${{ steps.setup-php.outputs.php-version }}-
+ tools-
+ - name: Install Tools
+ run: composer run install:tools
+ - name: Generate Docs
+ run: composer run docs:generate
+ - uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4.4.1
+ with:
+ name: docs
+ path: docs
+ - name: Package for GitHub Pages
+ uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1
+ with:
+ path: docs
+
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/part_release.yml b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/part_release.yml
new file mode 100644
index 0000000..c0f3867
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/part_release.yml
@@ -0,0 +1,94 @@
+on:
+ workflow_call:
+ inputs:
+ releaseName:
+ required: true
+ type: string
+ stable:
+ required: false
+ type: boolean
+ default: false
+
+name: "Release"
+
+permissions:
+ contents: read
+
+jobs:
+ create:
+ name: Create Release
+
+ runs-on: ubuntu-latest
+
+ permissions:
+ contents: write
+
+ steps:
+ - name: Harden Runner
+ uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
+ with:
+ egress-policy: audit
+
+ - name: Create prerelease
+ if: ${{ !inputs.stable }}
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ run: |
+ gh release create \
+ --repo ${{ github.repository }} \
+ --title ${{ inputs.releaseName }} \
+ --prerelease \
+ --generate-notes \
+ ${{ inputs.releaseName }}
+
+ - name: Create release
+ if: ${{ inputs.stable }}
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ run: |
+ gh release create \
+ --repo ${{ github.repository }} \
+ --title ${{ inputs.releaseName }} \
+ --generate-notes \
+ ${{ inputs.releaseName }}
+
+ upload_release:
+ name: "Upload"
+
+ needs: ["create"]
+
+ runs-on: ubuntu-latest
+
+ permissions:
+ id-token: write
+ contents: write
+ attestations: write
+
+ steps:
+ - name: Harden Runner
+ uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
+ with:
+ egress-policy: audit
+
+ - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+ - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
+ with:
+ name: docs
+ path: docs
+ - run: |
+ tar -czvf docs.tar.gz docs
+ - name: "Attest Documentation"
+ id: attestation
+ uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3
+ with:
+ subject-path: "docs.tar.gz"
+ - name: Copy Attestation
+ run: cp "$ATTESTATION" docs.tar.gz.sigstore
+ env:
+ ATTESTATION: "${{ steps.attestation.outputs.bundle-path }}"
+ - name: Upload
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ run: |
+ gh release upload --clobber "${{ github.ref_name }}" \
+ docs.tar.gz docs.tar.gz.sigstore
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/part_test.yml b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/part_test.yml
new file mode 100644
index 0000000..ccf4d66
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/part_test.yml
@@ -0,0 +1,183 @@
+on:
+ workflow_call:
+
+name: "Test"
+
+permissions:
+ contents: read
+
+jobs:
+ phpunit:
+ name: PHPUnit (PHP ${{ matrix.php }} on ${{ matrix.os }})
+
+ runs-on: ${{ matrix.os }}
+
+ continue-on-error: ${{ matrix.experimental }}
+
+ strategy:
+ fail-fast: false
+ matrix:
+ php: ["8.1", "8.2", "8.3"]
+ os: [ubuntu-latest]
+ experimental: [false]
+ include:
+ - php: nightly
+ os: ubuntu-latest
+ experimental: true
+ - php: "8.3"
+ os: windows-latest
+ experimental: false
+ - php: "8.3"
+ os: macos-latest
+ experimental: false
+
+ steps:
+ - name: Harden Runner
+ uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
+ with:
+ egress-policy: audit
+
+ - name: Checkout Code
+ uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+ - name: SetUp PHP
+ id: setup-php
+ uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2
+ with:
+ php-version: "${{ matrix.php }}"
+ tools: phpunit
+ coverage: xdebug
+ extensions: xdebug,zip
+ - name: Get composer cache directory
+ id: composer-cache-common
+ if: "${{ runner.os != 'Windows' }}"
+ run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+ - name: Get composer cache directory
+ id: composer-cache-windows
+ if: "${{ runner.os == 'Windows' }}"
+ run: echo "dir=$(composer config cache-files-dir)" >> $env:GITHUB_OUTPUT
+ - name: Cache Deps
+ uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
+ id: cache
+ with:
+ path: ${{ steps.composer-cache-common.outputs.dir }}${{ steps.composer-cache-windows.outputs.dir }}
+ key: deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}-composer-${{ hashFiles('**/composer.lock') }}
+ restore-keys: |
+ deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}-composer-
+ deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}-
+ deps-${{ steps.setup-php.outputs.php-version }}-
+ deps-
+ - name: Install Deps
+ if: matrix.php != 'nightly'
+ run: composer install --prefer-dist
+ - name: Install Deps (ignore PHP requirement)
+ if: matrix.php == 'nightly'
+ run: composer install --prefer-dist --ignore-platform-req=php+
+ - name: Run PHPUnit
+ run: composer run test:unit
+ env:
+ XDEBUG_MODE: coverage
+ - name: Upload coverage results to Coveralls
+ env:
+ COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ COVERALLS_PARALLEL: true
+ COVERALLS_FLAG_NAME: ${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}
+ run: composer run coverage:report
+ continue-on-error: ${{ matrix.experimental }}
+
+ mark_coverage_done:
+ needs: ["phpunit"]
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Harden Runner
+ uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
+ with:
+ egress-policy: audit
+
+ - name: Coveralls Finished
+ uses: coverallsapp/github-action@643bc377ffa44ace6394b2b5d0d3950076de9f63 # v2.3.0
+ with:
+ github-token: ${{ secrets.github_token }}
+ parallel-finished: true
+
+ psalm:
+ name: Run Psalm
+
+ runs-on: "ubuntu-latest"
+
+ permissions:
+ security-events: write
+
+ steps:
+ - name: Harden Runner
+ uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
+ with:
+ egress-policy: audit
+
+ - name: Checkout Code
+ uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+ - name: SetUp PHP
+ id: setup-php
+ uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2
+ with:
+ php-version: "8.3"
+ - name: Get composer cache directory
+ id: composer-cache
+ run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+ - name: Cache Deps
+ uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
+ id: cache
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}-composer-${{ hashFiles('**/composer.lock') }}
+ restore-keys: |
+ deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}-composer-
+ deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}-
+ deps-${{ steps.setup-php.outputs.php-version }}-
+ deps-
+ - name: Install Deps
+ run: composer install --prefer-dist
+ - name: Run Psalm
+ run: composer run test:lint -- --report=results.sarif
+ - name: "Upload SARIF"
+ uses: github/codeql-action/upload-sarif@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3
+ with:
+ sarif_file: results.sarif
+
+ php-cs:
+ name: Run PHP-CS
+
+ runs-on: "ubuntu-latest"
+
+ steps:
+ - name: Harden Runner
+ uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
+ with:
+ egress-policy: audit
+
+ - name: Checkout Code
+ uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+ - name: SetUp PHP
+ id: setup-php
+ uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2
+ with:
+ php-version: "8.3"
+ - name: Get composer cache directory
+ id: composer-cache
+ run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+ - name: Cache Deps
+ uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 # v4.1.1
+ id: cache
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}-composer-${{ hashFiles('**/composer.lock') }}
+ restore-keys: |
+ deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}-composer-
+ deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}-
+ deps-${{ steps.setup-php.outputs.php-version }}-
+ deps-
+ - name: Install Deps
+ run: composer install --prefer-dist
+ - name: Run PHP-CS
+ run: composer run test:formatted
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/pr.yml b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/pr.yml
new file mode 100644
index 0000000..05259d4
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/pr.yml
@@ -0,0 +1,50 @@
+on:
+ pull_request:
+ branches:
+ - "*"
+ workflow_dispatch: {}
+
+name: "Pull Request"
+
+permissions:
+ contents: read
+
+jobs:
+ test:
+ name: "Test"
+
+ permissions:
+ contents: read
+ security-events: write
+
+ uses: ./.github/workflows/part_test.yml
+
+ docs:
+ name: "Docs"
+
+ uses: ./.github/workflows/part_docs.yml
+
+ dependabot:
+ name: "Dependabot"
+
+ if: ${{ github.actor == 'dependabot[bot]'}}
+
+ permissions:
+ pull-requests: write
+ contents: write
+
+ uses: ./.github/workflows/part_dependabot.yml
+
+ dependency-review:
+ name: Dependency Review
+ runs-on: ubuntu-latest
+ steps:
+ - name: Harden Runner
+ uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
+ with:
+ egress-policy: audit
+
+ - name: 'Checkout Repository'
+ uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+ - name: 'Dependency Review'
+ uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/scorecard.yml b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/scorecard.yml
new file mode 100644
index 0000000..7bb8dbb
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/scorecard.yml
@@ -0,0 +1,78 @@
+# This workflow uses actions that are not certified by GitHub. They are provided
+# by a third-party and are governed by separate terms of service, privacy
+# policy, and support documentation.
+
+name: Scorecard supply-chain security
+on:
+ # For Branch-Protection check. Only the default branch is supported. See
+ # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
+ branch_protection_rule:
+ # To guarantee Maintained check is occasionally updated. See
+ # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
+ schedule:
+ - cron: '28 11 * * 3'
+ push:
+ branches: [ "main" ]
+
+# Declare default permissions as read only.
+permissions: read-all
+
+jobs:
+ analysis:
+ name: Scorecard analysis
+ runs-on: ubuntu-latest
+ permissions:
+ # Needed to upload the results to code-scanning dashboard.
+ security-events: write
+ # Needed to publish results and get a badge (see publish_results below).
+ id-token: write
+ # Uncomment the permissions below if installing in a private repository.
+ # contents: read
+ # actions: read
+
+ steps:
+ - name: Harden Runner
+ uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
+ with:
+ egress-policy: audit
+
+ - name: "Checkout code"
+ uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+ with:
+ persist-credentials: false
+
+ - name: "Run analysis"
+ uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
+ with:
+ results_file: results.sarif
+ results_format: sarif
+ # (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
+ # - you want to enable the Branch-Protection check on a *public* repository, or
+ # - you are installing Scorecard on a *private* repository
+ # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional.
+ # repo_token: ${{ secrets.SCORECARD_TOKEN }}
+
+ # Public repositories:
+ # - Publish results to OpenSSF REST API for easy access by consumers
+ # - Allows the repository to include the Scorecard badge.
+ # - See https://github.com/ossf/scorecard-action#publishing-results.
+ # For private repositories:
+ # - `publish_results` will always be set to `false`, regardless
+ # of the value entered here.
+ publish_results: true
+
+ # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
+ # format to the repository Actions tab.
+ - name: "Upload artifact"
+ uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4.4.1
+ with:
+ name: SARIF file
+ path: results.sarif
+ retention-days: 5
+
+ # Upload the results to GitHub's code scanning dashboard (optional).
+ # Commenting out will disable upload of results to your repo's Code Scanning dashboard
+ - name: "Upload to code-scanning"
+ uses: github/codeql-action/upload-sarif@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12
+ with:
+ sarif_file: results.sarif
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/tag-beta.yml b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/tag-beta.yml
new file mode 100644
index 0000000..b339945
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/tag-beta.yml
@@ -0,0 +1,29 @@
+on:
+ push:
+ tags:
+ - "[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+"
+
+name: "Beta Tag"
+
+permissions:
+ contents: read
+
+jobs:
+ docs:
+ name: "Docs"
+
+ uses: ./.github/workflows/part_docs.yml
+
+ release:
+ name: "Release"
+
+ needs: ["docs"]
+
+ permissions:
+ id-token: write
+ contents: write
+ attestations: write
+
+ uses: ./.github/workflows/part_release.yml
+ with:
+ releaseName: "${{ github.ref_name }}"
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/tag-stable.yml b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/tag-stable.yml
new file mode 100644
index 0000000..0e91cf0
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.github/workflows/tag-stable.yml
@@ -0,0 +1,55 @@
+on:
+ push:
+ tags:
+ - "[0-9]+.[0-9]+.[0-9]+"
+
+name: "Stable Tag"
+
+permissions:
+ contents: read
+
+jobs:
+ docs:
+ name: "Docs"
+
+ uses: ./.github/workflows/part_docs.yml
+
+ release:
+ name: "Release"
+
+ needs: ["docs"]
+
+ permissions:
+ id-token: write
+ contents: write
+ attestations: write
+
+ uses: ./.github/workflows/part_release.yml
+ with:
+ releaseName: "${{ github.ref_name }}"
+ stable: true
+
+ deploy_pages:
+ name: "Deploy to GitHub Pages"
+
+ needs: ["release", "docs"]
+
+ runs-on: ubuntu-latest
+
+ permissions:
+ pages: write
+ id-token: write
+
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+
+ steps:
+ - name: Harden Runner
+ uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
+ with:
+ egress-policy: audit
+
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.gitignore b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.gitignore
new file mode 100644
index 0000000..e52a498
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.gitignore
@@ -0,0 +1,12 @@
+/composer.lock
+/cov
+/coverage.clover.xml
+/docs
+.idea
+/.php-cs-fixer.cache
+/.phpdoc/cache
+/.phpunit.result.cache
+/phpunit.xml
+/.phpunit.cache
+/tools
+/vendor
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.phive/phars.xml b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.phive/phars.xml
new file mode 100644
index 0000000..183927b
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.phive/phars.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.php-cs-fixer.dist.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.php-cs-fixer.dist.php
new file mode 100644
index 0000000..38d6a76
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.php-cs-fixer.dist.php
@@ -0,0 +1,70 @@
+
+ * @copyright 2022 Nicolas CARPi
+ * @see https://github.com/maennchen/ZipStream-PHP
+ * @license MIT
+ * @package maennchen/ZipStream-PHP
+ */
+
+use PhpCsFixer\Config;
+use PhpCsFixer\Finder;
+
+$finder = Finder::create()
+ ->exclude('.github')
+ ->exclude('.phpdoc')
+ ->exclude('docs')
+ ->exclude('tools')
+ ->exclude('vendor')
+ ->in(__DIR__);
+
+$config = new Config();
+return $config->setRules([
+ '@PER' => true,
+ '@PER:risky' => true,
+ '@PHP82Migration' => true,
+ '@PHPUnit84Migration:risky' => true,
+ 'array_syntax' => ['syntax' => 'short'],
+ 'class_attributes_separation' => true,
+ 'declare_strict_types' => true,
+ 'dir_constant' => true,
+ 'is_null' => true,
+ 'no_homoglyph_names' => true,
+ 'no_null_property_initialization' => true,
+ 'no_php4_constructor' => true,
+ 'no_unused_imports' => true,
+ 'no_useless_else' => true,
+ 'non_printable_character' => true,
+ 'ordered_imports' => true,
+ 'ordered_class_elements' => true,
+ 'php_unit_construct' => true,
+ 'pow_to_exponentiation' => true,
+ 'psr_autoloading' => true,
+ 'random_api_migration' => true,
+ 'return_assignment' => true,
+ 'self_accessor' => true,
+ 'semicolon_after_instruction' => true,
+ 'short_scalar_cast' => true,
+ 'simplified_null_return' => true,
+ 'single_class_element_per_statement' => true,
+ 'single_line_comment_style' => true,
+ 'single_quote' => true,
+ 'space_after_semicolon' => true,
+ 'standardize_not_equals' => true,
+ 'strict_param' => true,
+ 'ternary_operator_spaces' => true,
+ 'trailing_comma_in_multiline' => true,
+ 'trim_array_spaces' => true,
+ 'unary_operator_spaces' => true,
+ 'global_namespace_import' => [
+ 'import_classes' => true,
+ 'import_functions' => true,
+ 'import_constants' => true,
+ ],
+ ])
+ ->setFinder($finder)
+ ->setRiskyAllowed(true);
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.phpdoc/template/base.html.twig b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.phpdoc/template/base.html.twig
new file mode 100644
index 0000000..b7507fb
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.phpdoc/template/base.html.twig
@@ -0,0 +1,15 @@
+{% extends 'layout.html.twig' %}
+
+{% set topMenu = {
+ "menu": [
+ { "name": "Guides", "url": "https://maennchen.dev/ZipStream-PHP/guide/index.html"},
+ { "name": "API", "url": "https://maennchen.dev/ZipStream-PHP/classes/ZipStream-ZipStream.html"},
+ { "name": "Issues", "url": "https://github.com/maennchen/ZipStream-PHP/issues"},
+ ],
+ "social": [
+ { "iconClass": "fab fa-github", "url": "https://github.com/maennchen/ZipStream-PHP"},
+ { "iconClass": "fas fa-envelope-open-text", "url": "https://github.com/maennchen/ZipStream-PHP/discussions"},
+ { "iconClass": "fas fa-money-bill", "url": "https://opencollective.com/zipstream"},
+ ]
+}
+%}
\ No newline at end of file
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.tool-versions b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.tool-versions
new file mode 100644
index 0000000..4a3dc9d
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/.tool-versions
@@ -0,0 +1 @@
+php 8.3.1
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/LICENSE b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/LICENSE
new file mode 100644
index 0000000..ebe7fe2
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/LICENSE
@@ -0,0 +1,24 @@
+MIT License
+
+Copyright (C) 2007-2009 Paul Duncan
+Copyright (C) 2014 Jonatan Männchen
+Copyright (C) 2014 Jesse G. Donat
+Copyright (C) 2018 Nicolas CARPi
+
+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/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/README.md b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/README.md
new file mode 100644
index 0000000..858add0
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/README.md
@@ -0,0 +1,154 @@
+# ZipStream-PHP
+
+[](https://github.com/maennchen/ZipStream-PHP/actions/workflows/branch_main.yml)
+[](https://coveralls.io/github/maennchen/ZipStream-PHP?branch=main)
+[](https://packagist.org/packages/maennchen/zipstream-php)
+[](https://packagist.org/packages/maennchen/zipstream-php)
+[](https://opencollective.com/zipstream) [](LICENSE)
+[](https://www.bestpractices.dev/projects/9524)
+[](https://scorecard.dev/viewer/?uri=github.com/maennchen/ZipStream-PHP)
+
+## Unstable Branch
+
+The `main` branch is not stable. Please see the
+[releases](https://github.com/maennchen/ZipStream-PHP/releases) for a stable
+version.
+
+## Overview
+
+A fast and simple streaming zip file downloader for PHP. Using this library will
+save you from having to write the Zip to disk. You can directly send it to the
+user, which is much faster. It can work with S3 buckets or any PSR7 Stream.
+
+Please see the [LICENSE](LICENSE) file for licensing and warranty information.
+
+## Installation
+
+Simply add a dependency on maennchen/zipstream-php to your project's
+`composer.json` file if you use Composer to manage the dependencies of your
+project. Use following command to add the package to your project's dependencies:
+
+```bash
+composer require maennchen/zipstream-php
+```
+
+## Usage
+
+For detailed instructions, please check the
+[Documentation](https://maennchen.github.io/ZipStream-PHP/).
+
+```php
+// Autoload the dependencies
+require 'vendor/autoload.php';
+
+// create a new zipstream object
+$zip = new ZipStream\ZipStream(
+ outputName: 'example.zip',
+
+ // enable output of HTTP headers
+ sendHttpHeaders: true,
+);
+
+// create a file named 'hello.txt'
+$zip->addFile(
+ fileName: 'hello.txt',
+ data: 'This is the contents of hello.txt',
+);
+
+// add a file named 'some_image.jpg' from a local file 'path/to/image.jpg'
+$zip->addFileFromPath(
+ fileName: 'some_image.jpg',
+ path: 'path/to/image.jpg',
+);
+
+// finish the zip stream
+$zip->finish();
+```
+
+## Upgrade to version 3.0.0
+
+### General
+
+- Minimum PHP Version: `8.1`
+- Only 64bit Architecture is supported.
+- The class `ZipStream\Option\Method` has been replaced with the enum
+ `ZipStream\CompressionMethod`.
+- Most clases have been flagged as `@internal` and should not be used from the
+ outside.
+ If you're using internal resources to extend this library, please open an
+ issue so that a clean interface can be added & published.
+ The externally available classes & enums are:
+ - `ZipStream\CompressionMethod`
+ - `ZipStream\Exception*`
+ - `ZipStream\ZipStream`
+
+### Archive Options
+
+- The class `ZipStream\Option\Archive` has been replaced in favor of named
+ arguments in the `ZipStream\ZipStream` constuctor.
+- The archive options `largeFileSize` & `largeFileMethod` has been removed. If
+ you want different `compressionMethods` based on the file size, you'll have to
+ implement this yourself.
+- The archive option `httpHeaderCallback` changed the type from `callable` to
+ `Closure`.
+- The archive option `zeroHeader` has been replaced with the option
+ `defaultEnableZeroHeader` and can be overridden for every file. Its default
+ value changed from `false` to `true`.
+- The archive option `statFiles` was removed since the library no longer checks
+ filesizes this way.
+- The archive option `deflateLevel` has been replaced with the option
+ `defaultDeflateLevel` and can be overridden for every file.
+- The first argument (`name`) of the `ZipStream\ZipStream` constuctor has been
+ replaced with the named argument `outputName`.
+- Headers are now also sent if the `outputName` is empty. If you do not want to
+ automatically send http headers, set `sendHttpHeaders` to `false`.
+
+### File Options
+
+- The class `ZipStream\Option\File` has been replaced in favor of named
+ arguments in the `ZipStream\ZipStream->addFile*` functions.
+- The file option `method` has been renamed to `compressionMethod`.
+- The file option `time` has been renamed to `lastModificationDateTime`.
+- The file option `size` has been renamed to `maxSize`.
+
+## Upgrade to version 2.0.0
+
+https://github.com/maennchen/ZipStream-PHP/tree/2.0.0#upgrade-to-version-200
+
+## Upgrade to version 1.0.0
+
+https://github.com/maennchen/ZipStream-PHP/tree/2.0.0#upgrade-to-version-100
+
+## Contributing
+
+ZipStream-PHP is a collaborative project. Please take a look at the
+[.github/CONTRIBUTING.md](.github/CONTRIBUTING.md) file.
+
+## Version Support
+
+Versions are supported according to the table below.
+
+Please do not open any pull requests contradicting the current version support
+status.
+
+Careful: Always check the `README` on `main` for up-to-date information.
+
+| Version | New Features | Bugfixes | Security |
+|---------|--------------|----------|----------|
+| *3* | ✓ | ✓ | ✓ |
+| *2* | ✗ | ✗ | ✓ |
+| *1* | ✗ | ✗ | ✗ |
+| *0* | ✗ | ✗ | ✗ |
+
+This library aligns itself with the PHP core support. New features and bugfixes
+will only target PHP versions according to their current status.
+
+See: https://www.php.net/supported-versions.php
+
+## About the Authors
+
+- Paul Duncan - https://pablotron.org/
+- Jonatan Männchen - https://maennchen.dev
+- Jesse G. Donat - https://donatstudios.com
+- Nicolas CARPi - https://www.deltablot.com
+- Nik Barham - https://www.brokencube.co.uk
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/composer.json b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/composer.json
new file mode 100644
index 0000000..de5e624
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/composer.json
@@ -0,0 +1,88 @@
+{
+ "name": "maennchen/zipstream-php",
+ "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.",
+ "keywords": ["zip", "stream"],
+ "type": "library",
+ "license": "MIT",
+ "authors": [{
+ "name": "Paul Duncan",
+ "email": "pabs@pablotron.org"
+ },
+ {
+ "name": "Jonatan Männchen",
+ "email": "jonatan@maennchen.ch"
+ },
+ {
+ "name": "Jesse Donat",
+ "email": "donatj@gmail.com"
+ },
+ {
+ "name": "András Kolesár",
+ "email": "kolesar@kolesar.hu"
+ }
+ ],
+ "require": {
+ "php-64bit": "^8.1",
+ "ext-mbstring": "*",
+ "ext-zlib": "*"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0",
+ "guzzlehttp/guzzle": "^7.5",
+ "ext-zip": "*",
+ "mikey179/vfsstream": "^1.6",
+ "php-coveralls/php-coveralls": "^2.5",
+ "friendsofphp/php-cs-fixer": "^3.16",
+ "vimeo/psalm": "^5.0"
+ },
+ "suggest": {
+ "psr/http-message": "^2.0",
+ "guzzlehttp/psr7": "^2.4"
+ },
+ "scripts": {
+ "format": "php-cs-fixer fix",
+ "test": [
+ "@test:unit",
+ "@test:formatted",
+ "@test:lint"
+ ],
+ "test:unit": "phpunit --coverage-clover=coverage.clover.xml --coverage-html cov",
+ "test:unit:slow": "@test:unit --group slow",
+ "test:unit:fast": "@test:unit --exclude-group slow",
+ "test:formatted": "@format --dry-run --stop-on-violation --using-cache=no",
+ "test:lint": "psalm --stats --show-info=true --find-unused-psalm-suppress",
+ "coverage:report": "php-coveralls --coverage_clover=coverage.clover.xml --json_path=coveralls-upload.json --insecure",
+ "install:tools": "phive install --trust-gpg-keys 0x67F861C3D889C656 --trust-gpg-keys 0x8AC0BAA79732DD42",
+ "docs:generate": "tools/phpdocumentor --sourcecode"
+ },
+ "autoload": {
+ "psr-4": {
+ "ZipStream\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": { "ZipStream\\Test\\": "test/" }
+ },
+ "archive": {
+ "exclude": [
+ "/composer.lock",
+ "/docs",
+ "/.gitattributes",
+ "/.github",
+ "/.gitignore",
+ "/guides",
+ "/.phive",
+ "/.php-cs-fixer.cache",
+ "/.php-cs-fixer.dist.php",
+ "/.phpdoc",
+ "/phpdoc.dist.xml",
+ "/.phpunit.result.cache",
+ "/phpunit.xml.dist",
+ "/psalm.xml",
+ "/test",
+ "/tools",
+ "/.tool-versions",
+ "/vendor"
+ ]
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/ContentLength.rst b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/ContentLength.rst
new file mode 100644
index 0000000..21fea34
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/ContentLength.rst
@@ -0,0 +1,47 @@
+Adding Content-Length header
+=============
+
+Adding a ``Content-Length`` header for ``ZipStream`` can be achieved by
+using the options ``SIMULATION_STRICT`` or ``SIMULATION_LAX`` in the
+``operationMode`` parameter.
+
+In the ``SIMULATION_STRICT`` mode, ``ZipStream`` will not allow to calculate the
+size based on reading the whole file. ``SIMULATION_LAX`` will read the whole
+file if neccessary.
+
+``SIMULATION_STRICT`` is therefore useful to make sure that the size can be
+calculated efficiently.
+
+.. code-block:: php
+ use ZipStream\OperationMode;
+ use ZipStream\ZipStream;
+
+ $zip = new ZipStream(
+ operationMode: OperationMode::SIMULATE_STRICT, // or SIMULATE_LAX
+ defaultEnableZeroHeader: false,
+ sendHttpHeaders: true,
+ outputStream: $stream,
+ );
+
+ // Normally add files
+ $zip->addFile('sample.txt', 'Sample String Data');
+
+ // Use addFileFromCallback and exactSize if you want to defer opening of
+ // the file resource
+ $zip->addFileFromCallback(
+ 'sample.txt',
+ exactSize: 18,
+ callback: function () {
+ return fopen('...');
+ }
+ );
+
+ // Read resulting file size
+ $size = $zip->finish();
+
+ // Tell it to the browser
+ header('Content-Length: '. $size);
+
+ // Execute the Simulation and stream the actual zip to the client
+ $zip->executeSimulation();
+
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/FlySystem.rst b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/FlySystem.rst
new file mode 100644
index 0000000..4e6c6fb
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/FlySystem.rst
@@ -0,0 +1,34 @@
+Usage with FlySystem
+===============
+
+For saving or uploading the generated zip, you can use the
+`Flysystem `_ package, and its many
+adapters.
+
+For that you will need to provide another stream than the ``php://output``
+default one, and pass it to Flysystem ``putStream`` method.
+
+.. code-block:: php
+
+ // Open Stream only once for read and write since it's a memory stream and
+ // the content is lost when closing the stream / opening another one
+ $tempStream = fopen('php://memory', 'w+');
+
+ // Create Zip Archive
+ $zipStream = new ZipStream(
+ outputStream: $tempStream,
+ outputName: 'test.zip',
+ );
+ $zipStream->addFile('test.txt', 'text');
+ $zipStream->finish();
+
+ // Store File
+ // (see Flysystem documentation, and all its framework integration)
+ // Can be any adapter (AWS, Google, Ftp, etc.)
+ $adapter = new Local(__DIR__.'/path/to/folder');
+ $filesystem = new Filesystem($adapter);
+
+ $filesystem->writeStream('test.zip', $tempStream)
+
+ // Close Stream
+ fclose($tempStream);
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/Nginx.rst b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/Nginx.rst
new file mode 100644
index 0000000..c53d300
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/Nginx.rst
@@ -0,0 +1,16 @@
+Usage with nginx
+=============
+
+If you are using nginx as a webserver, it will try to buffer the response.
+So you'll want to disable this with a custom header:
+
+.. code-block:: php
+ header('X-Accel-Buffering: no');
+ # or with the Response class from Symfony
+ $response->headers->set('X-Accel-Buffering', 'no');
+
+Alternatively, you can tweak the
+`fastcgi cache parameters `_
+within nginx config.
+
+See `original issue `_.
\ No newline at end of file
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/Options.rst b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/Options.rst
new file mode 100644
index 0000000..5e92e94
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/Options.rst
@@ -0,0 +1,66 @@
+Available options
+===============
+
+Here is the full list of options available to you. You can also have a look at
+``src/ZipStream.php`` file.
+
+.. code-block:: php
+
+ use ZipStream\ZipStream;
+
+ require_once 'vendor/autoload.php';
+
+ $zip = new ZipStream(
+ // Define output stream
+ // (argument is eiter a resource or implementing
+ // `Psr\Http\Message\StreamInterface`)
+ //
+ // Setup with `psr/http-message` & `guzzlehttp/psr7` dependencies
+ // required when using `Psr\Http\Message\StreamInterface`.
+ outputStream: $filePointer,
+
+ // Set the deflate level (default is 6; use -1 to disable it)
+ defaultDeflateLevel: 6,
+
+ // Add a comment to the zip file
+ comment: 'This is a comment.',
+
+ // Send http headers (default is true)
+ sendHttpHeaders: false,
+
+ // HTTP Content-Disposition.
+ // Defaults to 'attachment', where FILENAME is the specified filename.
+ // Note that this does nothing if you are not sending HTTP headers.
+ contentDisposition: 'attachment',
+
+ // Output Name for HTTP Content-Disposition
+ // Defaults to no name
+ outputName: "example.zip",
+
+ // HTTP Content-Type.
+ // Defaults to 'application/x-zip'.
+ // Note that this does nothing if you are not sending HTTP headers.
+ contentType: 'application/x-zip',
+
+ // Set the function called for setting headers.
+ // Default is the `header()` of PHP
+ httpHeaderCallback: header(...),
+
+ // Enable streaming files with single read where general purpose bit 3
+ // indicates local file header contain zero values in crc and size
+ // fields, these appear only after file contents in data descriptor
+ // block.
+ // Set to true if your input stream is remote
+ // (used with addFileFromStream()).
+ // Default is false.
+ defaultEnableZeroHeader: false,
+
+ // Enable zip64 extension, allowing very large archives
+ // (> 4Gb or file count > 64k)
+ // Default is true
+ enableZip64: true,
+
+ // Flush output buffer after every write
+ // Default is false
+ flushOutput: true,
+ );
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/PSR7Streams.rst b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/PSR7Streams.rst
new file mode 100644
index 0000000..22af71d
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/PSR7Streams.rst
@@ -0,0 +1,21 @@
+Usage with PSR 7 Streams
+===============
+
+PSR-7 streams are `standardized streams `_.
+
+ZipStream-PHP supports working with these streams with the function
+``addFileFromPsr7Stream``.
+
+For all parameters of the function see the API documentation.
+
+Example
+---------------
+
+.. code-block:: php
+
+ $stream = $response->getBody();
+ // add a file named 'streamfile.txt' from the content of the stream
+ $zip->addFileFromPsr7Stream(
+ fileName: 'streamfile.txt',
+ stream: $stream,
+ );
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/StreamOutput.rst b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/StreamOutput.rst
new file mode 100644
index 0000000..9f3165b
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/StreamOutput.rst
@@ -0,0 +1,39 @@
+Stream Output
+===============
+
+Stream to S3 Bucket
+---------------
+
+.. code-block:: php
+
+ use Aws\S3\S3Client;
+ use Aws\Credentials\CredentialProvider;
+ use ZipStream\ZipStream;
+
+ $bucket = 'your bucket name';
+ $client = new S3Client([
+ 'region' => 'your region',
+ 'version' => 'latest',
+ 'bucketName' => $bucket,
+ 'credentials' => CredentialProvider::defaultProvider(),
+ ]);
+ $client->registerStreamWrapper();
+
+ $zipFile = fopen("s3://$bucket/example.zip", 'w');
+
+ $zip = new ZipStream(
+ enableZip64: false,
+ outputStream: $zipFile,
+ );
+
+ $zip->addFile(
+ fileName: 'file1.txt',
+ data: 'File1 data',
+ );
+ $zip->addFile(
+ fileName: 'file2.txt',
+ data: 'File2 data',
+ );
+ $zip->finish();
+
+ fclose($zipFile);
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/Symfony.rst b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/Symfony.rst
new file mode 100644
index 0000000..902552c
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/Symfony.rst
@@ -0,0 +1,130 @@
+Usage with Symfony
+===============
+
+Overview for using ZipStream in Symfony
+--------
+
+Using ZipStream in Symfony requires use of Symfony's ``StreamedResponse`` when
+used in controller actions.
+
+Wrap your call to the relevant ``ZipStream`` stream method (i.e. ``addFile``,
+``addFileFromPath``, ``addFileFromStream``) in Symfony's ``StreamedResponse``
+function passing in any required arguments for your use case.
+
+Using Symfony's ``StreamedResponse`` will allow Symfony to stream output from
+ZipStream correctly to users' browsers and avoid a corrupted final zip landing
+on the users' end.
+
+Example for using ``ZipStream`` in a controller action to zip stream files
+stored in an AWS S3 bucket by key:
+
+.. code-block:: php
+
+ use Symfony\Component\HttpFoundation\StreamedResponse;
+ use Aws\S3\S3Client;
+ use ZipStream;
+
+ //...
+
+ /**
+ * @Route("/zipstream", name="zipstream")
+ */
+ public function zipStreamAction()
+ {
+ // sample test file on s3
+ $s3keys = array(
+ "ziptestfolder/file1.txt"
+ );
+
+ $s3Client = $this->get('app.amazon.s3'); //s3client service
+ $s3Client->registerStreamWrapper(); //required
+
+ // using StreamedResponse to wrap ZipStream functionality
+ // for files on AWS s3.
+ $response = new StreamedResponse(function() use($s3keys, $s3Client)
+ {
+ // Define suitable options for ZipStream Archive.
+ // this is needed to prevent issues with truncated zip files
+ //initialise zipstream with output zip filename and options.
+ $zip = new ZipStream\ZipStream(
+ outputName: 'test.zip',
+ defaultEnableZeroHeader: true,
+ contentType: 'application/octet-stream',
+ );
+
+ //loop keys - useful for multiple files
+ foreach ($s3keys as $key) {
+ // Get the file name in S3 key so we can save it to the zip
+ //file using the same name.
+ $fileName = basename($key);
+
+ // concatenate s3path.
+ // replace with your bucket name or get from parameters file.
+ $bucket = 'bucketname';
+ $s3path = "s3://" . $bucket . "/" . $key;
+
+ //addFileFromStream
+ if ($streamRead = fopen($s3path, 'r')) {
+ $zip->addFileFromStream(
+ fileName: $fileName,
+ stream: $streamRead,
+ );
+ } else {
+ die('Could not open stream for reading');
+ }
+ }
+
+ $zip->finish();
+
+ });
+
+ return $response;
+ }
+
+In the above example, files on AWS S3 are being streamed from S3 to the Symfon
+application via ``fopen`` call when the s3Client has ``registerStreamWrapper``
+applied. This stream is then passed to ``ZipStream`` via the
+``addFileFromStream`` function, which ZipStream then streams as a zip to the
+client browser via Symfony's ``StreamedResponse``. No Zip is created server
+side, which makes this approach a more efficient solution for streaming zips to
+the client browser especially for larger files.
+
+For the above use case you will need to have installed
+`aws/aws-sdk-php-symfony `_ to
+support accessing S3 objects in your Symfony web application. This is not
+required for locally stored files on you server you intend to stream via
+``ZipStream``.
+
+See official Symfony documentation for details on
+`Symfony's StreamedResponse `_
+``Symfony\Component\HttpFoundation\StreamedResponse``.
+
+Note from `S3 documentation `_:
+
+ Streams opened in "r" mode only allow data to be read from the stream, and
+ are not seekable by default. This is so that data can be downloaded from
+ Amazon S3 in a truly streaming manner, where previously read bytes do not
+ need to be buffered into memory. If you need a stream to be seekable, you
+ can pass seekable into the stream context options of a function.
+
+Make sure to configure your S3 context correctly!
+
+Uploading a file
+--------
+
+You need to add correct permissions
+(see `#120 `_)
+
+**example code**
+
+
+.. code-block:: php
+
+ $path = "s3://{$adapter->getBucket()}/{$this->getArchivePath()}";
+
+ // the important bit
+ $outputContext = stream_context_create([
+ 's3' => ['ACL' => 'public-read'],
+ ]);
+
+ fopen($path, 'w', null, $outputContext);
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/Varnish.rst b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/Varnish.rst
new file mode 100644
index 0000000..952d287
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/Varnish.rst
@@ -0,0 +1,22 @@
+Usage with Varnish
+=============
+
+Serving a big zip with varnish in between can cause random stream close.
+This can be solved by adding attached code to the vcl file.
+
+To avoid the problem, add the following to your varnish config file:
+
+.. code-block::
+ sub vcl_recv {
+ # Varnish can’t intercept the discussion anymore
+ # helps for streaming big zips
+ if (req.url ~ "\.(tar|gz|zip|7z|exe)$") {
+ return (pipe);
+ }
+ }
+ # Varnish can’t intercept the discussion anymore
+ # helps for streaming big zips
+ sub vcl_pipe {
+ set bereq.http.connection = "close";
+ return (pipe);
+ }
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/index.rst b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/index.rst
new file mode 100644
index 0000000..48f465a
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/guides/index.rst
@@ -0,0 +1,126 @@
+ZipStream PHP
+=============
+
+A fast and simple streaming zip file downloader for PHP. Using this library will
+save you from having to write the Zip to disk. You can directly send it to the
+user, which is much faster. It can work with S3 buckets or any PSR7 Stream.
+
+.. toctree::
+
+ index
+ Symfony
+ Options
+ StreamOutput
+ FlySystem
+ PSR7Streams
+ Nginx
+ Varnish
+ ContentLength
+
+Installation
+---------------
+
+Simply add a dependency on ``maennchen/zipstream-php`` to your project's
+``composer.json`` file if you use Composer to manage the dependencies of your
+project. Use following command to add the package to your project's
+dependencies:
+
+.. code-block:: sh
+ composer require maennchen/zipstream-php
+
+If you want to use``addFileFromPsr7Stream```
+(``Psr\Http\Message\StreamInterface``) or use a stream instead of a
+``resource`` as ``outputStream``, the following dependencies must be installed
+as well:
+
+.. code-block:: sh
+ composer require psr/http-message guzzlehttp/psr7
+
+If ``composer install`` yields the following error, your installation is missing
+the `mbstring extension `_,
+either `install it `_
+or run the follwoing command:
+
+.. code-block::
+ Your requirements could not be resolved to an installable set of packages.
+
+ Problem 1
+ - Root composer.json requires PHP extension ext-mbstring * but it is
+ missing from your system. Install or enable PHP's mbstrings extension.
+
+.. code-block:: sh
+ composer require symfony/polyfill-mbstring
+
+Usage Intro
+---------------
+
+Here's a simple example:
+
+.. code-block:: php
+
+ // Autoload the dependencies
+ require 'vendor/autoload.php';
+
+ // create a new zipstream object
+ $zip = new ZipStream\ZipStream(
+ outputName: 'example.zip',
+
+ // enable output of HTTP headers
+ sendHttpHeaders: true,
+ );
+
+ // create a file named 'hello.txt'
+ $zip->addFile(
+ fileName: 'hello.txt',
+ data: 'This is the contents of hello.txt',
+ );
+
+ // add a file named 'some_image.jpg' from a local file 'path/to/image.jpg'
+ $zip->addFileFromPath(
+ fileName: 'some_image.jpg',
+ path: 'path/to/image.jpg',
+ );
+
+ // add a file named 'goodbye.txt' from an open stream resource
+ $filePointer = tmpfile();
+ fwrite($filePointer, 'The quick brown fox jumped over the lazy dog.');
+ rewind($filePointer);
+ $zip->addFileFromStream(
+ fileName: 'goodbye.txt',
+ stream: $filePointer,
+ );
+ fclose($filePointer);
+
+ // add a file named 'streamfile.txt' from the body of a `guzzle` response
+ // Setup with `psr/http-message` & `guzzlehttp/psr7` dependencies required.
+ $zip->addFileFromPsr7Stream(
+ fileName: 'streamfile.txt',
+ stream: $response->getBody(),
+ );
+
+ // finish the zip stream
+ $zip->finish();
+
+You can also add comments, modify file timestamps, and customize (or
+disable) the HTTP headers. It is also possible to specify the storage method
+when adding files, the current default storage method is ``DEFLATE``
+i.e files are stored with Compression mode 0x08.
+
+Known Issues
+---------------
+
+The native Mac OS archive extraction tool prior to macOS 10.15 might not open
+archives in some conditions. A workaround is to disable the Zip64 feature with
+the option ``enableZip64: false``. This limits the archive to 4 Gb and 64k files
+but will allow users on macOS 10.14 and below to open them without issue.
+See `#116 `_.
+
+The linux ``unzip`` utility might not handle properly unicode characters.
+It is recommended to extract with another tool like
+`7-zip `_.
+See `#146 `_.
+
+It is the responsability of the client code to make sure that files are not
+saved with the same path, as it is not possible for the library to figure it out
+while streaming a zip.
+See `#154 `_.
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/phpdoc.dist.xml b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/phpdoc.dist.xml
new file mode 100644
index 0000000..b98fe1c
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/phpdoc.dist.xml
@@ -0,0 +1,39 @@
+
+
+ 💾 ZipStream-PHP
+
+
+
+
+ latest
+
+
+ src
+
+
+
+ tests/**/*
+ vendor/**/*
+
+
+ php
+
+ public
+ ZipStream
+ true
+
+
+
+ guides
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/phpunit.xml.dist b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/phpunit.xml.dist
new file mode 100644
index 0000000..1b02a3a
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/phpunit.xml.dist
@@ -0,0 +1,15 @@
+
+
+
+
+
+ test
+
+
+
+
+
+ src
+
+
+
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/psalm.xml b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/psalm.xml
new file mode 100644
index 0000000..4da8618
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/psalm.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/results.sarif b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/results.sarif
new file mode 100644
index 0000000..c99a3f4
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/results.sarif
@@ -0,0 +1 @@
+{"version":"2.1.0","$schema":"https:\/\/json.schemastore.org\/sarif-2.1.0.json","runs":[{"tool":{"driver":{"name":"Psalm","informationUri":"https:\/\/psalm.dev","version":"5.26.1@d747f6500b38ac4f7dfc5edbcae6e4b637d7add0"}},"results":[]}]}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/CentralDirectoryFileHeader.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/CentralDirectoryFileHeader.php
new file mode 100644
index 0000000..ffcfc6e
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/CentralDirectoryFileHeader.php
@@ -0,0 +1,52 @@
+value),
+ new PackField(format: 'V', value: Time::dateTimeToDosTime($lastModificationDateTime)),
+ new PackField(format: 'V', value: $crc32),
+ new PackField(format: 'V', value: $compressedSize),
+ new PackField(format: 'V', value: $uncompressedSize),
+ new PackField(format: 'v', value: strlen($fileName)),
+ new PackField(format: 'v', value: strlen($extraField)),
+ new PackField(format: 'v', value: strlen($fileComment)),
+ new PackField(format: 'v', value: $diskNumberStart),
+ new PackField(format: 'v', value: $internalFileAttributes),
+ new PackField(format: 'V', value: $externalFileAttributes),
+ new PackField(format: 'V', value: $relativeOffsetOfLocalHeader),
+ ) . $fileName . $extraField . $fileComment;
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/CompressionMethod.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/CompressionMethod.php
new file mode 100644
index 0000000..51e4363
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/CompressionMethod.php
@@ -0,0 +1,106 @@
+format(DateTimeInterface::ATOM) . " can't be represented as DOS time / date.");
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/Exception/FileNotFoundException.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/Exception/FileNotFoundException.php
new file mode 100644
index 0000000..350a7bf
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/Exception/FileNotFoundException.php
@@ -0,0 +1,22 @@
+resource = $resource;
+ parent::__construct('Function ' . $function . 'failed on resource.');
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/Exception/SimulationFileUnknownException.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/Exception/SimulationFileUnknownException.php
new file mode 100644
index 0000000..717c1aa
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/Exception/SimulationFileUnknownException.php
@@ -0,0 +1,19 @@
+fileName = self::filterFilename($fileName);
+ $this->checkEncoding();
+
+ if ($this->enableZeroHeader) {
+ $this->generalPurposeBitFlag |= GeneralPurposeBitFlag::ZERO_HEADER;
+ }
+
+ $this->version = $this->compressionMethod === CompressionMethod::DEFLATE ? Version::DEFLATE : Version::STORE;
+ }
+
+ public function cloneSimulationExecution(): self
+ {
+ return new self(
+ $this->fileName,
+ $this->dataCallback,
+ OperationMode::NORMAL,
+ $this->startOffset,
+ $this->compressionMethod,
+ $this->comment,
+ $this->lastModificationDateTime,
+ $this->deflateLevel,
+ $this->maxSize,
+ $this->exactSize,
+ $this->enableZip64,
+ $this->enableZeroHeader,
+ $this->send,
+ $this->recordSentBytes,
+ );
+ }
+
+ public function process(): string
+ {
+ $forecastSize = $this->forecastSize();
+
+ if ($this->enableZeroHeader) {
+ // No calculation required
+ } elseif ($this->isSimulation() && $forecastSize !== null) {
+ $this->uncompressedSize = $forecastSize;
+ $this->compressedSize = $forecastSize;
+ } else {
+ $this->readStream(send: false);
+ if (rewind($this->unpackStream()) === false) {
+ throw new ResourceActionException('rewind', $this->unpackStream());
+ }
+ }
+
+ $this->addFileHeader();
+
+ $detectedSize = $forecastSize ?? ($this->compressedSize > 0 ? $this->compressedSize : null);
+
+ if (
+ $this->isSimulation() &&
+ $detectedSize !== null
+ ) {
+ ($this->recordSentBytes)($detectedSize);
+ } else {
+ $this->readStream(send: true);
+ }
+
+ $this->addFileFooter();
+ return $this->getCdrFile();
+ }
+
+ /**
+ * @return resource
+ */
+ private function unpackStream()
+ {
+ if ($this->stream) {
+ return $this->stream;
+ }
+
+ if ($this->operationMode === OperationMode::SIMULATE_STRICT) {
+ throw new SimulationFileUnknownException();
+ }
+
+ $this->stream = ($this->dataCallback)();
+
+ if (!$this->enableZeroHeader && !stream_get_meta_data($this->stream)['seekable']) {
+ throw new StreamNotSeekableException();
+ }
+ if (!(
+ str_contains(stream_get_meta_data($this->stream)['mode'], 'r')
+ || str_contains(stream_get_meta_data($this->stream)['mode'], 'w+')
+ || str_contains(stream_get_meta_data($this->stream)['mode'], 'a+')
+ || str_contains(stream_get_meta_data($this->stream)['mode'], 'x+')
+ || str_contains(stream_get_meta_data($this->stream)['mode'], 'c+')
+ )) {
+ throw new StreamNotReadableException();
+ }
+
+ return $this->stream;
+ }
+
+ private function forecastSize(): ?int
+ {
+ if ($this->compressionMethod !== CompressionMethod::STORE) {
+ return null;
+ }
+ if ($this->exactSize !== null) {
+ return $this->exactSize;
+ }
+ $fstat = fstat($this->unpackStream());
+ if (!$fstat || !array_key_exists('size', $fstat) || $fstat['size'] < 1) {
+ return null;
+ }
+
+ if ($this->maxSize !== null && $this->maxSize < $fstat['size']) {
+ return $this->maxSize;
+ }
+
+ return $fstat['size'];
+ }
+
+ /**
+ * Create and send zip header for this file.
+ */
+ private function addFileHeader(): void
+ {
+ $forceEnableZip64 = $this->enableZeroHeader && $this->enableZip64;
+
+ $footer = $this->buildZip64ExtraBlock($forceEnableZip64);
+
+ $zip64Enabled = $footer !== '';
+
+ if ($zip64Enabled) {
+ $this->version = Version::ZIP64;
+ }
+
+ if ($this->generalPurposeBitFlag & GeneralPurposeBitFlag::EFS) {
+ // Put the tricky entry to
+ // force Linux unzip to lookup EFS flag.
+ $footer .= Zs\ExtendedInformationExtraField::generate();
+ }
+
+ $data = LocalFileHeader::generate(
+ versionNeededToExtract: $this->version->value,
+ generalPurposeBitFlag: $this->generalPurposeBitFlag,
+ compressionMethod: $this->compressionMethod,
+ lastModificationDateTime: $this->lastModificationDateTime,
+ crc32UncompressedData: $this->crc,
+ compressedSize: $zip64Enabled
+ ? 0xFFFFFFFF
+ : $this->compressedSize,
+ uncompressedSize: $zip64Enabled
+ ? 0xFFFFFFFF
+ : $this->uncompressedSize,
+ fileName: $this->fileName,
+ extraField: $footer,
+ );
+
+
+ ($this->send)($data);
+ }
+
+ /**
+ * Strip characters that are not legal in Windows filenames
+ * to prevent compatibility issues
+ */
+ private static function filterFilename(
+ /**
+ * Unprocessed filename
+ */
+ string $fileName
+ ): string {
+ // strip leading slashes from file name
+ // (fixes bug in windows archive viewer)
+ $fileName = ltrim($fileName, '/');
+
+ return str_replace(['\\', ':', '*', '?', '"', '<', '>', '|'], '_', $fileName);
+ }
+
+ private function checkEncoding(): void
+ {
+ // Sets Bit 11: Language encoding flag (EFS). If this bit is set,
+ // the filename and comment fields for this file
+ // MUST be encoded using UTF-8. (see APPENDIX D)
+ if (mb_check_encoding($this->fileName, 'UTF-8') &&
+ mb_check_encoding($this->comment, 'UTF-8')) {
+ $this->generalPurposeBitFlag |= GeneralPurposeBitFlag::EFS;
+ }
+ }
+
+ private function buildZip64ExtraBlock(bool $force = false): string
+ {
+ $outputZip64ExtraBlock = false;
+
+ $originalSize = null;
+ if ($force || $this->uncompressedSize > 0xFFFFFFFF) {
+ $outputZip64ExtraBlock = true;
+ $originalSize = $this->uncompressedSize;
+ }
+
+ $compressedSize = null;
+ if ($force || $this->compressedSize > 0xFFFFFFFF) {
+ $outputZip64ExtraBlock = true;
+ $compressedSize = $this->compressedSize;
+ }
+
+ // If this file will start over 4GB limit in ZIP file,
+ // CDR record will have to use Zip64 extension to describe offset
+ // to keep consistency we use the same value here
+ $relativeHeaderOffset = null;
+ if ($this->startOffset > 0xFFFFFFFF) {
+ $outputZip64ExtraBlock = true;
+ $relativeHeaderOffset = $this->startOffset;
+ }
+
+ if (!$outputZip64ExtraBlock) {
+ return '';
+ }
+
+ if (!$this->enableZip64) {
+ throw new OverflowException();
+ }
+
+ return Zip64\ExtendedInformationExtraField::generate(
+ originalSize: $originalSize,
+ compressedSize: $compressedSize,
+ relativeHeaderOffset: $relativeHeaderOffset,
+ diskStartNumber: null,
+ );
+ }
+
+ private function addFileFooter(): void
+ {
+ if (($this->compressedSize > 0xFFFFFFFF || $this->uncompressedSize > 0xFFFFFFFF) && $this->version !== Version::ZIP64) {
+ throw new OverflowException();
+ }
+
+ if (!$this->enableZeroHeader) {
+ return;
+ }
+
+ if ($this->version === Version::ZIP64) {
+ $footer = Zip64\DataDescriptor::generate(
+ crc32UncompressedData: $this->crc,
+ compressedSize: $this->compressedSize,
+ uncompressedSize: $this->uncompressedSize,
+ );
+ } else {
+ $footer = DataDescriptor::generate(
+ crc32UncompressedData: $this->crc,
+ compressedSize: $this->compressedSize,
+ uncompressedSize: $this->uncompressedSize,
+ );
+ }
+
+ ($this->send)($footer);
+ }
+
+ private function readStream(bool $send): void
+ {
+ $this->compressedSize = 0;
+ $this->uncompressedSize = 0;
+ $hash = hash_init('crc32b');
+
+ $deflate = $this->compressionInit();
+
+ while (
+ !feof($this->unpackStream()) &&
+ ($this->maxSize === null || $this->uncompressedSize < $this->maxSize) &&
+ ($this->exactSize === null || $this->uncompressedSize < $this->exactSize)
+ ) {
+ $readLength = min(
+ ($this->maxSize ?? PHP_INT_MAX) - $this->uncompressedSize,
+ ($this->exactSize ?? PHP_INT_MAX) - $this->uncompressedSize,
+ self::CHUNKED_READ_BLOCK_SIZE
+ );
+
+ $data = fread($this->unpackStream(), $readLength);
+
+ hash_update($hash, $data);
+
+ $this->uncompressedSize += strlen($data);
+
+ if ($deflate) {
+ $data = deflate_add(
+ $deflate,
+ $data,
+ feof($this->unpackStream()) ? ZLIB_FINISH : ZLIB_NO_FLUSH
+ );
+ }
+
+ $this->compressedSize += strlen($data);
+
+ if ($send) {
+ ($this->send)($data);
+ }
+ }
+
+ if ($this->exactSize !== null && $this->uncompressedSize !== $this->exactSize) {
+ throw new FileSizeIncorrectException(expectedSize: $this->exactSize, actualSize: $this->uncompressedSize);
+ }
+
+ $this->crc = hexdec(hash_final($hash));
+ }
+
+ private function compressionInit(): ?DeflateContext
+ {
+ switch ($this->compressionMethod) {
+ case CompressionMethod::STORE:
+ // Noting to do
+ return null;
+ case CompressionMethod::DEFLATE:
+ $deflateContext = deflate_init(
+ ZLIB_ENCODING_RAW,
+ ['level' => $this->deflateLevel]
+ );
+
+ if (!$deflateContext) {
+ // @codeCoverageIgnoreStart
+ throw new RuntimeException("Can't initialize deflate context.");
+ // @codeCoverageIgnoreEnd
+ }
+
+ // False positive, resource is no longer returned from this function
+ return $deflateContext;
+ default:
+ // @codeCoverageIgnoreStart
+ throw new RuntimeException('Unsupported Compression Method ' . print_r($this->compressionMethod, true));
+ // @codeCoverageIgnoreEnd
+ }
+ }
+
+ private function getCdrFile(): string
+ {
+ $footer = $this->buildZip64ExtraBlock();
+
+ return CentralDirectoryFileHeader::generate(
+ versionMadeBy: ZipStream::ZIP_VERSION_MADE_BY,
+ versionNeededToExtract: $this->version->value,
+ generalPurposeBitFlag: $this->generalPurposeBitFlag,
+ compressionMethod: $this->compressionMethod,
+ lastModificationDateTime: $this->lastModificationDateTime,
+ crc32: $this->crc,
+ compressedSize: $this->compressedSize > 0xFFFFFFFF
+ ? 0xFFFFFFFF
+ : $this->compressedSize,
+ uncompressedSize: $this->uncompressedSize > 0xFFFFFFFF
+ ? 0xFFFFFFFF
+ : $this->uncompressedSize,
+ fileName: $this->fileName,
+ extraField: $footer,
+ fileComment: $this->comment,
+ diskNumberStart: 0,
+ internalFileAttributes: 0,
+ externalFileAttributes: 32,
+ relativeOffsetOfLocalHeader: $this->startOffset > 0xFFFFFFFF
+ ? 0xFFFFFFFF
+ : $this->startOffset,
+ );
+ }
+
+ private function isSimulation(): bool
+ {
+ return $this->operationMode === OperationMode::SIMULATE_LAX || $this->operationMode === OperationMode::SIMULATE_STRICT;
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/GeneralPurposeBitFlag.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/GeneralPurposeBitFlag.php
new file mode 100644
index 0000000..23a66d8
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/GeneralPurposeBitFlag.php
@@ -0,0 +1,89 @@
+value),
+ new PackField(format: 'V', value: Time::dateTimeToDosTime($lastModificationDateTime)),
+ new PackField(format: 'V', value: $crc32UncompressedData),
+ new PackField(format: 'V', value: $compressedSize),
+ new PackField(format: 'V', value: $uncompressedSize),
+ new PackField(format: 'v', value: strlen($fileName)),
+ new PackField(format: 'v', value: strlen($extraField)),
+ ) . $fileName . $extraField;
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/OperationMode.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/OperationMode.php
new file mode 100644
index 0000000..dd650f0
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/OperationMode.php
@@ -0,0 +1,35 @@
+format;
+ }, '');
+
+ $args = array_map(function (self $field) {
+ switch ($field->format) {
+ case 'V':
+ if ($field->value > self::MAX_V) {
+ throw new RuntimeException(print_r($field->value, true) . ' is larger than 32 bits');
+ }
+ break;
+ case 'v':
+ if ($field->value > self::MAX_v) {
+ throw new RuntimeException(print_r($field->value, true) . ' is larger than 16 bits');
+ }
+ break;
+ case 'P': break;
+ default:
+ break;
+ }
+
+ return $field->value;
+ }, $fields);
+
+ return pack($fmt, ...$args);
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/Time.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/Time.php
new file mode 100644
index 0000000..1b4121c
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/Time.php
@@ -0,0 +1,39 @@
+getTimestamp() < $dosMinimumDate->getTimestamp()) {
+ throw new DosTimeOverflowException(dateTime: $dateTime);
+ }
+
+ $dateTime = DateTimeImmutable::createFromInterface($dateTime)->sub(new DateInterval('P1980Y'));
+
+ [$year, $month, $day, $hour, $minute, $second] = explode(' ', $dateTime->format('Y n j G i s'));
+
+ return
+ ((int) $year << 25) |
+ ((int) $month << 21) |
+ ((int) $day << 16) |
+ ((int) $hour << 11) |
+ ((int) $minute << 5) |
+ ((int) $second >> 1);
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/Version.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/Version.php
new file mode 100644
index 0000000..c014f8a
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/Version.php
@@ -0,0 +1,12 @@
+addFile(fileName: 'world.txt', data: 'Hello World');
+ *
+ * // add second file
+ * $zip->addFile(fileName: 'moon.txt', data: 'Hello Moon');
+ * ```
+ *
+ * 3. Finish the zip stream:
+ *
+ * ```php
+ * $zip->finish();
+ * ```
+ *
+ * You can also add an archive comment, add comments to individual files,
+ * and adjust the timestamp of files. See the API documentation for each
+ * method below for additional information.
+ *
+ * ## Example
+ *
+ * ```php
+ * // create a new zip stream object
+ * $zip = new ZipStream(outputName: 'some_files.zip');
+ *
+ * // list of local files
+ * $files = array('foo.txt', 'bar.jpg');
+ *
+ * // read and add each file to the archive
+ * foreach ($files as $path)
+ * $zip->addFileFromPath(fileName: $path, $path);
+ *
+ * // write archive footer to stream
+ * $zip->finish();
+ * ```
+ */
+class ZipStream
+{
+ /**
+ * This number corresponds to the ZIP version/OS used (2 bytes)
+ * From: https://www.iana.org/assignments/media-types/application/zip
+ * The upper byte (leftmost one) indicates the host system (OS) for the
+ * file. Software can use this information to determine
+ * the line record format for text files etc. The current
+ * mappings are:
+ *
+ * 0 - MS-DOS and OS/2 (F.A.T. file systems)
+ * 1 - Amiga 2 - VAX/VMS
+ * 3 - *nix 4 - VM/CMS
+ * 5 - Atari ST 6 - OS/2 H.P.F.S.
+ * 7 - Macintosh 8 - Z-System
+ * 9 - CP/M 10 thru 255 - unused
+ *
+ * The lower byte (rightmost one) indicates the version number of the
+ * software used to encode the file. The value/10
+ * indicates the major version number, and the value
+ * mod 10 is the minor version number.
+ * Here we are using 6 for the OS, indicating OS/2 H.P.F.S.
+ * to prevent file permissions issues upon extract (see #84)
+ * 0x603 is 00000110 00000011 in binary, so 6 and 3
+ *
+ * @internal
+ */
+ public const ZIP_VERSION_MADE_BY = 0x603;
+
+ private bool $ready = true;
+
+ private int $offset = 0;
+
+ /**
+ * @var string[]
+ */
+ private array $centralDirectoryRecords = [];
+
+ /**
+ * @var resource
+ */
+ private $outputStream;
+
+ private readonly Closure $httpHeaderCallback;
+
+ /**
+ * @var File[]
+ */
+ private array $recordedSimulation = [];
+
+ /**
+ * Create a new ZipStream object.
+ *
+ * ##### Examples
+ *
+ * ```php
+ * // create a new zip file named 'foo.zip'
+ * $zip = new ZipStream(outputName: 'foo.zip');
+ *
+ * // create a new zip file named 'bar.zip' with a comment
+ * $zip = new ZipStream(
+ * outputName: 'bar.zip',
+ * comment: 'this is a comment for the zip file.',
+ * );
+ * ```
+ *
+ * @param OperationMode $operationMode
+ * The mode can be used to switch between `NORMAL` and `SIMULATION_*` modes.
+ * For details see the `OperationMode` documentation.
+ *
+ * Default to `NORMAL`.
+ *
+ * @param string $comment
+ * Archive Level Comment
+ *
+ * @param StreamInterface|resource|null $outputStream
+ * Override the output of the archive to a different target.
+ *
+ * By default the archive is sent to `STDOUT`.
+ *
+ * @param CompressionMethod $defaultCompressionMethod
+ * How to handle file compression. Legal values are
+ * `CompressionMethod::DEFLATE` (the default), or
+ * `CompressionMethod::STORE`. `STORE` sends the file raw and is
+ * significantly faster, while `DEFLATE` compresses the file and
+ * is much, much slower.
+ *
+ * @param int $defaultDeflateLevel
+ * Default deflation level. Only relevant if `compressionMethod`
+ * is `DEFLATE`.
+ *
+ * See details of [`deflate_init`](https://www.php.net/manual/en/function.deflate-init.php#refsect1-function.deflate-init-parameters)
+ *
+ * @param bool $enableZip64
+ * Enable Zip64 extension, supporting very large
+ * archives (any size > 4 GB or file count > 64k)
+ *
+ * @param bool $defaultEnableZeroHeader
+ * Enable streaming files with single read.
+ *
+ * When the zero header is set, the file is streamed into the output
+ * and the size & checksum are added at the end of the file. This is the
+ * fastest method and uses the least memory. Unfortunately not all
+ * ZIP clients fully support this and can lead to clients reporting
+ * the generated ZIP files as corrupted in combination with other
+ * circumstances. (Zip64 enabled, using UTF8 in comments / names etc.)
+ *
+ * When the zero header is not set, the length & checksum need to be
+ * defined before the file is actually added. To prevent loading all
+ * the data into memory, the data has to be read twice. If the data
+ * which is added is not seekable, this call will fail.
+ *
+ * @param bool $sendHttpHeaders
+ * Boolean indicating whether or not to send
+ * the HTTP headers for this file.
+ *
+ * @param ?Closure $httpHeaderCallback
+ * The method called to send HTTP headers
+ *
+ * @param string|null $outputName
+ * The name of the created archive.
+ *
+ * Only relevant if `$sendHttpHeaders = true`.
+ *
+ * @param string $contentDisposition
+ * HTTP Content-Disposition
+ *
+ * Only relevant if `sendHttpHeaders = true`.
+ *
+ * @param string $contentType
+ * HTTP Content Type
+ *
+ * Only relevant if `sendHttpHeaders = true`.
+ *
+ * @param bool $flushOutput
+ * Enable flush after every write to output stream.
+ *
+ * @return self
+ */
+ public function __construct(
+ private OperationMode $operationMode = OperationMode::NORMAL,
+ private readonly string $comment = '',
+ $outputStream = null,
+ private readonly CompressionMethod $defaultCompressionMethod = CompressionMethod::DEFLATE,
+ private readonly int $defaultDeflateLevel = 6,
+ private readonly bool $enableZip64 = true,
+ private readonly bool $defaultEnableZeroHeader = true,
+ private bool $sendHttpHeaders = true,
+ ?Closure $httpHeaderCallback = null,
+ private readonly ?string $outputName = null,
+ private readonly string $contentDisposition = 'attachment',
+ private readonly string $contentType = 'application/x-zip',
+ private bool $flushOutput = false,
+ ) {
+ $this->outputStream = self::normalizeStream($outputStream);
+ $this->httpHeaderCallback = $httpHeaderCallback ?? header(...);
+ }
+
+ /**
+ * Add a file to the archive.
+ *
+ * ##### File Options
+ *
+ * See {@see addFileFromPsr7Stream()}
+ *
+ * ##### Examples
+ *
+ * ```php
+ * // add a file named 'world.txt'
+ * $zip->addFile(fileName: 'world.txt', data: 'Hello World!');
+ *
+ * // add a file named 'bar.jpg' with a comment and a last-modified
+ * // time of two hours ago
+ * $zip->addFile(
+ * fileName: 'bar.jpg',
+ * data: $data,
+ * comment: 'this is a comment about bar.jpg',
+ * lastModificationDateTime: new DateTime('2 hours ago'),
+ * );
+ * ```
+ *
+ * @param string $data
+ *
+ * contents of file
+ */
+ public function addFile(
+ string $fileName,
+ string $data,
+ string $comment = '',
+ ?CompressionMethod $compressionMethod = null,
+ ?int $deflateLevel = null,
+ ?DateTimeInterface $lastModificationDateTime = null,
+ ?int $maxSize = null,
+ ?int $exactSize = null,
+ ?bool $enableZeroHeader = null,
+ ): void {
+ $this->addFileFromCallback(
+ fileName: $fileName,
+ callback: fn() => $data,
+ comment: $comment,
+ compressionMethod: $compressionMethod,
+ deflateLevel: $deflateLevel,
+ lastModificationDateTime: $lastModificationDateTime,
+ maxSize: $maxSize,
+ exactSize: $exactSize,
+ enableZeroHeader: $enableZeroHeader,
+ );
+ }
+
+ /**
+ * Add a file at path to the archive.
+ *
+ * ##### File Options
+ *
+ * See {@see addFileFromPsr7Stream()}
+ *
+ * ###### Examples
+ *
+ * ```php
+ * // add a file named 'foo.txt' from the local file '/tmp/foo.txt'
+ * $zip->addFileFromPath(
+ * fileName: 'foo.txt',
+ * path: '/tmp/foo.txt',
+ * );
+ *
+ * // add a file named 'bigfile.rar' from the local file
+ * // '/usr/share/bigfile.rar' with a comment and a last-modified
+ * // time of two hours ago
+ * $zip->addFileFromPath(
+ * fileName: 'bigfile.rar',
+ * path: '/usr/share/bigfile.rar',
+ * comment: 'this is a comment about bigfile.rar',
+ * lastModificationDateTime: new DateTime('2 hours ago'),
+ * );
+ * ```
+ *
+ * @throws \ZipStream\Exception\FileNotFoundException
+ * @throws \ZipStream\Exception\FileNotReadableException
+ */
+ public function addFileFromPath(
+ /**
+ * name of file in archive (including directory path).
+ */
+ string $fileName,
+
+ /**
+ * path to file on disk (note: paths should be encoded using
+ * UNIX-style forward slashes -- e.g '/path/to/some/file').
+ */
+ string $path,
+ string $comment = '',
+ ?CompressionMethod $compressionMethod = null,
+ ?int $deflateLevel = null,
+ ?DateTimeInterface $lastModificationDateTime = null,
+ ?int $maxSize = null,
+ ?int $exactSize = null,
+ ?bool $enableZeroHeader = null,
+ ): void {
+ if (!is_readable($path)) {
+ if (!file_exists($path)) {
+ throw new FileNotFoundException($path);
+ }
+ throw new FileNotReadableException($path);
+ }
+
+ $fileTime = filemtime($path);
+ if ($fileTime !== false) {
+ $lastModificationDateTime ??= (new DateTimeImmutable())->setTimestamp($fileTime);
+ }
+
+ $this->addFileFromCallback(
+ fileName: $fileName,
+ callback: function () use ($path) {
+
+ $stream = fopen($path, 'rb');
+
+ if (!$stream) {
+ // @codeCoverageIgnoreStart
+ throw new ResourceActionException('fopen');
+ // @codeCoverageIgnoreEnd
+ }
+
+ return $stream;
+ },
+ comment: $comment,
+ compressionMethod: $compressionMethod,
+ deflateLevel: $deflateLevel,
+ lastModificationDateTime: $lastModificationDateTime,
+ maxSize: $maxSize,
+ exactSize: $exactSize,
+ enableZeroHeader: $enableZeroHeader,
+ );
+ }
+
+ /**
+ * Add an open stream (resource) to the archive.
+ *
+ * ##### File Options
+ *
+ * See {@see addFileFromPsr7Stream()}
+ *
+ * ##### Examples
+ *
+ * ```php
+ * // create a temporary file stream and write text to it
+ * $filePointer = tmpfile();
+ * fwrite($filePointer, 'The quick brown fox jumped over the lazy dog.');
+ *
+ * // add a file named 'streamfile.txt' from the content of the stream
+ * $archive->addFileFromStream(
+ * fileName: 'streamfile.txt',
+ * stream: $filePointer,
+ * );
+ * ```
+ *
+ * @param resource $stream contents of file as a stream resource
+ */
+ public function addFileFromStream(
+ string $fileName,
+ $stream,
+ string $comment = '',
+ ?CompressionMethod $compressionMethod = null,
+ ?int $deflateLevel = null,
+ ?DateTimeInterface $lastModificationDateTime = null,
+ ?int $maxSize = null,
+ ?int $exactSize = null,
+ ?bool $enableZeroHeader = null,
+ ): void {
+ $this->addFileFromCallback(
+ fileName: $fileName,
+ callback: fn() => $stream,
+ comment: $comment,
+ compressionMethod: $compressionMethod,
+ deflateLevel: $deflateLevel,
+ lastModificationDateTime: $lastModificationDateTime,
+ maxSize: $maxSize,
+ exactSize: $exactSize,
+ enableZeroHeader: $enableZeroHeader,
+ );
+ }
+
+ /**
+ * Add an open stream to the archive.
+ *
+ * ##### Examples
+ *
+ * ```php
+ * $stream = $response->getBody();
+ * // add a file named 'streamfile.txt' from the content of the stream
+ * $archive->addFileFromPsr7Stream(
+ * fileName: 'streamfile.txt',
+ * stream: $stream,
+ * );
+ * ```
+ *
+ * @param string $fileName
+ * path of file in archive (including directory)
+ *
+ * @param StreamInterface $stream
+ * contents of file as a stream resource
+ *
+ * @param string $comment
+ * ZIP comment for this file
+ *
+ * @param ?CompressionMethod $compressionMethod
+ * Override `defaultCompressionMethod`
+ *
+ * See {@see __construct()}
+ *
+ * @param ?int $deflateLevel
+ * Override `defaultDeflateLevel`
+ *
+ * See {@see __construct()}
+ *
+ * @param ?DateTimeInterface $lastModificationDateTime
+ * Set last modification time of file.
+ *
+ * Default: `now`
+ *
+ * @param ?int $maxSize
+ * Only read `maxSize` bytes from file.
+ *
+ * The file is considered done when either reaching `EOF`
+ * or the `maxSize`.
+ *
+ * @param ?int $exactSize
+ * Read exactly `exactSize` bytes from file.
+ * If `EOF` is reached before reading `exactSize` bytes, an error will be
+ * thrown. The parameter allows for faster size calculations if the `stream`
+ * does not support `fstat` size or is slow and otherwise known beforehand.
+ *
+ * @param ?bool $enableZeroHeader
+ * Override `defaultEnableZeroHeader`
+ *
+ * See {@see __construct()}
+ */
+ public function addFileFromPsr7Stream(
+ string $fileName,
+ StreamInterface $stream,
+ string $comment = '',
+ ?CompressionMethod $compressionMethod = null,
+ ?int $deflateLevel = null,
+ ?DateTimeInterface $lastModificationDateTime = null,
+ ?int $maxSize = null,
+ ?int $exactSize = null,
+ ?bool $enableZeroHeader = null,
+ ): void {
+ $this->addFileFromCallback(
+ fileName: $fileName,
+ callback: fn() => $stream,
+ comment: $comment,
+ compressionMethod: $compressionMethod,
+ deflateLevel: $deflateLevel,
+ lastModificationDateTime: $lastModificationDateTime,
+ maxSize: $maxSize,
+ exactSize: $exactSize,
+ enableZeroHeader: $enableZeroHeader,
+ );
+ }
+
+ /**
+ * Add a file based on a callback.
+ *
+ * This is useful when you want to simulate a lot of files without keeping
+ * all of the file handles open at the same time.
+ *
+ * ##### Examples
+ *
+ * ```php
+ * foreach($files as $name => $size) {
+ * $archive->addFileFromCallback(
+ * fileName: 'streamfile.txt',
+ * exactSize: $size,
+ * callback: function() use($name): Psr\Http\Message\StreamInterface {
+ * $response = download($name);
+ * return $response->getBody();
+ * }
+ * );
+ * }
+ * ```
+ *
+ * @param string $fileName
+ * path of file in archive (including directory)
+ *
+ * @param Closure $callback
+ * @psalm-param Closure(): (resource|StreamInterface|string) $callback
+ * A callback to get the file contents in the shape of a PHP stream,
+ * a Psr StreamInterface implementation, or a string.
+ *
+ * @param string $comment
+ * ZIP comment for this file
+ *
+ * @param ?CompressionMethod $compressionMethod
+ * Override `defaultCompressionMethod`
+ *
+ * See {@see __construct()}
+ *
+ * @param ?int $deflateLevel
+ * Override `defaultDeflateLevel`
+ *
+ * See {@see __construct()}
+ *
+ * @param ?DateTimeInterface $lastModificationDateTime
+ * Set last modification time of file.
+ *
+ * Default: `now`
+ *
+ * @param ?int $maxSize
+ * Only read `maxSize` bytes from file.
+ *
+ * The file is considered done when either reaching `EOF`
+ * or the `maxSize`.
+ *
+ * @param ?int $exactSize
+ * Read exactly `exactSize` bytes from file.
+ * If `EOF` is reached before reading `exactSize` bytes, an error will be
+ * thrown. The parameter allows for faster size calculations if the `stream`
+ * does not support `fstat` size or is slow and otherwise known beforehand.
+ *
+ * @param ?bool $enableZeroHeader
+ * Override `defaultEnableZeroHeader`
+ *
+ * See {@see __construct()}
+ */
+ public function addFileFromCallback(
+ string $fileName,
+ Closure $callback,
+ string $comment = '',
+ ?CompressionMethod $compressionMethod = null,
+ ?int $deflateLevel = null,
+ ?DateTimeInterface $lastModificationDateTime = null,
+ ?int $maxSize = null,
+ ?int $exactSize = null,
+ ?bool $enableZeroHeader = null,
+ ): void {
+ $file = new File(
+ dataCallback: function () use ($callback, $maxSize) {
+ $data = $callback();
+
+ if (is_resource($data)) {
+ return $data;
+ }
+
+ if ($data instanceof StreamInterface) {
+ return StreamWrapper::getResource($data);
+ }
+
+
+ $stream = fopen('php://memory', 'rw+');
+ if ($stream === false) {
+ // @codeCoverageIgnoreStart
+ throw new ResourceActionException('fopen');
+ // @codeCoverageIgnoreEnd
+ }
+ if ($maxSize !== null && fwrite($stream, $data, $maxSize) === false) {
+ // @codeCoverageIgnoreStart
+ throw new ResourceActionException('fwrite', $stream);
+ // @codeCoverageIgnoreEnd
+ } elseif (fwrite($stream, $data) === false) {
+ // @codeCoverageIgnoreStart
+ throw new ResourceActionException('fwrite', $stream);
+ // @codeCoverageIgnoreEnd
+ }
+ if (rewind($stream) === false) {
+ // @codeCoverageIgnoreStart
+ throw new ResourceActionException('rewind', $stream);
+ // @codeCoverageIgnoreEnd
+ }
+
+ return $stream;
+
+ },
+ send: $this->send(...),
+ recordSentBytes: $this->recordSentBytes(...),
+ operationMode: $this->operationMode,
+ fileName: $fileName,
+ startOffset: $this->offset,
+ compressionMethod: $compressionMethod ?? $this->defaultCompressionMethod,
+ comment: $comment,
+ deflateLevel: $deflateLevel ?? $this->defaultDeflateLevel,
+ lastModificationDateTime: $lastModificationDateTime ?? new DateTimeImmutable(),
+ maxSize: $maxSize,
+ exactSize: $exactSize,
+ enableZip64: $this->enableZip64,
+ enableZeroHeader: $enableZeroHeader ?? $this->defaultEnableZeroHeader,
+ );
+
+ if ($this->operationMode !== OperationMode::NORMAL) {
+ $this->recordedSimulation[] = $file;
+ }
+
+ $this->centralDirectoryRecords[] = $file->process();
+ }
+
+ /**
+ * Add a directory to the archive.
+ *
+ * ##### File Options
+ *
+ * See {@see addFileFromPsr7Stream()}
+ *
+ * ##### Examples
+ *
+ * ```php
+ * // add a directory named 'world/'
+ * $zip->addDirectory(fileName: 'world/');
+ * ```
+ */
+ public function addDirectory(
+ string $fileName,
+ string $comment = '',
+ ?DateTimeInterface $lastModificationDateTime = null,
+ ): void {
+ if (!str_ends_with($fileName, '/')) {
+ $fileName .= '/';
+ }
+
+ $this->addFile(
+ fileName: $fileName,
+ data: '',
+ comment: $comment,
+ compressionMethod: CompressionMethod::STORE,
+ deflateLevel: null,
+ lastModificationDateTime: $lastModificationDateTime,
+ maxSize: 0,
+ exactSize: 0,
+ enableZeroHeader: false,
+ );
+ }
+
+ /**
+ * Executes a previously calculated simulation.
+ *
+ * ##### Example
+ *
+ * ```php
+ * $zip = new ZipStream(
+ * outputName: 'foo.zip',
+ * operationMode: OperationMode::SIMULATE_STRICT,
+ * );
+ *
+ * $zip->addFile('test.txt', 'Hello World');
+ *
+ * $size = $zip->finish();
+ *
+ * header('Content-Length: '. $size);
+ *
+ * $zip->executeSimulation();
+ * ```
+ */
+ public function executeSimulation(): void
+ {
+ if ($this->operationMode !== OperationMode::NORMAL) {
+ throw new RuntimeException('Zip simulation is not finished.');
+ }
+
+ foreach ($this->recordedSimulation as $file) {
+ $this->centralDirectoryRecords[] = $file->cloneSimulationExecution()->process();
+ }
+
+ $this->finish();
+ }
+
+ /**
+ * Write zip footer to stream.
+ *
+ * The clase is left in an unusable state after `finish`.
+ *
+ * ##### Example
+ *
+ * ```php
+ * // write footer to stream
+ * $zip->finish();
+ * ```
+ */
+ public function finish(): int
+ {
+ $centralDirectoryStartOffsetOnDisk = $this->offset;
+ $sizeOfCentralDirectory = 0;
+
+ // add trailing cdr file records
+ foreach ($this->centralDirectoryRecords as $centralDirectoryRecord) {
+ $this->send($centralDirectoryRecord);
+ $sizeOfCentralDirectory += strlen($centralDirectoryRecord);
+ }
+
+ // Add 64bit headers (if applicable)
+ if (count($this->centralDirectoryRecords) >= 0xFFFF ||
+ $centralDirectoryStartOffsetOnDisk > 0xFFFFFFFF ||
+ $sizeOfCentralDirectory > 0xFFFFFFFF) {
+ if (!$this->enableZip64) {
+ throw new OverflowException();
+ }
+
+ $this->send(Zip64\EndOfCentralDirectory::generate(
+ versionMadeBy: self::ZIP_VERSION_MADE_BY,
+ versionNeededToExtract: Version::ZIP64->value,
+ numberOfThisDisk: 0,
+ numberOfTheDiskWithCentralDirectoryStart: 0,
+ numberOfCentralDirectoryEntriesOnThisDisk: count($this->centralDirectoryRecords),
+ numberOfCentralDirectoryEntries: count($this->centralDirectoryRecords),
+ sizeOfCentralDirectory: $sizeOfCentralDirectory,
+ centralDirectoryStartOffsetOnDisk: $centralDirectoryStartOffsetOnDisk,
+ extensibleDataSector: '',
+ ));
+
+ $this->send(Zip64\EndOfCentralDirectoryLocator::generate(
+ numberOfTheDiskWithZip64CentralDirectoryStart: 0x00,
+ zip64centralDirectoryStartOffsetOnDisk: $centralDirectoryStartOffsetOnDisk + $sizeOfCentralDirectory,
+ totalNumberOfDisks: 1,
+ ));
+ }
+
+ // add trailing cdr eof record
+ $numberOfCentralDirectoryEntries = min(count($this->centralDirectoryRecords), 0xFFFF);
+ $this->send(EndOfCentralDirectory::generate(
+ numberOfThisDisk: 0x00,
+ numberOfTheDiskWithCentralDirectoryStart: 0x00,
+ numberOfCentralDirectoryEntriesOnThisDisk: $numberOfCentralDirectoryEntries,
+ numberOfCentralDirectoryEntries: $numberOfCentralDirectoryEntries,
+ sizeOfCentralDirectory: min($sizeOfCentralDirectory, 0xFFFFFFFF),
+ centralDirectoryStartOffsetOnDisk: min($centralDirectoryStartOffsetOnDisk, 0xFFFFFFFF),
+ zipFileComment: $this->comment,
+ ));
+
+ $size = $this->offset;
+
+ // The End
+ $this->clear();
+
+ return $size;
+ }
+
+ /**
+ * @param StreamInterface|resource|null $outputStream
+ * @return resource
+ */
+ private static function normalizeStream($outputStream)
+ {
+ if ($outputStream instanceof StreamInterface) {
+ return StreamWrapper::getResource($outputStream);
+ }
+ if (is_resource($outputStream)) {
+ return $outputStream;
+ }
+ return fopen('php://output', 'wb');
+ }
+
+ /**
+ * Record sent bytes
+ */
+ private function recordSentBytes(int $sentBytes): void
+ {
+ $this->offset += $sentBytes;
+ }
+
+ /**
+ * Send string, sending HTTP headers if necessary.
+ * Flush output after write if configure option is set.
+ */
+ private function send(string $data): void
+ {
+ if (!$this->ready) {
+ throw new RuntimeException('Archive is already finished');
+ }
+
+ if ($this->operationMode === OperationMode::NORMAL && $this->sendHttpHeaders) {
+ $this->sendHttpHeaders();
+ $this->sendHttpHeaders = false;
+ }
+
+ $this->recordSentBytes(strlen($data));
+
+ if ($this->operationMode === OperationMode::NORMAL) {
+ if (fwrite($this->outputStream, $data) === false) {
+ throw new ResourceActionException('fwrite', $this->outputStream);
+ }
+
+ if ($this->flushOutput) {
+ // flush output buffer if it is on and flushable
+ $status = ob_get_status();
+ if (isset($status['flags']) && is_int($status['flags']) && ($status['flags'] & PHP_OUTPUT_HANDLER_FLUSHABLE)) {
+ ob_flush();
+ }
+
+ // Flush system buffers after flushing userspace output buffer
+ flush();
+ }
+ }
+ }
+
+ /**
+ * Send HTTP headers for this stream.
+ */
+ private function sendHttpHeaders(): void
+ {
+ // grab content disposition
+ $disposition = $this->contentDisposition;
+
+ if ($this->outputName !== null) {
+ // Various different browsers dislike various characters here. Strip them all for safety.
+ $safeOutput = trim(str_replace(['"', "'", '\\', ';', "\n", "\r"], '', $this->outputName));
+
+ // Check if we need to UTF-8 encode the filename
+ $urlencoded = rawurlencode($safeOutput);
+ $disposition .= "; filename*=UTF-8''{$urlencoded}";
+ }
+
+ $headers = [
+ 'Content-Type' => $this->contentType,
+ 'Content-Disposition' => $disposition,
+ 'Pragma' => 'public',
+ 'Cache-Control' => 'public, must-revalidate',
+ 'Content-Transfer-Encoding' => 'binary',
+ ];
+
+ foreach ($headers as $key => $val) {
+ ($this->httpHeaderCallback)("$key: $val");
+ }
+ }
+
+ /**
+ * Clear all internal variables. Note that the stream object is not
+ * usable after this.
+ */
+ private function clear(): void
+ {
+ $this->centralDirectoryRecords = [];
+ $this->offset = 0;
+
+ if ($this->operationMode === OperationMode::NORMAL) {
+ $this->ready = false;
+ $this->recordedSimulation = [];
+ } else {
+ $this->operationMode = OperationMode::NORMAL;
+ }
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/Zs/ExtendedInformationExtraField.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/Zs/ExtendedInformationExtraField.php
new file mode 100644
index 0000000..bf621bc
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/src/Zs/ExtendedInformationExtraField.php
@@ -0,0 +1,23 @@
+fail("File {$filePath} must contain {$needle}");
+ }
+
+ protected function assertFileDoesNotContain(string $filePath, string $needle): void
+ {
+ $last = '';
+
+ $handle = fopen($filePath, 'r');
+ while (!feof($handle)) {
+ $line = fgets($handle, 1024);
+
+ if (str_contains($last . $line, $needle)) {
+ fclose($handle);
+
+ $this->fail("File {$filePath} must not contain {$needle}");
+ }
+
+ $last = $line;
+ }
+
+ fclose($handle);
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/CentralDirectoryFileHeaderTest.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/CentralDirectoryFileHeaderTest.php
new file mode 100644
index 0000000..5457b4f
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/CentralDirectoryFileHeaderTest.php
@@ -0,0 +1,60 @@
+assertSame(
+ bin2hex($header),
+ '504b0102' . // 4 bytes; central file header signature
+ '0306' . // 2 bytes; version made by
+ '2d00' . // 2 bytes; version needed to extract
+ '2222' . // 2 bytes; general purpose bit flag
+ '0800' . // 2 bytes; compression method
+ '2008' . // 2 bytes; last mod file time
+ '2154' . // 2 bytes; last mod file date
+ '11111111' . // 4 bytes; crc-32
+ '77777777' . // 4 bytes; compressed size
+ '99999999' . // 4 bytes; uncompressed size
+ '0800' . // 2 bytes; file name length (n)
+ '0c00' . // 2 bytes; extra field length (m)
+ '0c00' . // 2 bytes; file comment length (o)
+ '0000' . // 2 bytes; disk number start
+ '0000' . // 2 bytes; internal file attributes
+ '20000000' . // 4 bytes; external file attributes
+ '34120000' . // 4 bytes; relative offset of local header
+ '746573742e706e67' . // n bytes; file name
+ '736f6d6520636f6e74656e74' . // m bytes; extra field
+ '736f6d6520636f6d6d656e74' // o bytes; file comment
+ );
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/DataDescriptorTest.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/DataDescriptorTest.php
new file mode 100644
index 0000000..cc886c7
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/DataDescriptorTest.php
@@ -0,0 +1,26 @@
+assertSame(
+ bin2hex(DataDescriptor::generate(
+ crc32UncompressedData: 0x11111111,
+ compressedSize: 0x77777777,
+ uncompressedSize: 0x99999999,
+ )),
+ '504b0708' . // 4 bytes; Optional data descriptor signature = 0x08074b50
+ '11111111' . // 4 bytes; CRC-32 of uncompressed data
+ '77777777' . // 4 bytes; Compressed size
+ '99999999' // 4 bytes; Uncompressed size
+ );
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/EndOfCentralDirectoryTest.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/EndOfCentralDirectoryTest.php
new file mode 100644
index 0000000..be0a907
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/EndOfCentralDirectoryTest.php
@@ -0,0 +1,35 @@
+assertSame(
+ bin2hex(EndOfCentralDirectory::generate(
+ numberOfThisDisk: 0x00,
+ numberOfTheDiskWithCentralDirectoryStart: 0x00,
+ numberOfCentralDirectoryEntriesOnThisDisk: 0x10,
+ numberOfCentralDirectoryEntries: 0x10,
+ sizeOfCentralDirectory: 0x22,
+ centralDirectoryStartOffsetOnDisk: 0x33,
+ zipFileComment: 'foo',
+ )),
+ '504b0506' . // 4 bytes; end of central dir signature 0x06054b50
+ '0000' . // 2 bytes; number of this disk
+ '0000' . // 2 bytes; number of the disk with the start of the central directory
+ '1000' . // 2 bytes; total number of entries in the central directory on this disk
+ '1000' . // 2 bytes; total number of entries in the central directory
+ '22000000' . // 4 bytes; size of the central directory
+ '33000000' . // 4 bytes; offset of start of central directory with respect to the starting disk number
+ '0300' . // 2 bytes; .ZIP file comment length
+ bin2hex('foo')
+ );
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/EndlessCycleStream.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/EndlessCycleStream.php
new file mode 100644
index 0000000..d9e7df1
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/EndlessCycleStream.php
@@ -0,0 +1,104 @@
+detach();
+ }
+
+ /**
+ * @return null
+ */
+ public function detach()
+ {
+ return;
+ }
+
+ public function getSize(): ?int
+ {
+ return null;
+ }
+
+ public function tell(): int
+ {
+ return $this->offset;
+ }
+
+ public function eof(): bool
+ {
+ return false;
+ }
+
+ public function isSeekable(): bool
+ {
+ return true;
+ }
+
+ public function seek(int $offset, int $whence = SEEK_SET): void
+ {
+ switch ($whence) {
+ case SEEK_SET:
+ $this->offset = $offset;
+ break;
+ case SEEK_CUR:
+ $this->offset += $offset;
+ break;
+ case SEEK_END:
+ throw new RuntimeException('Infinite Stream!');
+ break;
+ }
+ }
+
+ public function rewind(): void
+ {
+ $this->seek(0);
+ }
+
+ public function isWritable(): bool
+ {
+ return false;
+ }
+
+ public function write(string $string): int
+ {
+ throw new RuntimeException('Not writeable');
+ }
+
+ public function isReadable(): bool
+ {
+ return true;
+ }
+
+ public function read(int $length): string
+ {
+ $this->offset += $length;
+ return substr(str_repeat($this->toRepeat, (int) ceil($length / strlen($this->toRepeat))), 0, $length);
+ }
+
+ public function getContents(): string
+ {
+ throw new RuntimeException('Infinite Stream!');
+ }
+
+ public function getMetadata(?string $key = null): array|null
+ {
+ return $key !== null ? null : [];
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/FaultInjectionResource.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/FaultInjectionResource.php
new file mode 100644
index 0000000..3d4440e
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/FaultInjectionResource.php
@@ -0,0 +1,141 @@
+context);
+
+ if (!isset($options[self::NAME]['injectFaults'])) {
+ return false;
+ }
+
+ $this->mode = $mode;
+ $this->injectFaults = $options[self::NAME]['injectFaults'];
+
+ if ($this->shouldFail(__FUNCTION__)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function stream_write(string $data)
+ {
+ if ($this->shouldFail(__FUNCTION__)) {
+ return false;
+ }
+ return true;
+ }
+
+ public function stream_eof()
+ {
+ return true;
+ }
+
+ public function stream_seek(int $offset, int $whence): bool
+ {
+ if ($this->shouldFail(__FUNCTION__)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function stream_tell(): int
+ {
+ if ($this->shouldFail(__FUNCTION__)) {
+ return false;
+ }
+
+ return 0;
+ }
+
+ public static function register(): void
+ {
+ if (!in_array(self::NAME, stream_get_wrappers(), true)) {
+ stream_wrapper_register(self::NAME, __CLASS__);
+ }
+ }
+
+ public function stream_stat(): array
+ {
+ static $modeMap = [
+ 'r' => 33060,
+ 'rb' => 33060,
+ 'r+' => 33206,
+ 'w' => 33188,
+ 'wb' => 33188,
+ ];
+
+ return [
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => $modeMap[$this->mode],
+ 'nlink' => 0,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => 0,
+ 'atime' => 0,
+ 'mtime' => 0,
+ 'ctime' => 0,
+ 'blksize' => 0,
+ 'blocks' => 0,
+ ];
+ }
+
+ public function url_stat(string $path, int $flags): array
+ {
+ return [
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => 0,
+ 'nlink' => 0,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => 0,
+ 'atime' => 0,
+ 'mtime' => 0,
+ 'ctime' => 0,
+ 'blksize' => 0,
+ 'blocks' => 0,
+ ];
+ }
+
+ private static function createStreamContext(array $injectFaults)
+ {
+ return stream_context_create([
+ self::NAME => ['injectFaults' => $injectFaults],
+ ]);
+ }
+
+ private function shouldFail(string $function): bool
+ {
+ return in_array($function, $this->injectFaults, true);
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/LocalFileHeaderTest.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/LocalFileHeaderTest.php
new file mode 100644
index 0000000..196dd0f
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/LocalFileHeaderTest.php
@@ -0,0 +1,47 @@
+assertSame(
+ bin2hex((string) $header),
+ '504b0304' . // 4 bytes; Local file header signature
+ '2d00' . // 2 bytes; Version needed to extract (minimum)
+ '2222' . // 2 bytes; General purpose bit flag
+ '0800' . // 2 bytes; Compression method; e.g. none = 0, DEFLATE = 8
+ '2008' . // 2 bytes; File last modification time
+ '2154' . // 2 bytes; File last modification date
+ '11111111' . // 4 bytes; CRC-32 of uncompressed data
+ '77777777' . // 4 bytes; Compressed size (or 0xffffffff for ZIP64)
+ '99999999' . // 4 bytes; Uncompressed size (or 0xffffffff for ZIP64)
+ '0800' . // 2 bytes; File name length (n)
+ '0c00' . // 2 bytes; Extra field length (m)
+ '746573742e706e67' . // n bytes; File name
+ '736f6d6520636f6e74656e74' // m bytes; Extra field
+ );
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/PackFieldTest.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/PackFieldTest.php
new file mode 100644
index 0000000..ecd66ba
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/PackFieldTest.php
@@ -0,0 +1,42 @@
+assertSame(
+ bin2hex(PackField::pack(new PackField(format: 'v', value: 0x1122))),
+ '2211',
+ );
+ }
+
+ public function testOverflow2(): void
+ {
+ $this->expectException(RuntimeException::class);
+
+ PackField::pack(new PackField(format: 'v', value: 0xFFFFF));
+ }
+
+ public function testOverflow4(): void
+ {
+ $this->expectException(RuntimeException::class);
+
+ PackField::pack(new PackField(format: 'V', value: 0xFFFFFFFFF));
+ }
+
+ public function testUnknownOperator(): void
+ {
+ $this->assertSame(
+ bin2hex(PackField::pack(new PackField(format: 'a', value: 0x1122))),
+ '34',
+ );
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/ResourceStream.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/ResourceStream.php
new file mode 100644
index 0000000..752a1a3
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/ResourceStream.php
@@ -0,0 +1,159 @@
+isSeekable()) {
+ $this->seek(0);
+ }
+ return (string) stream_get_contents($this->stream);
+ }
+
+ public function close(): void
+ {
+ $stream = $this->detach();
+ if ($stream) {
+ fclose($stream);
+ }
+ }
+
+ public function detach()
+ {
+ $result = $this->stream;
+ // According to the interface, the stream is left in an unusable state;
+ /** @psalm-suppress PossiblyNullPropertyAssignmentValue */
+ $this->stream = null;
+ return $result;
+ }
+
+ public function seek(int $offset, int $whence = SEEK_SET): void
+ {
+ if (!$this->isSeekable()) {
+ throw new RuntimeException();
+ }
+ if (fseek($this->stream, $offset, $whence) !== 0) {
+ // @codeCoverageIgnoreStart
+ throw new RuntimeException();
+ // @codeCoverageIgnoreEnd
+ }
+ }
+
+ public function isSeekable(): bool
+ {
+ return (bool) $this->getMetadata('seekable');
+ }
+
+ public function getMetadata(?string $key = null)
+ {
+ $metadata = stream_get_meta_data($this->stream);
+ return $key !== null ? @$metadata[$key] : $metadata;
+ }
+
+ public function getSize(): ?int
+ {
+ $stats = fstat($this->stream);
+ return $stats['size'];
+ }
+
+ public function tell(): int
+ {
+ $position = ftell($this->stream);
+ if ($position === false) {
+ // @codeCoverageIgnoreStart
+ throw new RuntimeException();
+ // @codeCoverageIgnoreEnd
+ }
+ return $position;
+ }
+
+ public function eof(): bool
+ {
+ return feof($this->stream);
+ }
+
+ public function rewind(): void
+ {
+ $this->seek(0);
+ }
+
+ public function write(string $string): int
+ {
+ if (!$this->isWritable()) {
+ throw new RuntimeException();
+ }
+ if (fwrite($this->stream, $string) === false) {
+ // @codeCoverageIgnoreStart
+ throw new RuntimeException();
+ // @codeCoverageIgnoreEnd
+ }
+ return strlen($string);
+ }
+
+ public function isWritable(): bool
+ {
+ $mode = $this->getMetadata('mode');
+ if (!is_string($mode)) {
+ // @codeCoverageIgnoreStart
+ throw new RuntimeException('Could not get stream mode from metadata!');
+ // @codeCoverageIgnoreEnd
+ }
+ return preg_match('/[waxc+]/', $mode) === 1;
+ }
+
+ public function read(int $length): string
+ {
+ if (!$this->isReadable()) {
+ throw new RuntimeException();
+ }
+ $result = fread($this->stream, $length);
+ if ($result === false) {
+ // @codeCoverageIgnoreStart
+ throw new RuntimeException();
+ // @codeCoverageIgnoreEnd
+ }
+ return $result;
+ }
+
+ public function isReadable(): bool
+ {
+ $mode = $this->getMetadata('mode');
+ if (!is_string($mode)) {
+ // @codeCoverageIgnoreStart
+ throw new RuntimeException('Could not get stream mode from metadata!');
+ // @codeCoverageIgnoreEnd
+ }
+ return preg_match('/[r+]/', $mode) === 1;
+ }
+
+ public function getContents(): string
+ {
+ if (!$this->isReadable()) {
+ throw new RuntimeException();
+ }
+ $result = stream_get_contents($this->stream);
+ if ($result === false) {
+ // @codeCoverageIgnoreStart
+ throw new RuntimeException();
+ // @codeCoverageIgnoreEnd
+ }
+ return $result;
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/Tempfile.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/Tempfile.php
new file mode 100644
index 0000000..7ef9c61
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/Tempfile.php
@@ -0,0 +1,42 @@
+getTmpFileStream();
+
+ $this->tempfile = $tempfile;
+ $this->tempfileStream = $tempfileStream;
+ }
+
+ protected function tearDown(): void
+ {
+ unlink($this->tempfile);
+ if (is_resource($this->tempfileStream)) {
+ fclose($this->tempfileStream);
+ }
+
+ $this->tempfile = null;
+ $this->tempfileStream = null;
+ }
+
+ protected function getTmpFileStream(): array
+ {
+ $tmp = tempnam(sys_get_temp_dir(), 'zipstreamtest');
+ $stream = fopen($tmp, 'wb+');
+
+ return [$tmp, $stream];
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/TimeTest.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/TimeTest.php
new file mode 100644
index 0000000..61cfe03
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/TimeTest.php
@@ -0,0 +1,44 @@
+assertSame(
+ Time::dateTimeToDosTime(new DateTimeImmutable('2014-11-17T17:46:08Z')),
+ 1165069764
+ );
+
+ // January 1 1980 - DOS Epoch.
+ $this->assertSame(
+ Time::dateTimeToDosTime(new DateTimeImmutable('1980-01-01T00:00:00+00:00')),
+ 2162688
+ );
+
+ // Local timezone different than UTC.
+ $prevLocalTimezone = date_default_timezone_get();
+ date_default_timezone_set('Europe/Berlin');
+ $this->assertSame(
+ Time::dateTimeToDosTime(new DateTimeImmutable('1980-01-01T00:00:00+00:00')),
+ 2162688
+ );
+ date_default_timezone_set($prevLocalTimezone);
+ }
+
+ public function testTooEarlyDateToDosTime(): void
+ {
+ $this->expectException(DosTimeOverflowException::class);
+
+ // January 1 1980 is the minimum DOS Epoch.
+ Time::dateTimeToDosTime(new DateTimeImmutable('1970-01-01T00:00:00+00:00'));
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/Util.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/Util.php
new file mode 100644
index 0000000..86592b4
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/Util.php
@@ -0,0 +1,127 @@
+cmdExists('hexdump')) {
+ return '';
+ }
+
+ $output = [];
+
+ if (!exec("hexdump -C \"$path\" | head -n 50", $output)) {
+ return '';
+ }
+
+ return "\nHexdump:\n" . implode("\n", $output);
+ }
+
+ protected function validateAndExtractZip(string $zipPath): string
+ {
+ $tmpDir = $this->getTmpDir();
+
+ $zipArchive = new ZipArchive();
+ $result = $zipArchive->open($zipPath);
+
+ if ($result !== true) {
+ $codeName = $this->zipArchiveOpenErrorCodeName($result);
+ $debugInformation = $this->dumpZipContents($zipPath);
+
+ $this->fail("Failed to open {$zipPath}. Code: $result ($codeName)$debugInformation");
+
+ return $tmpDir;
+ }
+
+ $this->assertSame(0, $zipArchive->status);
+ $this->assertSame(0, $zipArchive->statusSys);
+
+ $zipArchive->extractTo($tmpDir);
+ $zipArchive->close();
+
+ return $tmpDir;
+ }
+
+ protected function zipArchiveOpenErrorCodeName(int $code): string
+ {
+ switch ($code) {
+ case ZipArchive::ER_EXISTS: return 'ER_EXISTS';
+ case ZipArchive::ER_INCONS: return 'ER_INCONS';
+ case ZipArchive::ER_INVAL: return 'ER_INVAL';
+ case ZipArchive::ER_MEMORY: return 'ER_MEMORY';
+ case ZipArchive::ER_NOENT: return 'ER_NOENT';
+ case ZipArchive::ER_NOZIP: return 'ER_NOZIP';
+ case ZipArchive::ER_OPEN: return 'ER_OPEN';
+ case ZipArchive::ER_READ: return 'ER_READ';
+ case ZipArchive::ER_SEEK: return 'ER_SEEK';
+ default: return 'unknown';
+ }
+ }
+
+ protected function getTmpDir(): string
+ {
+ $tmp = tempnam(sys_get_temp_dir(), 'zipstreamtest');
+ unlink($tmp);
+ mkdir($tmp) or $this->fail('Failed to make directory');
+
+ return $tmp;
+ }
+
+ /**
+ * @return string[]
+ */
+ protected function getRecursiveFileList(string $path, bool $includeDirectories = false): array
+ {
+ $data = [];
+ $path = (string) realpath($path);
+ $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path));
+
+ $pathLen = strlen($path);
+ foreach ($files as $file) {
+ $filePath = $file->getRealPath();
+
+ if (is_dir($filePath) && !$includeDirectories) {
+ continue;
+ }
+
+ $data[] = substr($filePath, $pathLen + 1);
+ }
+
+ sort($data);
+
+ return $data;
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/Zip64/DataDescriptorTest.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/Zip64/DataDescriptorTest.php
new file mode 100644
index 0000000..49fb2cc
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/Zip64/DataDescriptorTest.php
@@ -0,0 +1,28 @@
+assertSame(
+ bin2hex($descriptor),
+ '504b0708' . // 4 bytes; Optional data descriptor signature = 0x08074b50
+ '11111111' . // 4 bytes; CRC-32 of uncompressed data
+ '6666666677777777' . // 8 bytes; Compressed size
+ '8888888899999999' // 8 bytes; Uncompressed size
+ );
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryLocatorTest.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryLocatorTest.php
new file mode 100644
index 0000000..271a298
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryLocatorTest.php
@@ -0,0 +1,28 @@
+assertSame(
+ bin2hex($descriptor),
+ '504b0607' . // 4 bytes; zip64 end of central dir locator signature - 0x07064b50
+ '11111111' . // 4 bytes; number of the disk with the start of the zip64 end of central directory
+ '3333333322222222' . // 28 bytes; relative offset of the zip64 end of central directory record
+ '44444444' // 4 bytes;total number of disks
+ );
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryTest.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryTest.php
new file mode 100644
index 0000000..b86fb17
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryTest.php
@@ -0,0 +1,41 @@
+assertSame(
+ bin2hex($descriptor),
+ '504b0606' . // 4 bytes;zip64 end of central dir signature - 0x06064b50
+ '2f00000000000000' . // 8 bytes; size of zip64 end of central directory record
+ '3333' . // 2 bytes; version made by
+ '4444' . // 2 bytes; version needed to extract
+ '55555555' . // 4 bytes; number of this disk
+ '66666666' . // 4 bytes; number of the disk with the start of the central directory
+ '8888888877777777' . // 8 bytes; total number of entries in the central directory on this disk
+ 'aaaaaaaa99999999' . // 8 bytes; total number of entries in the central directory
+ 'ccccccccbbbbbbbb' . // 8 bytes; size of the central directory
+ 'eeeeeeeedddddddd' . // 8 bytes; offset of start of central directory with respect to the starting disk number
+ bin2hex('foo')
+ );
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/Zip64/ExtendedInformationExtraFieldTest.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/Zip64/ExtendedInformationExtraFieldTest.php
new file mode 100644
index 0000000..904783d
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/Zip64/ExtendedInformationExtraFieldTest.php
@@ -0,0 +1,42 @@
+assertSame(
+ bin2hex($extraField),
+ '0100' . // 2 bytes; Tag for this "extra" block type
+ '1c00' . // 2 bytes; Size of this "extra" block
+ '6666666677777777' . // 8 bytes; Original uncompressed file size
+ '8888888899999999' . // 8 bytes; Size of compressed data
+ '1111111122222222' . // 8 bytes; Offset of local header record
+ '33333333' // 4 bytes; Number of the disk on which this file starts
+ );
+ }
+
+ public function testSerializesEmptyCorrectly(): void
+ {
+ $extraField = ExtendedInformationExtraField::generate();
+
+ $this->assertSame(
+ bin2hex($extraField),
+ '0100' . // 2 bytes; Tag for this "extra" block type
+ '0000' // 2 bytes; Size of this "extra" block
+ );
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/ZipStreamTest.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/ZipStreamTest.php
new file mode 100644
index 0000000..9b10ba6
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/ZipStreamTest.php
@@ -0,0 +1,1195 @@
+tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ $zip->addFile('sample.txt', 'Sample String Data');
+ $zip->addFile('test/sample.txt', 'More Simple Sample Data');
+
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+ $this->assertSame(['sample.txt', 'test' . DIRECTORY_SEPARATOR . 'sample.txt'], $files);
+
+ $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data');
+ $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data');
+ }
+
+ public function testAddFileUtf8NameComment(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ $name = 'árvíztűrő tükörfúrógép.txt';
+ $content = 'Sample String Data';
+ $comment =
+ 'Filename has every special characters ' .
+ 'from Hungarian language in lowercase. ' .
+ 'In uppercase: ÁÍŰŐÜÖÚÓÉ';
+
+ $zip->addFile(fileName: $name, data: $content, comment: $comment);
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+ $this->assertSame([$name], $files);
+ $this->assertStringEqualsFile($tmpDir . '/' . $name, $content);
+
+ $zipArchive = new ZipArchive();
+ $zipArchive->open($this->tempfile);
+ $this->assertSame($comment, $zipArchive->getCommentName($name));
+ }
+
+ public function testAddFileUtf8NameNonUtfComment(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ $name = 'á.txt';
+ $content = 'any';
+ $comment = mb_convert_encoding('á', 'ISO-8859-2', 'UTF-8');
+
+ // @see https://libzip.org/documentation/zip_file_get_comment.html
+ //
+ // mb_convert_encoding hasn't CP437.
+ // nearly CP850 (DOS-Latin-1)
+ $guessComment = mb_convert_encoding($comment, 'UTF-8', 'CP850');
+
+ $zip->addFile(fileName: $name, data: $content, comment: $comment);
+
+ $zip->finish();
+
+ $zipArch = new ZipArchive();
+ $zipArch->open($this->tempfile);
+ $this->assertSame($guessComment, $zipArch->getCommentName($name));
+ $this->assertSame($comment, $zipArch->getCommentName($name, ZipArchive::FL_ENC_RAW));
+ }
+
+ public function testAddFileWithStorageMethod(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ $zip->addFile(fileName: 'sample.txt', data: 'Sample String Data', compressionMethod: CompressionMethod::STORE);
+ $zip->addFile(fileName: 'test/sample.txt', data: 'More Simple Sample Data');
+ $zip->finish();
+
+ $zipArchive = new ZipArchive();
+ $zipArchive->open($this->tempfile);
+
+ $sample1 = $zipArchive->statName('sample.txt');
+ $sample12 = $zipArchive->statName('test/sample.txt');
+ $this->assertSame($sample1['comp_method'], CompressionMethod::STORE->value);
+ $this->assertSame($sample12['comp_method'], CompressionMethod::DEFLATE->value);
+
+ $zipArchive->close();
+ }
+
+ public function testAddFileFromPath(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ [$tmpExample, $streamExample] = $this->getTmpFileStream();
+ fwrite($streamExample, 'Sample String Data');
+ fclose($streamExample);
+ $zip->addFileFromPath(fileName: 'sample.txt', path: $tmpExample);
+
+ [$tmpExample, $streamExample] = $this->getTmpFileStream();
+ fwrite($streamExample, 'More Simple Sample Data');
+ fclose($streamExample);
+ $zip->addFileFromPath(fileName: 'test/sample.txt', path: $tmpExample);
+
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+ $this->assertSame(['sample.txt', 'test' . DIRECTORY_SEPARATOR . 'sample.txt'], $files);
+
+ $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data');
+ $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data');
+
+ unlink($tmpExample);
+ }
+
+ public function testAddFileFromPathFileNotFoundException(): void
+ {
+ $this->expectException(FileNotFoundException::class);
+
+ // Get ZipStream Object
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ // Trigger error by adding a file which doesn't exist
+ $zip->addFileFromPath(fileName: 'foobar.php', path: '/foo/bar/foobar.php');
+ }
+
+ public function testAddFileFromPathFileNotReadableException(): void
+ {
+ $this->expectException(FileNotReadableException::class);
+
+ // create new virtual filesystem
+ $root = vfsStream::setup('vfs');
+ // create a virtual file with no permissions
+ $file = vfsStream::newFile('foo.txt', 0)->at($root)->setContent('bar');
+
+ // Get ZipStream Object
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ $zip->addFileFromPath('foo.txt', $file->url());
+ }
+
+ public function testAddFileFromPathWithStorageMethod(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ [$tmpExample, $streamExample] = $this->getTmpFileStream();
+ fwrite($streamExample, 'Sample String Data');
+ fclose($streamExample);
+ $zip->addFileFromPath(fileName: 'sample.txt', path: $tmpExample, compressionMethod: CompressionMethod::STORE);
+
+ [$tmpExample, $streamExample] = $this->getTmpFileStream();
+ fwrite($streamExample, 'More Simple Sample Data');
+ fclose($streamExample);
+ $zip->addFileFromPath('test/sample.txt', $tmpExample);
+
+ $zip->finish();
+
+ $zipArchive = new ZipArchive();
+ $zipArchive->open($this->tempfile);
+
+ $sample1 = $zipArchive->statName('sample.txt');
+ $this->assertSame(CompressionMethod::STORE->value, $sample1['comp_method']);
+
+ $sample2 = $zipArchive->statName('test/sample.txt');
+ $this->assertSame(CompressionMethod::DEFLATE->value, $sample2['comp_method']);
+
+ $zipArchive->close();
+ }
+
+ public function testAddLargeFileFromPath(): void
+ {
+ foreach ([CompressionMethod::DEFLATE, CompressionMethod::STORE] as $compressionMethod) {
+ foreach ([false, true] as $zeroHeader) {
+ foreach ([false, true] as $zip64) {
+ if ($zeroHeader && $compressionMethod === CompressionMethod::DEFLATE) {
+ continue;
+ }
+ $this->addLargeFileFileFromPath(
+ compressionMethod: $compressionMethod,
+ zeroHeader: $zeroHeader,
+ zip64: $zip64
+ );
+ }
+ }
+ }
+ }
+
+ public function testAddFileFromStream(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ // In this test we can't use temporary stream to feed data
+ // because zlib.deflate filter gives empty string before PHP 7
+ // it works fine with file stream
+ $streamExample = fopen(__FILE__, 'rb');
+ $zip->addFileFromStream('sample.txt', $streamExample);
+ fclose($streamExample);
+
+ $streamExample2 = fopen('php://temp', 'wb+');
+ fwrite($streamExample2, 'More Simple Sample Data');
+ rewind($streamExample2); // move the pointer back to the beginning of file.
+ $zip->addFileFromStream('test/sample.txt', $streamExample2); //, $fileOptions);
+ fclose($streamExample2);
+
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+ $this->assertSame(['sample.txt', 'test' . DIRECTORY_SEPARATOR . 'sample.txt'], $files);
+
+ $this->assertStringEqualsFile(__FILE__, file_get_contents($tmpDir . '/sample.txt'));
+ $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data');
+ }
+
+ public function testAddFileFromStreamUnreadableInput(): void
+ {
+ $this->expectException(StreamNotReadableException::class);
+
+ [$tmpInput] = $this->getTmpFileStream();
+
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ $streamUnreadable = fopen($tmpInput, 'w');
+
+ $zip->addFileFromStream('sample.json', $streamUnreadable);
+ }
+
+ public function testAddFileFromStreamBrokenOutputWrite(): void
+ {
+ $this->expectException(ResourceActionException::class);
+
+ $outputStream = FaultInjectionResource::getResource(['stream_write']);
+
+ $zip = new ZipStream(
+ outputStream: $outputStream,
+ sendHttpHeaders: false,
+ );
+
+ $zip->addFile('sample.txt', 'foobar');
+ }
+
+ public function testAddFileFromStreamBrokenInputRewind(): void
+ {
+ $this->expectException(ResourceActionException::class);
+
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ defaultEnableZeroHeader: false,
+ );
+
+ $fileStream = FaultInjectionResource::getResource(['stream_seek']);
+
+ $zip->addFileFromStream('sample.txt', $fileStream, maxSize: 0);
+ }
+
+ public function testAddFileFromStreamUnseekableInputWithoutZeroHeader(): void
+ {
+ $this->expectException(StreamNotSeekableException::class);
+
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ defaultEnableZeroHeader: false,
+ );
+
+ if (file_exists('/dev/null')) {
+ $streamUnseekable = fopen('/dev/null', 'w+');
+ } elseif (file_exists('NUL')) {
+ $streamUnseekable = fopen('NUL', 'w+');
+ } else {
+ $this->markTestSkipped('Needs file /dev/null');
+ }
+
+ $zip->addFileFromStream('sample.txt', $streamUnseekable, maxSize: 2);
+ }
+
+ public function testAddFileFromStreamUnseekableInputWithZeroHeader(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ defaultEnableZeroHeader: true,
+ defaultCompressionMethod: CompressionMethod::STORE,
+ );
+
+ $streamUnseekable = StreamWrapper::getResource(new class ('test') extends EndlessCycleStream {
+ public function isSeekable(): bool
+ {
+ return false;
+ }
+
+ public function seek(int $offset, int $whence = SEEK_SET): void
+ {
+ throw new RuntimeException('Not seekable');
+ }
+ });
+
+ $zip->addFileFromStream('sample.txt', $streamUnseekable, maxSize: 7);
+
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+ $this->assertSame(['sample.txt'], $files);
+
+ $this->assertSame(filesize($tmpDir . '/sample.txt'), 7);
+ }
+
+ public function testAddFileFromStreamWithStorageMethod(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ $streamExample = fopen('php://temp', 'wb+');
+ fwrite($streamExample, 'Sample String Data');
+ rewind($streamExample); // move the pointer back to the beginning of file.
+ $zip->addFileFromStream('sample.txt', $streamExample, compressionMethod: CompressionMethod::STORE);
+ fclose($streamExample);
+
+ $streamExample2 = fopen('php://temp', 'bw+');
+ fwrite($streamExample2, 'More Simple Sample Data');
+ rewind($streamExample2); // move the pointer back to the beginning of file.
+ $zip->addFileFromStream('test/sample.txt', $streamExample2, compressionMethod: CompressionMethod::DEFLATE);
+ fclose($streamExample2);
+
+ $zip->finish();
+
+ $zipArchive = new ZipArchive();
+ $zipArchive->open($this->tempfile);
+
+ $sample1 = $zipArchive->statName('sample.txt');
+ $this->assertSame(CompressionMethod::STORE->value, $sample1['comp_method']);
+
+ $sample2 = $zipArchive->statName('test/sample.txt');
+ $this->assertSame(CompressionMethod::DEFLATE->value, $sample2['comp_method']);
+
+ $zipArchive->close();
+ }
+
+ public function testAddFileFromPsr7Stream(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ $body = 'Sample String Data';
+ $response = new Response(200, [], $body);
+
+ $zip->addFileFromPsr7Stream('sample.json', $response->getBody());
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+ $this->assertSame(['sample.json'], $files);
+ $this->assertStringEqualsFile($tmpDir . '/sample.json', $body);
+ }
+
+ /**
+ * @group slow
+ */
+ public function testAddLargeFileFromPsr7Stream(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ enableZip64: true,
+ );
+
+ $zip->addFileFromPsr7Stream(
+ fileName: 'sample.json',
+ stream: new EndlessCycleStream('0'),
+ maxSize: 0x100000000,
+ compressionMethod: CompressionMethod::STORE,
+ lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'),
+ );
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+ $this->assertSame(['sample.json'], $files);
+ $this->assertFileIsReadable($tmpDir . '/sample.json');
+ $this->assertStringStartsWith('000000', file_get_contents(filename: $tmpDir . '/sample.json', length: 20));
+ }
+
+ public function testContinueFinishedZip(): void
+ {
+ $this->expectException(RuntimeException::class);
+
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+ $zip->finish();
+
+ $zip->addFile('sample.txt', '1234');
+ }
+
+ /**
+ * @group slow
+ */
+ public function testManyFilesWithoutZip64(): void
+ {
+ $this->expectException(OverflowException::class);
+
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ enableZip64: false,
+ );
+
+ for ($i = 0; $i <= 0xFFFF; $i++) {
+ $zip->addFile('sample' . $i, '');
+ }
+
+ $zip->finish();
+ }
+
+ /**
+ * @group slow
+ */
+ public function testManyFilesWithZip64(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ enableZip64: true,
+ );
+
+ for ($i = 0; $i <= 0xFFFF; $i++) {
+ $zip->addFile('sample' . $i, '');
+ }
+
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+
+ $this->assertSame(count($files), 0x10000);
+ }
+
+ /**
+ * @group slow
+ */
+ public function testLongZipWithout64(): void
+ {
+ $this->expectException(OverflowException::class);
+
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ enableZip64: false,
+ defaultCompressionMethod: CompressionMethod::STORE,
+ );
+
+ for ($i = 0; $i < 4; $i++) {
+ $zip->addFileFromPsr7Stream(
+ fileName: 'sample' . $i,
+ stream: new EndlessCycleStream('0'),
+ maxSize: 0xFFFFFFFF,
+ compressionMethod: CompressionMethod::STORE,
+ lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'),
+ );
+ }
+ }
+
+ /**
+ * @group slow
+ */
+ public function testLongZipWith64(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ enableZip64: true,
+ defaultCompressionMethod: CompressionMethod::STORE,
+ );
+
+ for ($i = 0; $i < 4; $i++) {
+ $zip->addFileFromPsr7Stream(
+ fileName: 'sample' . $i,
+ stream: new EndlessCycleStream('0'),
+ maxSize: 0x5FFFFFFF,
+ compressionMethod: CompressionMethod::STORE,
+ lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'),
+ );
+ }
+
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+ $this->assertSame(['sample0', 'sample1', 'sample2', 'sample3'], $files);
+ }
+
+ /**
+ * @group slow
+ */
+ public function testAddLargeFileWithoutZip64WithZeroHeader(): void
+ {
+ $this->expectException(OverflowException::class);
+
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ enableZip64: false,
+ defaultEnableZeroHeader: true,
+ );
+
+ $zip->addFileFromPsr7Stream(
+ fileName: 'sample.json',
+ stream: new EndlessCycleStream('0'),
+ maxSize: 0x100000000,
+ compressionMethod: CompressionMethod::STORE,
+ lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'),
+ );
+ }
+
+ /**
+ * @group slow
+ */
+ public function testAddsZip64HeaderWhenNeeded(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ enableZip64: true,
+ defaultEnableZeroHeader: false,
+ );
+
+ $zip->addFileFromPsr7Stream(
+ fileName: 'sample.json',
+ stream: new EndlessCycleStream('0'),
+ maxSize: 0x100000000,
+ compressionMethod: CompressionMethod::STORE,
+ lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'),
+ );
+
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+ $files = $this->getRecursiveFileList($tmpDir);
+
+ $this->assertSame(['sample.json'], $files);
+ $this->assertFileContains($this->tempfile, PackField::pack(
+ new PackField(format: 'V', value: 0x06064b50)
+ ));
+ }
+
+ /**
+ * @group slow
+ */
+ public function testDoesNotAddZip64HeaderWhenNotNeeded(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ enableZip64: true,
+ defaultEnableZeroHeader: false,
+ );
+
+ $zip->addFileFromPsr7Stream(
+ fileName: 'sample.json',
+ stream: new EndlessCycleStream('0'),
+ maxSize: 0x10,
+ compressionMethod: CompressionMethod::STORE,
+ lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'),
+ );
+
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+ $files = $this->getRecursiveFileList($tmpDir);
+
+ $this->assertSame(['sample.json'], $files);
+ $this->assertFileDoesNotContain($this->tempfile, PackField::pack(
+ new PackField(format: 'V', value: 0x06064b50)
+ ));
+ }
+
+ /**
+ * @group slow
+ */
+ public function testAddLargeFileWithoutZip64WithoutZeroHeader(): void
+ {
+ $this->expectException(OverflowException::class);
+
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ enableZip64: false,
+ defaultEnableZeroHeader: false,
+ );
+
+ $zip->addFileFromPsr7Stream(
+ fileName: 'sample.json',
+ stream: new EndlessCycleStream('0'),
+ maxSize: 0x100000000,
+ compressionMethod: CompressionMethod::STORE,
+ lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'),
+ );
+ }
+
+ public function testAddFileFromPsr7StreamWithOutputToPsr7Stream(): void
+ {
+ $psr7OutputStream = new ResourceStream($this->tempfileStream);
+
+ $zip = new ZipStream(
+ outputStream: $psr7OutputStream,
+ sendHttpHeaders: false,
+ );
+
+ $body = 'Sample String Data';
+ $response = new Response(200, [], $body);
+
+ $zip->addFileFromPsr7Stream(
+ fileName: 'sample.json',
+ stream: $response->getBody(),
+ compressionMethod: CompressionMethod::STORE,
+ );
+ $zip->finish();
+ $psr7OutputStream->close();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+ $files = $this->getRecursiveFileList($tmpDir);
+
+ $this->assertSame(['sample.json'], $files);
+ $this->assertStringEqualsFile($tmpDir . '/sample.json', $body);
+ }
+
+ public function testAddFileFromPsr7StreamWithFileSizeSet(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ $body = 'Sample String Data';
+ $fileSize = strlen($body);
+ // Add fake padding
+ $fakePadding = "\0\0\0\0\0\0";
+ $response = new Response(200, [], $body . $fakePadding);
+
+ $zip->addFileFromPsr7Stream(
+ fileName: 'sample.json',
+ stream: $response->getBody(),
+ compressionMethod: CompressionMethod::STORE,
+ maxSize: $fileSize
+ );
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+ $this->assertSame(['sample.json'], $files);
+ $this->assertStringEqualsFile($tmpDir . '/sample.json', $body);
+ }
+
+ public function testCreateArchiveHeaders(): void
+ {
+ $headers = [];
+
+ $httpHeaderCallback = function (string $header) use (&$headers) {
+ $headers[] = $header;
+ };
+
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: true,
+ outputName: 'example.zip',
+ httpHeaderCallback: $httpHeaderCallback,
+ );
+
+ $zip->addFile(
+ fileName: 'sample.json',
+ data: 'foo',
+ );
+ $zip->finish();
+
+ $this->assertContains('Content-Type: application/x-zip', $headers);
+ $this->assertContains("Content-Disposition: attachment; filename*=UTF-8''example.zip", $headers);
+ $this->assertContains('Pragma: public', $headers);
+ $this->assertContains('Cache-Control: public, must-revalidate', $headers);
+ $this->assertContains('Content-Transfer-Encoding: binary', $headers);
+ }
+
+ public function testCreateArchiveWithFlushOptionSet(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ flushOutput: true,
+ sendHttpHeaders: false,
+ );
+
+ $zip->addFile('sample.txt', 'Sample String Data');
+ $zip->addFile('test/sample.txt', 'More Simple Sample Data');
+
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+ $this->assertSame(['sample.txt', 'test' . DIRECTORY_SEPARATOR . 'sample.txt'], $files);
+
+ $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data');
+ $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data');
+ }
+
+ public function testCreateArchiveWithOutputBufferingOffAndFlushOptionSet(): void
+ {
+ // WORKAROUND (1/2): remove phpunit's output buffer in order to run test without any buffering
+ ob_end_flush();
+ $this->assertSame(0, ob_get_level());
+
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ flushOutput: true,
+ sendHttpHeaders: false,
+ );
+
+ $zip->addFile('sample.txt', 'Sample String Data');
+
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+ $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data');
+
+ // WORKAROUND (2/2): add back output buffering so that PHPUnit doesn't complain that it is missing
+ ob_start();
+ }
+
+ public function testAddEmptyDirectory(): void
+ {
+ $zip = new ZipStream(
+ outputStream: $this->tempfileStream,
+ sendHttpHeaders: false,
+ );
+
+ $zip->addDirectory('foo');
+
+ $zip->finish();
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir, includeDirectories: true);
+
+ $this->assertContains('foo', $files);
+
+ $this->assertFileExists($tmpDir . DIRECTORY_SEPARATOR . 'foo');
+ $this->assertDirectoryExists($tmpDir . DIRECTORY_SEPARATOR . 'foo');
+ }
+
+ public function testAddFileSimulate(): void
+ {
+ $create = function (OperationMode $operationMode): int {
+ $zip = new ZipStream(
+ sendHttpHeaders: false,
+ operationMode: $operationMode,
+ defaultEnableZeroHeader: true,
+ outputStream: $this->tempfileStream,
+ );
+
+ $zip->addFile('sample.txt', 'Sample String Data');
+ $zip->addFile('test/sample.txt', 'More Simple Sample Data');
+
+ return $zip->finish();
+ };
+
+
+ $sizeExpected = $create(OperationMode::NORMAL);
+ $sizeActual = $create(OperationMode::SIMULATE_LAX);
+
+ $this->assertEquals($sizeExpected, $sizeActual);
+ }
+
+ public function testAddFileSimulateWithMaxSize(): void
+ {
+ $create = function (OperationMode $operationMode): int {
+ $zip = new ZipStream(
+ sendHttpHeaders: false,
+ operationMode: $operationMode,
+ defaultCompressionMethod: CompressionMethod::STORE,
+ defaultEnableZeroHeader: true,
+ outputStream: $this->tempfileStream,
+ );
+
+ $zip->addFile('sample.txt', 'Sample String Data', maxSize: 0);
+
+ return $zip->finish();
+ };
+
+
+ $sizeExpected = $create(OperationMode::NORMAL);
+ $sizeActual = $create(OperationMode::SIMULATE_LAX);
+
+ $this->assertEquals($sizeExpected, $sizeActual);
+ }
+
+ public function testAddFileSimulateWithFstat(): void
+ {
+ $create = function (OperationMode $operationMode): int {
+ $zip = new ZipStream(
+ sendHttpHeaders: false,
+ operationMode: $operationMode,
+ defaultCompressionMethod: CompressionMethod::STORE,
+ defaultEnableZeroHeader: true,
+ outputStream: $this->tempfileStream,
+ );
+
+ $zip->addFile('sample.txt', 'Sample String Data');
+ $zip->addFile('test/sample.txt', 'More Simple Sample Data');
+
+ return $zip->finish();
+ };
+
+
+ $sizeExpected = $create(OperationMode::NORMAL);
+ $sizeActual = $create(OperationMode::SIMULATE_LAX);
+
+ $this->assertEquals($sizeExpected, $sizeActual);
+ }
+
+ public function testAddFileSimulateWithExactSizeZero(): void
+ {
+ $create = function (OperationMode $operationMode): int {
+ $zip = new ZipStream(
+ sendHttpHeaders: false,
+ operationMode: $operationMode,
+ defaultCompressionMethod: CompressionMethod::STORE,
+ defaultEnableZeroHeader: true,
+ outputStream: $this->tempfileStream,
+ );
+
+ $zip->addFile('sample.txt', 'Sample String Data', exactSize: 18);
+
+ return $zip->finish();
+ };
+
+
+ $sizeExpected = $create(OperationMode::NORMAL);
+ $sizeActual = $create(OperationMode::SIMULATE_LAX);
+
+ $this->assertEquals($sizeExpected, $sizeActual);
+ }
+
+ public function testAddFileSimulateWithExactSizeInitial(): void
+ {
+ $create = function (OperationMode $operationMode): int {
+ $zip = new ZipStream(
+ sendHttpHeaders: false,
+ operationMode: $operationMode,
+ defaultCompressionMethod: CompressionMethod::STORE,
+ defaultEnableZeroHeader: false,
+ outputStream: $this->tempfileStream,
+ );
+
+ $zip->addFile('sample.txt', 'Sample String Data', exactSize: 18);
+
+ return $zip->finish();
+ };
+
+ $sizeExpected = $create(OperationMode::NORMAL);
+ $sizeActual = $create(OperationMode::SIMULATE_LAX);
+
+ $this->assertEquals($sizeExpected, $sizeActual);
+ }
+
+ public function testAddFileSimulateWithZeroSizeInFstat(): void
+ {
+ $create = function (OperationMode $operationMode): int {
+ $zip = new ZipStream(
+ sendHttpHeaders: false,
+ operationMode: $operationMode,
+ defaultCompressionMethod: CompressionMethod::STORE,
+ defaultEnableZeroHeader: false,
+ outputStream: $this->tempfileStream,
+ );
+
+ $zip->addFileFromPsr7Stream('sample.txt', new class implements StreamInterface {
+ public $pos = 0;
+
+ public function __toString(): string
+ {
+ return 'test';
+ }
+
+ public function close(): void {}
+
+ public function detach() {}
+
+ public function getSize(): ?int
+ {
+ return null;
+ }
+
+ public function tell(): int
+ {
+ return $this->pos;
+ }
+
+ public function eof(): bool
+ {
+ return $this->pos >= 4;
+ }
+
+ public function isSeekable(): bool
+ {
+ return true;
+ }
+
+ public function seek(int $offset, int $whence = SEEK_SET): void
+ {
+ $this->pos = $offset;
+ }
+
+ public function rewind(): void
+ {
+ $this->pos = 0;
+ }
+
+ public function isWritable(): bool
+ {
+ return false;
+ }
+
+ public function write(string $string): int
+ {
+ return 0;
+ }
+
+ public function isReadable(): bool
+ {
+ return true;
+ }
+
+ public function read(int $length): string
+ {
+ $data = substr('test', $this->pos, $length);
+ $this->pos += strlen($data);
+ return $data;
+ }
+
+ public function getContents(): string
+ {
+ return $this->read(4);
+ }
+
+ public function getMetadata(?string $key = null)
+ {
+ return $key !== null ? null : [];
+ }
+ });
+
+ return $zip->finish();
+ };
+
+ $sizeExpected = $create(OperationMode::NORMAL);
+ $sizeActual = $create(OperationMode::SIMULATE_LAX);
+
+
+ $this->assertEquals($sizeExpected, $sizeActual);
+ }
+
+ public function testAddFileSimulateWithWrongExactSize(): void
+ {
+ $this->expectException(FileSizeIncorrectException::class);
+
+ $zip = new ZipStream(
+ sendHttpHeaders: false,
+ operationMode: OperationMode::SIMULATE_LAX,
+ );
+
+ $zip->addFile('sample.txt', 'Sample String Data', exactSize: 1000);
+ }
+
+ public function testAddFileSimulateStrictZero(): void
+ {
+ $this->expectException(SimulationFileUnknownException::class);
+
+ $zip = new ZipStream(
+ sendHttpHeaders: false,
+ operationMode: OperationMode::SIMULATE_STRICT,
+ defaultEnableZeroHeader: true
+ );
+
+ $zip->addFile('sample.txt', 'Sample String Data');
+ }
+
+ public function testAddFileSimulateStrictInitial(): void
+ {
+ $this->expectException(SimulationFileUnknownException::class);
+
+ $zip = new ZipStream(
+ sendHttpHeaders: false,
+ operationMode: OperationMode::SIMULATE_STRICT,
+ defaultEnableZeroHeader: false
+ );
+
+ $zip->addFile('sample.txt', 'Sample String Data');
+ }
+
+ public function testAddFileCallbackStrict(): void
+ {
+ $this->expectException(SimulationFileUnknownException::class);
+
+ $zip = new ZipStream(
+ sendHttpHeaders: false,
+ operationMode: OperationMode::SIMULATE_STRICT,
+ defaultEnableZeroHeader: false
+ );
+
+ $zip->addFileFromCallback('sample.txt', callback: function () {
+ return '';
+ });
+ }
+
+ public function testAddFileCallbackLax(): void
+ {
+ $zip = new ZipStream(
+ operationMode: OperationMode::SIMULATE_LAX,
+ defaultEnableZeroHeader: false,
+ sendHttpHeaders: false,
+ );
+
+ $zip->addFileFromCallback('sample.txt', callback: function () {
+ return 'Sample String Data';
+ });
+
+ $size = $zip->finish();
+
+ $this->assertEquals($size, 142);
+ }
+
+ public function testExecuteSimulation(): void
+ {
+ $zip = new ZipStream(
+ operationMode: OperationMode::SIMULATE_STRICT,
+ defaultCompressionMethod: CompressionMethod::STORE,
+ defaultEnableZeroHeader: false,
+ sendHttpHeaders: false,
+ outputStream: $this->tempfileStream,
+ );
+
+ $zip->addFileFromCallback(
+ 'sample.txt',
+ exactSize: 18,
+ callback: function () {
+ return 'Sample String Data';
+ }
+ );
+
+ $zip->addFileFromCallback(
+ '.gitkeep',
+ exactSize: 0,
+ callback: function () {
+ return '';
+ }
+ );
+
+ $size = $zip->finish();
+
+ $this->assertEquals(filesize($this->tempfile), 0);
+
+ $zip->executeSimulation();
+
+ clearstatcache();
+
+ $this->assertEquals(filesize($this->tempfile), $size);
+
+ $tmpDir = $this->validateAndExtractZip($this->tempfile);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+ $this->assertSame(['.gitkeep', 'sample.txt'], $files);
+ }
+
+ public function testExecuteSimulationBeforeFinish(): void
+ {
+ $this->expectException(RuntimeException::class);
+
+ $zip = new ZipStream(
+ operationMode: OperationMode::SIMULATE_LAX,
+ defaultEnableZeroHeader: false,
+ sendHttpHeaders: false,
+ outputStream: $this->tempfileStream,
+ );
+
+ $zip->executeSimulation();
+ }
+
+ private function addLargeFileFileFromPath(CompressionMethod $compressionMethod, $zeroHeader, $zip64): void
+ {
+ [$tmp, $stream] = $this->getTmpFileStream();
+
+ $zip = new ZipStream(
+ outputStream: $stream,
+ sendHttpHeaders: false,
+ defaultEnableZeroHeader: $zeroHeader,
+ enableZip64: $zip64,
+ );
+
+ [$tmpExample, $streamExample] = $this->getTmpFileStream();
+ for ($i = 0; $i <= 10000; $i++) {
+ fwrite($streamExample, sha1((string) $i));
+ if ($i % 100 === 0) {
+ fwrite($streamExample, "\n");
+ }
+ }
+ fclose($streamExample);
+ $shaExample = sha1_file($tmpExample);
+ $zip->addFileFromPath('sample.txt', $tmpExample);
+ unlink($tmpExample);
+
+ $zip->finish();
+ fclose($stream);
+
+ $tmpDir = $this->validateAndExtractZip($tmp);
+
+ $files = $this->getRecursiveFileList($tmpDir);
+ $this->assertSame(['sample.txt'], $files);
+
+ $this->assertSame(sha1_file($tmpDir . '/sample.txt'), $shaExample, "SHA-1 Mismatch Method: {$compressionMethod->value}");
+
+ unlink($tmp);
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/Zs/ExtendedInformationExtraFieldTest.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/Zs/ExtendedInformationExtraFieldTest.php
new file mode 100644
index 0000000..2b8dbed
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/Zs/ExtendedInformationExtraFieldTest.php
@@ -0,0 +1,22 @@
+assertSame(
+ bin2hex((string) $extraField),
+ '5356' . // 2 bytes; Tag for this "extra" block type
+ '0000' // 2 bytes; TODO: Document
+ );
+ }
+}
diff --git a/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/bootstrap.php b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/bootstrap.php
new file mode 100644
index 0000000..13c7a0e
--- /dev/null
+++ b/cms/lib/plugins/cms_api/v3/libraries/vendor/maennchen/zipstream-php/test/bootstrap.php
@@ -0,0 +1,7 @@
+