From 76012c0071f127966b3e30d5393f545bafe82fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Mon, 18 Sep 2023 14:53:11 +0200 Subject: [PATCH 01/42] feat: php 8.2 --- .drone.star | 4 +-- composer.json | 7 ++--- lib/private/AppFramework/Http/Request.php | 10 +++---- lib/private/Archive/ZIP.php | 8 +++--- lib/private/Cache/CappedMemoryCache.php | 8 +++--- lib/private/Session/Memory.php | 2 +- lib/private/Session/Session.php | 18 ++++++------- lib/private/legacy/image.php | 32 +++++++++++++---------- 8 files changed, 47 insertions(+), 42 deletions(-) diff --git a/.drone.star b/.drone.star index 34daff243d57..ed62428f3f1f 100644 --- a/.drone.star +++ b/.drone.star @@ -26,7 +26,7 @@ SONARSOURCE_SONAR_SCANNER_CLI = "sonarsource/sonar-scanner-cli:5" TOOLHIPPIE_CALENS = "toolhippie/calens:latest" WEBHIPPIE_REDIS = "webhippie/redis:latest" -DEFAULT_PHP_VERSION = "7.4" +DEFAULT_PHP_VERSION = "8.2" DEFAULT_NODEJS_VERSION = "14" # minio mc environment variables @@ -64,7 +64,7 @@ config = { "phpunit": { "mostDatabases": { "phpVersions": [ - DEFAULT_PHP_VERSION, + DEFAULT_PHP_VERSION ], # Gather coverage for all databases except Oracle "coverage": True, diff --git a/composer.json b/composer.json index 3371b53e4ec9..12f76fad6074 100644 --- a/composer.json +++ b/composer.json @@ -7,12 +7,13 @@ "optimize-autoloader": true, "classmap-authoritative": false, "platform": { - "php": "7.4" + "php": "8.2" }, "allow-plugins": { "bamarni/composer-bin-plugin": true, "dg/composer-cleaner": true - } + }, + "sort-packages": true }, "autoload" : { "psr-4": { @@ -44,7 +45,7 @@ "roave/security-advisories": "dev-latest" }, "require": { - "php": ">=7.4", + "php": ">=8.2", "ext-apcu": "*", "ext-ctype": "*", "ext-curl": "*", diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php index e9e0e6cabdba..72e2848fe3c2 100644 --- a/lib/private/AppFramework/Http/Request.php +++ b/lib/private/AppFramework/Http/Request.php @@ -168,7 +168,7 @@ public function setUrlParameters(array $parameters): void { * Countable method * @return int */ - public function count() { + public function count(): int { return \count(\array_keys($this->items['parameters'])); } @@ -192,28 +192,28 @@ public function count() { * @param string $offset The key to lookup * @return boolean */ - public function offsetExists($offset) { + public function offsetExists($offset): bool { return isset($this->items['parameters'][$offset]); } /** * @see offsetExists */ - public function offsetGet($offset) { + public function offsetGet($offset): mixed { return $this->items['parameters'][$offset] ?? null; } /** * @see offsetExists */ - public function offsetSet($offset, $value) { + public function offsetSet($offset, $value): void { throw new \RuntimeException('You cannot change the contents of the request object'); } /** * @see offsetExists */ - public function offsetUnset($offset) { + public function offsetUnset($offset): void { throw new \RuntimeException('You cannot change the contents of the request object'); } diff --git a/lib/private/Archive/ZIP.php b/lib/private/Archive/ZIP.php index 897d6f671221..ba7f6eda226f 100644 --- a/lib/private/Archive/ZIP.php +++ b/lib/private/Archive/ZIP.php @@ -30,9 +30,11 @@ namespace OC\Archive; +use ZipArchive; + class ZIP extends Archive { /** - * @var \ZipArchive zip + * @var ZipArchive zip */ private $zip=null; private $path; @@ -42,8 +44,8 @@ class ZIP extends Archive { */ public function __construct($source) { $this->path=$source; - $this->zip=new \ZipArchive(); - if ($this->zip->open($source, \ZipArchive::CREATE)) { + $this->zip=new ZipArchive(); + if ($this->zip->open($source, ZipArchive::CREATE)) { } else { \OCP\Util::writeLog('files_archive', 'Error while opening archive '.$source, \OCP\Util::WARN); } diff --git a/lib/private/Cache/CappedMemoryCache.php b/lib/private/Cache/CappedMemoryCache.php index 1e0b9eddef3c..38322c66bf06 100644 --- a/lib/private/Cache/CappedMemoryCache.php +++ b/lib/private/Cache/CappedMemoryCache.php @@ -60,19 +60,19 @@ public function clear($prefix = '') { return true; } - public function offsetExists($offset) { + public function offsetExists($offset): bool { return $this->hasKey($offset); } - public function &offsetGet($offset) { + public function &offsetGet($offset): mixed { return $this->cache[$offset]; } - public function offsetSet($offset, $value) { + public function offsetSet($offset, $value): void { $this->set($offset, $value); } - public function offsetUnset($offset) { + public function offsetUnset($offset): void { $this->remove($offset); } diff --git a/lib/private/Session/Memory.php b/lib/private/Session/Memory.php index c9349a750bc7..c187591685cb 100644 --- a/lib/private/Session/Memory.php +++ b/lib/private/Session/Memory.php @@ -37,7 +37,7 @@ * @package OC\Session */ class Memory extends Session { - protected $data; + protected array $data; public function __construct() { //no need to use $name since all data is already scoped to this instance diff --git a/lib/private/Session/Session.php b/lib/private/Session/Session.php index 8875dbb27077..fd1ef1397618 100644 --- a/lib/private/Session/Session.php +++ b/lib/private/Session/Session.php @@ -23,19 +23,17 @@ namespace OC\Session; +use ArrayAccess; use OCP\ISession; -abstract class Session implements \ArrayAccess, ISession { - /** - * @var bool - */ - protected $sessionClosed = false; +abstract class Session implements ArrayAccess, ISession { + protected bool $sessionClosed = false; /** * @param mixed $offset * @return bool */ - public function offsetExists($offset) { + public function offsetExists(mixed $offset): bool { return $this->exists($offset); } @@ -43,7 +41,7 @@ public function offsetExists($offset) { * @param mixed $offset * @return mixed */ - public function offsetGet($offset) { + public function offsetGet(mixed $offset): mixed { return $this->get($offset); } @@ -51,21 +49,21 @@ public function offsetGet($offset) { * @param mixed $offset * @param mixed $value */ - public function offsetSet($offset, $value) { + public function offsetSet(mixed $offset, mixed $value): void { $this->set($offset, $value); } /** * @param mixed $offset */ - public function offsetUnset($offset) { + public function offsetUnset(mixed $offset): void { $this->remove($offset); } /** * Close the session and release the lock */ - public function close() { + public function close(): void { $this->sessionClosed = true; } } diff --git a/lib/private/legacy/image.php b/lib/private/legacy/image.php index 8844710a3f56..f602fbc63686 100644 --- a/lib/private/legacy/image.php +++ b/lib/private/legacy/image.php @@ -42,8 +42,7 @@ * Class for basic image manipulation */ class OC_Image implements \OCP\IImage { - /** @var false|resource */ - protected $resource = false; // tmp resource. + protected ?\GdImage $resource = null; // tmp resource. /** @var int */ protected $imageType = IMAGETYPE_PNG; // Default to png if file type isn't evident. /** @var string */ @@ -511,21 +510,26 @@ public function fixOrientation() { * Loads an image from a local file, a base64 encoded string or a resource created by an imagecreate* function. * * @param resource|string $imageRef The path to a local file, a base64 encoded string or a resource created by an imagecreate* function or a file resource (file handle ). - * @return resource|false An image resource or false on error + * @return GdImage|false An image resource or false on error */ - public function load($imageRef) { - if (\is_resource($imageRef)) { - if (\get_resource_type($imageRef) == 'gd') { - $this->resource = $imageRef; - return $this->resource; - } elseif (\in_array(\get_resource_type($imageRef), ['file', 'stream'])) { - return $this->loadFromFileHandle($imageRef); - } - } elseif ($this->loadFromBase64($imageRef) !== false) { + public function load(\GdImage|string $imageRef) { + if ($imageRef instanceof \GdImage) { + $this->resource = $imageRef; + return $this->resource; + } + if (is_resource($imageRef)) { + return $this->loadFromFileHandle($imageRef); + } + + if ($this->loadFromBase64($imageRef) !== false) { return $this->resource; - } elseif ($this->loadFromFile($imageRef) !== false) { + } + + if ($this->loadFromFile($imageRef) !== false) { return $this->resource; - } elseif ($this->loadFromData($imageRef) !== false) { + } + + if ($this->loadFromData($imageRef) !== false) { return $this->resource; } $this->logger->debug(__METHOD__ . '(): could not load anything. Giving up!', ['app' => 'core']); From 739fcb442104e51aae383bdda6acb1abbd12941d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Tue, 19 Sep 2023 10:37:10 +0200 Subject: [PATCH 02/42] fix: param declarations and deprecations --- lib/private/App/DependencyAnalyzer.php | 22 +++++----- lib/private/App/InfoParser.php | 2 - .../AppFramework/Utility/SimpleContainer.php | 39 +++++++++--------- .../Authentication/Token/DefaultToken.php | 2 +- lib/private/DB/MDB2SchemaReader.php | 2 - lib/private/Files/External/StorageConfig.php | 4 +- lib/private/L10N/L10N.php | 3 +- lib/private/Memcache/ArrayCache.php | 11 ++--- lib/private/Memcache/Cache.php | 32 ++++++--------- lib/private/Updater/VersionCheck.php | 2 - lib/private/legacy/l10n/string.php | 30 ++++++-------- .../Files/External/Auth/AuthMechanism.php | 2 +- lib/public/Files/External/Backend/Backend.php | 2 +- .../Files/External/DefinitionParameter.php | 5 +-- lib/public/Files/External/IStorageConfig.php | 2 +- lib/public/IL10N.php | 3 +- lib/public/Lock/LockedException.php | 2 +- tests/lib/App/DependencyAnalyzerTest.php | 22 +++++----- tests/lib/L10N/L10nTest.php | 40 ++++++++++--------- 19 files changed, 102 insertions(+), 125 deletions(-) diff --git a/lib/private/App/DependencyAnalyzer.php b/lib/private/App/DependencyAnalyzer.php index 4acbc9b181dc..15d47f928fb0 100644 --- a/lib/private/App/DependencyAnalyzer.php +++ b/lib/private/App/DependencyAnalyzer.php @@ -136,19 +136,19 @@ private function analyzePhpVersion(array $dependencies) { if (isset($dependencies['php']['@attributes']['min-version'])) { $minVersion = $dependencies['php']['@attributes']['min-version']; if ($this->compareSmaller($this->platform->getPhpVersion(), $minVersion)) { - $missing[] = (string)$this->l->t('PHP %s or higher is required.', $minVersion); + $missing[] = (string)$this->l->t('PHP %s or higher is required.', [$minVersion]); } } if (isset($dependencies['php']['@attributes']['max-version'])) { $maxVersion = $dependencies['php']['@attributes']['max-version']; if ($this->compareBigger($this->platform->getPhpVersion(), $maxVersion)) { - $missing[] = (string)$this->l->t('PHP with a version lower than %s is required.', $maxVersion); + $missing[] = (string)$this->l->t('PHP with a version lower than %s is required.', [$maxVersion]); } } if (isset($dependencies['php']['@attributes']['min-int-size'])) { $intSize = $dependencies['php']['@attributes']['min-int-size']; if ($intSize > $this->platform->getIntSize()*8) { - $missing[] = (string)$this->l->t('%sbit or higher PHP required.', $intSize); + $missing[] = (string)$this->l->t('%sbit or higher PHP required.', [$intSize]); } } return $missing; @@ -175,8 +175,8 @@ private function analyzeDatabases(array $dependencies) { return $this->getValue($db); }, $supportedDatabases); $currentDatabase = $this->platform->getDatabase(); - if (!\in_array($currentDatabase, $supportedDatabases)) { - $missing[] = (string)$this->l->t('Following databases are supported: %s', \join(', ', $supportedDatabases)); + if (!\in_array($currentDatabase, $supportedDatabases, true)) { + $missing[] = (string)$this->l->t('Following databases are supported: %s', [\implode(', ', $supportedDatabases)]); } return $missing; } @@ -205,7 +205,7 @@ private function analyzeCommands(array $dependencies) { } $commandName = $this->getValue($command); if (!$this->platform->isCommandKnown($commandName)) { - $missing[] = (string)$this->l->t('The command line tool %s could not be found', $commandName); + $missing[] = (string)$this->l->t('The command line tool %s could not be found', [$commandName]); } } return $missing; @@ -232,7 +232,7 @@ private function analyzeLibraries(array $dependencies) { $libName = $this->getValue($lib); $libVersion = $this->platform->getLibraryVersion($libName); if ($libVersion === null) { - $missing[] = (string)$this->l->t('The library %s is not available.', $libName); + $missing[] = (string)$this->l->t('The library %s is not available.', [$libName]); continue; } @@ -282,8 +282,8 @@ private function analyzeOS(array $dependencies) { $oss = [$oss]; } $currentOS = $this->platform->getOS(); - if (!\in_array($currentOS, $oss)) { - $missing[] = (string)$this->l->t('Following platforms are supported: %s', \join(', ', $oss)); + if (!\in_array($currentOS, $oss, true)) { + $missing[] = (string)$this->l->t('Following platforms are supported: %s', [\implode(', ', $oss)]); } return $missing; } @@ -312,12 +312,12 @@ private function analyzeOC(array $dependencies, array $appInfo) { if ($minVersion !== null) { if ($this->compareSmaller($this->platform->getOcVersion(), $minVersion)) { - $missing[] = (string)$this->l->t('ownCloud %s or higher is required.', $minVersion); + $missing[] = (string)$this->l->t('ownCloud %s or higher is required.', [$minVersion]); } } if ($maxVersion !== null) { if ($this->compareBigger($this->platform->getOcVersion(), $maxVersion)) { - $missing[] = (string)$this->l->t('ownCloud %s or lower is required.', $maxVersion); + $missing[] = (string)$this->l->t('ownCloud %s or lower is required.', [$maxVersion]); } } return $missing; diff --git a/lib/private/App/InfoParser.php b/lib/private/App/InfoParser.php index 6ca1ecc2ca7f..93f5ead4fd8e 100644 --- a/lib/private/App/InfoParser.php +++ b/lib/private/App/InfoParser.php @@ -43,10 +43,8 @@ public function parse($file) { } \libxml_use_internal_errors(true); - $loadEntities = \libxml_disable_entity_loader(false); $xml = \simplexml_load_file($file); - \libxml_disable_entity_loader($loadEntities); if ($xml === false) { \libxml_clear_errors(); throw new InvalidArgumentException('Invalid XML'); diff --git a/lib/private/AppFramework/Utility/SimpleContainer.php b/lib/private/AppFramework/Utility/SimpleContainer.php index 91d7b1a15484..1d8d820a22c8 100644 --- a/lib/private/AppFramework/Utility/SimpleContainer.php +++ b/lib/private/AppFramework/Utility/SimpleContainer.php @@ -47,31 +47,30 @@ private function buildClass(ReflectionClass $class) { $constructor = $class->getConstructor(); if ($constructor === null) { return $class->newInstance(); - } else { - $parameters = []; - foreach ($constructor->getParameters() as $parameter) { - $parameterClass = $parameter->getClass(); + } - // try to find out if it is a class or a simple parameter - if ($parameterClass === null) { - $resolveName = $parameter->getName(); - } else { - $resolveName = $parameterClass->name; - } + $parameters = []; + foreach ($constructor->getParameters() as $parameter) { + $resolveName = $parameter->getName(); - try { - $parameters[] = $this->query($resolveName); - } catch (QueryException $ex) { - if ($parameter->isDefaultValueAvailable()) { - $default = $parameter->getDefaultValue(); - $parameters[] = $default; - } else { - throw $ex; - } + $parameterType = $parameter->getType(); + // try to find out if it is a class or a simple parameter + if (($parameterType instanceof \ReflectionNamedType) && !$parameterType->isBuiltin()) { + $resolveName = $parameterType->getName(); + } + + try { + $parameters[] = $this->query($resolveName); + } catch (QueryException $ex) { + if ($parameter->isDefaultValueAvailable()) { + $default = $parameter->getDefaultValue(); + $parameters[] = $default; + } else { + throw $ex; } } - return $class->newInstanceArgs($parameters); } + return $class->newInstanceArgs($parameters); } /** diff --git a/lib/private/Authentication/Token/DefaultToken.php b/lib/private/Authentication/Token/DefaultToken.php index 64a7d676f7b0..43f1a33ab22b 100644 --- a/lib/private/Authentication/Token/DefaultToken.php +++ b/lib/private/Authentication/Token/DefaultToken.php @@ -102,7 +102,7 @@ public function getPassword() { return $this->password; } - public function jsonSerialize() { + public function jsonSerialize(): array { return [ 'id' => $this->id, 'name' => $this->name, diff --git a/lib/private/DB/MDB2SchemaReader.php b/lib/private/DB/MDB2SchemaReader.php index cf6be41ce7b0..e917cf0c9cab 100644 --- a/lib/private/DB/MDB2SchemaReader.php +++ b/lib/private/DB/MDB2SchemaReader.php @@ -54,9 +54,7 @@ public function __construct(IConfig $config, AbstractPlatform $platform) { * @return Schema */ public function loadSchemaFromFile($file, Schema $schema) { - $loadEntities = \libxml_disable_entity_loader(false); $xml = \simplexml_load_file($file); - \libxml_disable_entity_loader($loadEntities); foreach ($xml->children() as $child) { /** * @var \SimpleXMLElement $child diff --git a/lib/private/Files/External/StorageConfig.php b/lib/private/Files/External/StorageConfig.php index 936f5628ef0c..407dff485551 100644 --- a/lib/private/Files/External/StorageConfig.php +++ b/lib/private/Files/External/StorageConfig.php @@ -391,10 +391,8 @@ public function setType($type) { /** * Serialize config to JSON - * - * @return array */ - public function jsonSerialize() { + public function jsonSerialize(): array { $result = []; if ($this->id !== null) { $result['id'] = $this->id; diff --git a/lib/private/L10N/L10N.php b/lib/private/L10N/L10N.php index 9e663d84943c..b4ec20eaa331 100644 --- a/lib/private/L10N/L10N.php +++ b/lib/private/L10N/L10N.php @@ -70,6 +70,7 @@ public function getLanguageCode() { /** * Translating + * * @param string $text The text we need a translation for * @param array $parameters default:array() Parameters for sprintf * @return string Translation or the same text @@ -77,7 +78,7 @@ public function getLanguageCode() { * Returns the translation. If no translation is found, $text will be * returned. */ - public function t($text, $parameters = []) { + public function t(string $text, $parameters = []) { return (string) new \OC_L10N_String($this, $text, $parameters); } diff --git a/lib/private/Memcache/ArrayCache.php b/lib/private/Memcache/ArrayCache.php index b5e0bd8b2604..be54e9c311e4 100644 --- a/lib/private/Memcache/ArrayCache.php +++ b/lib/private/Memcache/ArrayCache.php @@ -28,14 +28,14 @@ class ArrayCache extends Cache implements IMemcache { /** @var array Array with the cached data */ - protected $cachedData = []; + protected array $cachedData = []; use CADTrait; /** * {@inheritDoc} */ - public function get($key) { + public function get($key): mixed { if ($this->hasKey($key)) { return $this->cachedData[$key]; } @@ -45,7 +45,7 @@ public function get($key) { /** * {@inheritDoc} */ - public function set($key, $value, $ttl = 0) { + public function set($key, $value, $ttl = 0): mixed { $this->cachedData[$key] = $value; return true; } @@ -150,10 +150,7 @@ public function cas($key, $old, $new) { } } - /** - * {@inheritDoc} - */ - public static function isAvailable() { + public static function isAvailable(): true { return true; } } diff --git a/lib/private/Memcache/Cache.php b/lib/private/Memcache/Cache.php index 99692026edd5..40696920f6ae 100644 --- a/lib/private/Memcache/Cache.php +++ b/lib/private/Memcache/Cache.php @@ -24,31 +24,23 @@ namespace OC\Memcache; -abstract class Cache implements \ArrayAccess, \OCP\ICache { - /** - * @var string $prefix - */ - protected $prefix; +use OCP\ICache; - /** - * @param string $prefix - */ - public function __construct($prefix = '') { +abstract class Cache implements \ArrayAccess, ICache { + protected string $prefix; + + public function __construct(string $prefix = '') { $this->prefix = $prefix; } /** * @return string Prefix used for caching purposes */ - public function getPrefix() { + public function getPrefix(): string { return $this->prefix; } - /** - * @param string $key - * @return mixed - */ - abstract public function get($key); + abstract public function get($key): mixed; /** * @param string $key @@ -56,7 +48,7 @@ abstract public function get($key); * @param int $ttl * @return mixed */ - abstract public function set($key, $value, $ttl = 0); + abstract public function set($key, $value, $ttl = 0): mixed; /** * @param string $key @@ -78,19 +70,19 @@ abstract public function clear($prefix = ''); //implement the ArrayAccess interface - public function offsetExists($offset) { + public function offsetExists($offset): bool { return $this->hasKey($offset); } - public function offsetSet($offset, $value) { + public function offsetSet($offset, $value): void { $this->set($offset, $value); } - public function offsetGet($offset) { + public function offsetGet($offset): mixed { return $this->get($offset); } - public function offsetUnset($offset) { + public function offsetUnset($offset): void { $this->remove($offset); } } diff --git a/lib/private/Updater/VersionCheck.php b/lib/private/Updater/VersionCheck.php index 9269717c6341..062a196e0f9f 100644 --- a/lib/private/Updater/VersionCheck.php +++ b/lib/private/Updater/VersionCheck.php @@ -78,9 +78,7 @@ public function check() { $tmp = []; $xml = $this->getUrlContent($url); if ($xml) { - $loadEntities = \libxml_disable_entity_loader(true); $data = @\simplexml_load_string($xml); - \libxml_disable_entity_loader($loadEntities); if ($data !== false) { $tmp['version'] = (string)$data->version; $tmp['versionstring'] = (string)$data->versionstring; diff --git a/lib/private/legacy/l10n/string.php b/lib/private/legacy/l10n/string.php index fbfd94860ded..2c71b95c320f 100644 --- a/lib/private/legacy/l10n/string.php +++ b/lib/private/legacy/l10n/string.php @@ -1,4 +1,7 @@ * @author Bernhard Posselt @@ -26,25 +29,17 @@ */ class OC_L10N_String implements JsonSerializable { - /** @var \OC\L10N\L10N */ - protected $l10n; + protected L10N $l10n; - /** @var string */ - protected $text; + protected string $text; /** @var array */ - protected $parameters; + protected array $parameters; /** @var integer */ - protected $count; + protected int $count; - /** - * @param \OC\L10N\L10N $l10n - * @param string|string[] $text - * @param array $parameters - * @param int $count - */ - public function __construct($l10n, $text, $parameters, $count = 1) { + public function __construct(L10N $l10n, string $text, array $parameters, int $count = 1) { $this->l10n = $l10n; $this->text = $text; $this->parameters = $parameters; @@ -66,13 +61,14 @@ public function __toString() { // Replace %n first (won't interfere with vsprintf) $text = \str_replace('%n', $this->count, $text); - $text = @\vsprintf($text, $this->parameters); + if (count($this->parameters) === 0) { + return (string)$text; + } - // If vsprintf fails, return untranslated string - return $text === false ? $this->text : $text; + return \vsprintf($text, $this->parameters); } - public function jsonSerialize() { + public function jsonSerialize(): string { return $this->__toString(); } } diff --git a/lib/public/Files/External/Auth/AuthMechanism.php b/lib/public/Files/External/Auth/AuthMechanism.php index b637b9b8de0a..00c6592e562d 100644 --- a/lib/public/Files/External/Auth/AuthMechanism.php +++ b/lib/public/Files/External/Auth/AuthMechanism.php @@ -96,7 +96,7 @@ public function setScheme($scheme) { * @return array * @since 10.0 */ - public function jsonSerialize() { + public function jsonSerialize(): array { $data = $this->jsonSerializeDefinition(); $data += $this->jsonSerializeIdentifier(); diff --git a/lib/public/Files/External/Backend/Backend.php b/lib/public/Files/External/Backend/Backend.php index e11856d65cc6..4288bb60fe0f 100644 --- a/lib/public/Files/External/Backend/Backend.php +++ b/lib/public/Files/External/Backend/Backend.php @@ -118,7 +118,7 @@ public function addAuthScheme($scheme) { * @return array * @since 10.0 */ - public function jsonSerialize() { + public function jsonSerialize(): array { $data = $this->jsonSerializeDefinition(); $data += $this->jsonSerializeIdentifier(); diff --git a/lib/public/Files/External/DefinitionParameter.php b/lib/public/Files/External/DefinitionParameter.php index ed00e2c1d9c9..8c947ed97f05 100644 --- a/lib/public/Files/External/DefinitionParameter.php +++ b/lib/public/Files/External/DefinitionParameter.php @@ -156,11 +156,8 @@ public function isFlagSet($flag) { /** * Serialize into JSON for client-side JS - * - * @return string - * @since 10.0 */ - public function jsonSerialize() { + public function jsonSerialize(): array { return [ 'value' => $this->getText(), 'flags' => $this->getFlags(), diff --git a/lib/public/Files/External/IStorageConfig.php b/lib/public/Files/External/IStorageConfig.php index 8405968f726d..e04c9b29ace5 100644 --- a/lib/public/Files/External/IStorageConfig.php +++ b/lib/public/Files/External/IStorageConfig.php @@ -248,5 +248,5 @@ public function setType($type); * @return array * @since 10.0 */ - public function jsonSerialize(); + public function jsonSerialize(): array; } diff --git a/lib/public/IL10N.php b/lib/public/IL10N.php index 933ce1f75d7a..f871ee57f592 100644 --- a/lib/public/IL10N.php +++ b/lib/public/IL10N.php @@ -44,6 +44,7 @@ interface IL10N { /** * Translating + * * @param string $text The text we need a translation for * @param array $parameters default:array() Parameters for sprintf * @return \OC_L10N_String Translation or the same text @@ -52,7 +53,7 @@ interface IL10N { * returned. * @since 6.0.0 */ - public function t($text, $parameters = []); + public function t(string $text, $parameters = []); /** * Translating diff --git a/lib/public/Lock/LockedException.php b/lib/public/Lock/LockedException.php index 87d7c1970f2b..fd1667e91c6b 100644 --- a/lib/public/Lock/LockedException.php +++ b/lib/public/Lock/LockedException.php @@ -46,7 +46,7 @@ class LockedException extends \Exception { * @since 8.1.0 */ public function __construct($path, \Exception $previous = null) { - $message = \OC::$server->getL10N('lib')->t('"%s" is locked', $path); + $message = \OC::$server->getL10N('lib')->t('"%s" is locked', [$path]); parent::__construct($message, 0, $previous); $this->path = $path; } diff --git a/tests/lib/App/DependencyAnalyzerTest.php b/tests/lib/App/DependencyAnalyzerTest.php index ef15a0e69835..c8ac9ad41891 100644 --- a/tests/lib/App/DependencyAnalyzerTest.php +++ b/tests/lib/App/DependencyAnalyzerTest.php @@ -30,41 +30,41 @@ public function setUp(): void { ->getMock(); $this->platformMock->expects($this->any()) ->method('getPhpVersion') - ->will($this->returnValue('5.4.3')); + ->willReturn('5.4.3'); $this->platformMock->expects($this->any()) ->method('getIntSize') - ->will($this->returnValue('4')); + ->willReturn('4'); $this->platformMock->expects($this->any()) ->method('getDatabase') - ->will($this->returnValue('mysql')); + ->willReturn('mysql'); $this->platformMock->expects($this->any()) ->method('getOS') - ->will($this->returnValue('Linux')); + ->willReturn('Linux'); $this->platformMock->expects($this->any()) ->method('isCommandKnown') - ->will($this->returnCallback(function ($command) { + ->willReturnCallback(function ($command) { return ($command === 'grep'); - })); + }); $this->platformMock->expects($this->any()) ->method('getLibraryVersion') - ->will($this->returnCallback(function ($lib) { + ->willReturnCallback(function ($lib) { if ($lib === 'curl') { return "2.3.4"; } return null; - })); + }); $this->platformMock->expects($this->any()) ->method('getOcVersion') - ->will($this->returnValue('8.0.2')); + ->willReturn('8.0.2'); $this->l10nMock = $this->getMockBuilder('\OCP\IL10N') ->disableOriginalConstructor() ->getMock(); $this->l10nMock->expects($this->any()) ->method('t') - ->will($this->returnCallback(function ($text, $parameters = []) { + ->willReturnCallback(function ($text, $parameters = []) { return \vsprintf($text, $parameters); - })); + }); $this->analyser = new DependencyAnalyzer($this->platformMock, $this->l10nMock); } diff --git a/tests/lib/L10N/L10nTest.php b/tests/lib/L10N/L10nTest.php index c3a67a5d7e67..68a7e54548d5 100644 --- a/tests/lib/L10N/L10nTest.php +++ b/tests/lib/L10N/L10nTest.php @@ -11,9 +11,11 @@ use DateTime; use OC\L10N\Factory; use OC\L10N\L10N; +use OCP\IConfig; use OCP\IUserSession; use OCP\Theme\IThemeService; use Test\TestCase; +use OCP\IRequest; /** * Class L10nTest @@ -24,21 +26,21 @@ class L10nTest extends TestCase { /** * @return Factory */ - protected function getFactory() { - /** @var \OCP\IConfig $config */ - $config = $this->createMock('OCP\IConfig'); + protected function getFactory(): Factory { + /** @var IConfig $config */ + $config = $this->createMock(IConfig::class); /** @var \OCP\IRequest $request */ - $request = $this->createMock('OCP\IRequest'); + $request = $this->createMock(IRequest::class); /** @var IThemeService $themeService */ $themeService = $this->getMockBuilder(IThemeService::class) ->disableOriginalConstructor() ->getMock(); /** @var IUserSession $userSession */ - $userSession = $this->createMock('OCP\IUserSession'); + $userSession = $this->createMock(IUserSession::class); return new Factory($config, $request, $themeService, $userSession, \OC::$SERVERROOT); } - public function testGermanPluralTranslations() { + public function testGermanPluralTranslations(): void { $transFile = \OC::$SERVERROOT.'/tests/data/l10n/de.json'; $l = new L10N($this->getFactory(), 'test', 'de', [$transFile]); @@ -46,15 +48,15 @@ public function testGermanPluralTranslations() { $this->assertEquals('2 Dateien', (string) $l->n('%n file', '%n files', 2)); } - public function testMalformedTranslations() { - $lMock = $this->createMock('OC\L10N\L10N'); + public function testMalformedTranslations(): void { + $lMock = $this->createMock(L10N::class); $lMock->method('getTranslations')->willReturn(['malformed' => 'malformed %']); $lString = new \OC_L10N_String($lMock, "malformed", []); - self::assertEquals('malformed', $lString->__toString()); + self::assertEquals('malformed %', $lString->__toString()); } - public function testRussianPluralTranslations() { + public function testRussianPluralTranslations(): void { $transFile = \OC::$SERVERROOT.'/tests/data/l10n/ru.json'; $l = new L10N($this->getFactory(), 'test', 'ru', [$transFile]); @@ -78,7 +80,7 @@ public function testRussianPluralTranslations() { */ } - public function testCzechPluralTranslations() { + public function testCzechPluralTranslations(): void { $transFile = \OC::$SERVERROOT.'/tests/data/l10n/cs.json'; $l = new L10N($this->getFactory(), 'test', 'cs', [$transFile]); @@ -87,7 +89,7 @@ public function testCzechPluralTranslations() { $this->assertEquals('5 oken', (string)$l->n('%n window', '%n windows', 5)); } - public function localizationData() { + public function localizationData(): array { $narrowNoBreakSpace = "\xE2\x80\xAF"; return [ // timestamp as string @@ -127,12 +129,12 @@ public function localizationData() { /** * @dataProvider localizationData */ - public function testNumericStringLocalization($expectedDate, $lang, $type, $value) { + public function testNumericStringLocalization($expectedDate, $lang, $type, $value): void { $l = new L10N($this->getFactory(), 'test', $lang, []); $this->assertSame($expectedDate, $l->l($type, $value)); } - public function firstDayData() { + public function firstDayData(): array { return [ [1, 'de'], [0, 'en'], @@ -144,12 +146,12 @@ public function firstDayData() { * @param $expected * @param $lang */ - public function testFirstWeekDay($expected, $lang) { + public function testFirstWeekDay($expected, $lang): void { $l = new L10N($this->getFactory(), 'test', $lang, []); $this->assertSame($expected, $l->l('firstday', 'firstday')); } - public function jsDateData() { + public function jsDateData(): array { return [ ['dd.MM.yy', 'de'], ['M/d/yy', 'en'], @@ -161,17 +163,17 @@ public function jsDateData() { * @param $expected * @param $lang */ - public function testJSDate($expected, $lang) { + public function testJSDate($expected, $lang): void { $l = new L10N($this->getFactory(), 'test', $lang, []); $this->assertSame($expected, $l->l('jsdate', 'jsdate')); } - public function testFactoryGetLanguageCode() { + public function testFactoryGetLanguageCode(): void { $l = $this->getFactory()->get('lib', 'de'); $this->assertEquals('de', $l->getLanguageCode()); } - public function testServiceGetLanguageCode() { + public function testServiceGetLanguageCode(): void { $l = \OC::$server->getL10N('lib', 'de'); $this->assertEquals('de', $l->getLanguageCode()); } From 7acdeeadbf93dab1355e8bdd8c10fe6d38c3ab6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Tue, 19 Sep 2023 12:30:47 +0200 Subject: [PATCH 03/42] chore: move adjustments --- lib/private/Image/BmpToResource.php | 24 ++-- lib/private/IntegrityCheck/Checker.php | 3 +- lib/private/Memcache/APCu.php | 8 +- lib/private/Memcache/NullCache.php | 8 +- lib/private/Memcache/Redis.php | 12 +- lib/private/legacy/image.php | 81 ++++++------- lib/private/legacy/l10n/string.php | 2 +- .../lib/AppFramework/Db/MapperTestUtility.php | 2 + tests/lib/AvatarTest.php | 108 +++++++++--------- tests/lib/Files/ViewTest.php | 1 + tests/lib/ImageTest.php | 3 +- 11 files changed, 132 insertions(+), 120 deletions(-) diff --git a/lib/private/Image/BmpToResource.php b/lib/private/Image/BmpToResource.php index 4eb87814aa61..44713af3d78b 100644 --- a/lib/private/Image/BmpToResource.php +++ b/lib/private/Image/BmpToResource.php @@ -46,8 +46,7 @@ class BmpToResource { /** @var string[][] $pixelArray */ private $pixelArray; - /** @var resource $resource */ - private $resource; + private \GdImage $resource; /** @var array $bytesPerDepth */ private $bytesPerDepth = [ @@ -87,10 +86,11 @@ public function toResource() { $this->pixelArray = $this->readPixelArray(); // create gd image - $this->resource = \imagecreatetruecolor($this->header['width'], $this->header['height']); - if ($this->resource === false) { + $resource = \imagecreatetruecolor($this->header['width'], $this->header['height']); + if ($resource === false) { throw new \RuntimeException('imagecreatetruecolor failed for file ' . $this->getFilename() . '" with dimensions ' . $this->header['width'] . 'x' . $this->header['height']); } + $this->resource = $resource; $this->pixelArrayToImage(); } catch (\Exception $e) { @@ -149,7 +149,7 @@ private function readDibHeader() { } $validBitDepth = \array_keys($this->bytesPerDepth); - if (!\in_array($dibHeader['bits'], $validBitDepth)) { + if (!\in_array($dibHeader['bits'], $validBitDepth, true)) { throw new \UnexpectedValueException('Bit Depth ' . $dibHeader['bits'] . ' in ' . $this->getFilename() . ' is not supported'); } @@ -159,7 +159,7 @@ private function readDibHeader() { private function fixImageSize($header) { // No compression - calculate it in our own if ($header['compression'] === self::COMPRESSION_BI_RGB) { - $bytesPerRow = \intval(\floor(($header['bits'] * $header['width'] + 31) / 32) * 4); + $bytesPerRow = (int)(\floor(($header['bits'] * $header['width'] + 31) / 32) * 4); $imageSize = $bytesPerRow * \abs($header['height']); } else { $imageSize = $this->file->getSize() - $this->header['offset']; @@ -196,7 +196,7 @@ private function readPixelArray() { $this->file->fseek($this->header['offset'], SEEK_SET); $pixelString = $this->readFile($this->header['imagesize']); - $bytesPerRow = \intval(\floor(($this->header['bits'] * $this->header['width'] + 31) / 32) * 4); + $bytesPerRow = (int)(\floor(($this->header['bits'] * $this->header['width'] + 31) / 32) * 4); $plainPixelArray = \str_split($pixelString, $bytesPerRow); // Positive height: Bottom row first. @@ -212,10 +212,7 @@ private function readPixelArray() { return $pixelArray; } - /** - * @return resource - */ - private function pixelArrayToImage() { + private function pixelArrayToImage(): \GdImage { $x = 0; $y = 0; foreach ($this->pixelArray as $pixelRow) { @@ -246,7 +243,7 @@ private function pixelArrayToImage() { private function getColors($raw) { $extra = \chr(0); // used to complement an argument to word or double word $colors = []; - if (\in_array($this->header['bits'], [32, 24])) { + if (\in_array($this->header['bits'], [32, 24], true)) { $colors = @\unpack('V', $raw . $extra); } elseif ($this->header['bits'] === 16) { $colors = @\unpack('v', $raw); @@ -264,8 +261,7 @@ function ($i) { ); } - $colors = \array_values($colors); - return $colors; + return \array_values($colors); } /** diff --git a/lib/private/IntegrityCheck/Checker.php b/lib/private/IntegrityCheck/Checker.php index f69de935766f..a27191387c1b 100644 --- a/lib/private/IntegrityCheck/Checker.php +++ b/lib/private/IntegrityCheck/Checker.php @@ -464,7 +464,8 @@ public function getResults() { return \json_decode($cachedResults, true); } - return \json_decode($this->getAppValue(self::CACHE_KEY, '{}'), true); + $v = $this->getAppValue(self::CACHE_KEY, '{}') ?? '{}'; + return \json_decode($v, true); } /** diff --git a/lib/private/Memcache/APCu.php b/lib/private/Memcache/APCu.php index c3fdc6f15b9b..a3e045b007f0 100644 --- a/lib/private/Memcache/APCu.php +++ b/lib/private/Memcache/APCu.php @@ -35,7 +35,7 @@ class APCu extends Cache implements IMemcache { use CADTrait; - public function get($key) { + public function get($key): mixed { $result = \apcu_fetch($this->getPrefix() . $key, $success); if (!$success) { return null; @@ -43,7 +43,7 @@ public function get($key) { return $result; } - public function set($key, $value, $ttl = 0) { + public function set($key, $value, $ttl = 0): mixed { return \apcu_store($this->getPrefix() . $key, $value, $ttl); } @@ -105,7 +105,7 @@ public function dec($key, $step = 1) { * @param mixed $new * @return bool */ - public function cas($key, $old, $new) { + public function cas($key, $old, $new): bool { // apc only does cas for ints if (\is_int($old) && \is_int($new)) { return \apcu_cas($this->getPrefix() . $key, $old, $new); @@ -117,7 +117,7 @@ public function cas($key, $old, $new) { /** * @return bool */ - public static function isAvailable() { + public static function isAvailable(): bool { if (!\extension_loaded('apcu')) { return false; } diff --git a/lib/private/Memcache/NullCache.php b/lib/private/Memcache/NullCache.php index 792410710258..49c9aa6b2d19 100644 --- a/lib/private/Memcache/NullCache.php +++ b/lib/private/Memcache/NullCache.php @@ -25,12 +25,14 @@ namespace OC\Memcache; -class NullCache extends Cache implements \OCP\IMemcache { - public function get($key) { +use OCP\IMemcache; + +class NullCache extends Cache implements IMemcache { + public function get($key): mixed { return null; } - public function set($key, $value, $ttl = 0) { + public function set($key, $value, $ttl = 0): mixed { return true; } diff --git a/lib/private/Memcache/Redis.php b/lib/private/Memcache/Redis.php index d92bc5941221..5717005ee372 100644 --- a/lib/private/Memcache/Redis.php +++ b/lib/private/Memcache/Redis.php @@ -45,21 +45,21 @@ protected function getNameSpace() { return $this->prefix; } - public function get($key) { + public function get($key): mixed { $result = self::$cache->get($this->getNameSpace() . $key); if ($result === false && !self::$cache->exists($this->getNameSpace() . $key)) { return null; - } else { - return \json_decode($result, true); } + + return \json_decode($result, true); } - public function set($key, $value, $ttl = 0) { + public function set($key, $value, $ttl = 0): mixed { if ($ttl > 0) { return self::$cache->setex($this->getNameSpace() . $key, $ttl, \json_encode($value)); - } else { - return self::$cache->set($this->getNameSpace() . $key, \json_encode($value)); } + + return self::$cache->set($this->getNameSpace() . $key, \json_encode($value)); } public function hasKey($key) { diff --git a/lib/private/legacy/image.php b/lib/private/legacy/image.php index f602fbc63686..a6d5658c6eac 100644 --- a/lib/private/legacy/image.php +++ b/lib/private/legacy/image.php @@ -106,11 +106,10 @@ public function __construct($imageRef = null, $logger = null, $config = null) { /** * Determine whether the object contains an image resource. - * - * @return bool */ - public function valid() { // apparently you can't name a method 'empty'... - return \is_resource($this->resource); + public function valid(): bool { + // apparently you can't name a method 'empty'... + return $this->resource instanceof \GdImage; } /** @@ -217,7 +216,9 @@ public function save($filePath = null, $mimeType = null) { if ($filePath === null && $this->filePath === null) { $this->logger->error(__METHOD__ . '(): called with no path.', ['app' => 'core']); return false; - } elseif ($filePath === null && $this->filePath !== null) { + } + + if ($filePath === null && $this->filePath !== null) { $filePath = $this->filePath; } return $this->_output($filePath, $mimeType); @@ -239,7 +240,9 @@ private function _output($filePath = null, $mimeType = null) { if (!\is_writable(\dirname($filePath))) { $this->logger->error(__METHOD__ . '(): Directory \'' . \dirname($filePath) . '\' is not writable.', ['app' => 'core']); return false; - } elseif (\is_writable(\dirname($filePath)) && \file_exists($filePath) && !\is_writable($filePath)) { + } + + if (\is_writable(\dirname($filePath)) && \file_exists($filePath) && !\is_writable($filePath)) { $this->logger->error(__METHOD__ . '(): File \'' . $filePath . '\' is not writable.', ['app' => 'core']); return false; } @@ -315,10 +318,7 @@ public function __invoke() { return $this->show(); } - /** - * @return resource Returns the image resource in any. - */ - public function resource() { + public function resource(): ?GdImage { return $this->resource; } @@ -490,18 +490,18 @@ public function fixOrientation() { \imagedestroy($this->resource); $this->resource = $res; return true; - } else { - $this->logger->debug('OC_Image->fixOrientation() Error during alpha-saving', ['app' => 'core']); - return false; } - } else { - $this->logger->debug('OC_Image->fixOrientation() Error during alpha-blending', ['app' => 'core']); + + $this->logger->debug('OC_Image->fixOrientation() Error during alpha-saving', ['app' => 'core']); return false; } - } else { - $this->logger->debug('OC_Image->fixOrientation() Error during orientation fixing', ['app' => 'core']); + + $this->logger->debug('OC_Image->fixOrientation() Error during alpha-blending', ['app' => 'core']); return false; } + + $this->logger->debug('OC_Image->fixOrientation() Error during orientation fixing', ['app' => 'core']); + return false; } return false; } @@ -517,7 +517,7 @@ public function load(\GdImage|string $imageRef) { $this->resource = $imageRef; return $this->resource; } - if (is_resource($imageRef)) { + if (\is_resource($imageRef)) { return $this->loadFromFileHandle($imageRef); } @@ -541,9 +541,9 @@ public function load(\GdImage|string $imageRef) { * It is the responsibility of the caller to position the pointer at the correct place and to close the handle again. * * @param resource $handle - * @return resource|false An image resource or false on error + * @return GdImage|false|null An image resource or false on error */ - public function loadFromFileHandle($handle) { + public function loadFromFileHandle($handle): GdImage|false|null { $contents = \stream_get_contents($handle); if ($this->loadFromData($contents)) { $this->adjustStreamChunkSize($handle); @@ -671,7 +671,10 @@ public function loadFromFile($imagePath = false) { } break; case IMAGETYPE_BMP: - $this->resource = $this->imagecreatefrombmp($imagePath); + $resource = $this->imagecreatefrombmp($imagePath); + if ($resource !== false) { + $this->resource = $resource; + } break; /* case IMAGETYPE_TIFF_II: // (intel byte order) @@ -718,52 +721,52 @@ public function loadFromFile($imagePath = false) { * Loads an image from a string of data. * * @param string $str A string of image data as read from a file. - * @return bool|resource An image resource or false on error + * @return bool An image resource or false on error */ public function loadFromData($str) { - if (\is_resource($str)) { + if (!\is_string($str)) { return false; } - $this->resource = @\imagecreatefromstring($str); + $resource = @\imagecreatefromstring($str); if ($this->fileInfo) { $this->mimeType = $this->fileInfo->buffer($str); } - if (\is_resource($this->resource)) { - \imagealphablending($this->resource, false); - \imagesavealpha($this->resource, true); - } - - if (!$this->resource) { + if (!$resource) { $this->logger->debug('OC_Image->loadFromFile, could not load', ['app' => 'core']); return false; } - return $this->resource; + $this->resource = $resource; + \imagealphablending($this->resource, false); + \imagesavealpha($this->resource, true); + + return true; } /** * Loads an image from a base64 encoded string. * * @param string $str A string base64 encoded string of image data. - * @return bool|resource An image resource or false on error + * @return bool An image resource or false on error */ - public function loadFromBase64($str) { + private function loadFromBase64($str) { if (!\is_string($str)) { return false; } $data = \base64_decode($str); if ($data) { // try to load from string data - $this->resource = @\imagecreatefromstring($data); + $resource = @\imagecreatefromstring($data); if ($this->fileInfo) { $this->mimeType = $this->fileInfo->buffer($data); } - if (!$this->resource) { + if ($resource === false) { $this->logger->debug('OC_Image->loadFromBase64, could not load', ['app' => 'core']); return false; } - return $this->resource; - } else { - return false; + $this->resource = $resource; + return true; } + + return false; } /** @@ -1019,7 +1022,7 @@ public function __destruct() { * @link http://www.programmierer-forum.de/imagebmp-gute-funktion-gefunden-t143716.htm * @author mgutt * @version 1.00 - * @param resource $im + * @param \GdImage $im * @param string $fileName [optional]

The path to save the file to.

* @param int $bit [optional]

Bit depth, (default is 24).

* @param int $compression [optional] diff --git a/lib/private/legacy/l10n/string.php b/lib/private/legacy/l10n/string.php index 2c71b95c320f..ee1f58f8279f 100644 --- a/lib/private/legacy/l10n/string.php +++ b/lib/private/legacy/l10n/string.php @@ -61,7 +61,7 @@ public function __toString() { // Replace %n first (won't interfere with vsprintf) $text = \str_replace('%n', $this->count, $text); - if (count($this->parameters) === 0) { + if (\count($this->parameters) === 0) { return (string)$text; } diff --git a/tests/lib/AppFramework/Db/MapperTestUtility.php b/tests/lib/AppFramework/Db/MapperTestUtility.php index 2795f2fd5445..cdf316df6735 100644 --- a/tests/lib/AppFramework/Db/MapperTestUtility.php +++ b/tests/lib/AppFramework/Db/MapperTestUtility.php @@ -58,6 +58,8 @@ protected function setUp(): void { $this->result = $this->createMock(Result::class); $this->iterators = []; $this->fetchAt = 0; + + $this->query->method('execute')->willReturn(true); } /** diff --git a/tests/lib/AvatarTest.php b/tests/lib/AvatarTest.php index 8a73a176b6f3..f0d67874a4fb 100644 --- a/tests/lib/AvatarTest.php +++ b/tests/lib/AvatarTest.php @@ -11,6 +11,7 @@ namespace Test; +use OC\Avatar; use OC\User\User; use OCP\Files\Storage\IStorage; use OCP\Files\StorageNotAvailableException; @@ -21,9 +22,6 @@ class AvatarTest extends \Test\TestCase { /** @var IStorage | \PHPUnit\Framework\MockObject\MockObject */ private $storage; - /** @var \OC\Avatar */ - private $avatar; - /** @var \OC\User\User | \PHPUnit\Framework\MockObject\MockObject $user */ private $user; @@ -31,11 +29,7 @@ public function setUp(): void { parent::setUp(); $this->storage = $this->createMock(IStorage::class); - /** @var \OCP\IL10N | \PHPUnit\Framework\MockObject\MockObject $l */ - $l = $this->createMock(IL10N::class); - $l->method('t')->will($this->returnArgument(0)); $this->user = $this->getMockBuilder(User::class)->disableOriginalConstructor()->getMock(); - $this->avatar = new \OC\Avatar($this->storage, $l, $this->user, $this->createMock(ILogger::class)); } /** @@ -43,16 +37,14 @@ public function setUp(): void { * @param $expectedPath * @param $userId */ - public function testPathBuilding($expectedPath, $userId) { + public function testPathBuilding($expectedPath, $userId): void { $this->user->method('getUID')->willReturn($userId); - $l = $this->createMock(IL10N::class); - $l->method('t')->will($this->returnArgument(0)); - $this->avatar = new \OC\Avatar($this->storage, $l, $this->user, $this->createMock(ILogger::class)); - $path = static::invokePrivate($this->avatar, 'buildAvatarPath'); + $avatar = $this->mockAvatar(); + $path = static::invokePrivate($avatar, 'buildAvatarPath'); $this->assertEquals($expectedPath, $path); } - public function providesUserIds() { + public function providesUserIds(): array { return [ ['avatars/21/23/2f297a57a5a743894a0e4a801fc3', 'admin'], ['avatars/c4/ca/4238a0b923820dcc509a6f75849b', '1'], @@ -61,97 +53,103 @@ public function providesUserIds() { ]; } - public function testGetNoAvatar() { - $this->assertFalse($this->avatar->get()); + public function testGetNoAvatar(): void { + $avatar = $this->mockAvatar(); + $this->assertFalse($avatar->get()); } - public function testGetAvatarSizeMatch() { + public function testGetAvatarSizeMatch(): void { $this->storage->method('file_exists') - ->will($this->returnValueMap([ + ->willReturnMap([ ['avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.jpg', true], ['avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.128.jpg', true], - ])); + ]); $expected = new \OC_Image(\OC::$SERVERROOT . '/tests/data/testavatar.png'); $this->storage->method('file_get_contents')->with('avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.128.jpg')->willReturn($expected->data()); - $this->assertEquals($expected->data(), $this->avatar->get(128)->data()); + $avatar = $this->mockAvatar(); + $this->assertEquals($expected->data(), $avatar->get(128)->data()); } - public function testGetAvatarSizeMinusOne() { + public function testGetAvatarSizeMinusOne(): void { $this->storage->method('file_exists') - ->will($this->returnValueMap([ + ->willReturnMap([ ['avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.jpg', true], - ])); + ]); $expected = new \OC_Image(\OC::$SERVERROOT . '/tests/data/testavatar.png'); $this->storage->method('file_get_contents')->with('avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.jpg')->willReturn($expected->data()); - $this->assertEquals($expected->data(), $this->avatar->get(-1)->data()); + $avatar = $this->mockAvatar(); + $this->assertEquals($expected->data(), $avatar->get(-1)->data()); } - public function testGetAvatarNoSizeMatch() { + public function testGetAvatarNoSizeMatch(): void { $this->storage->method('file_exists') - ->will($this->returnValueMap([ + ->willReturnMap([ ['avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.png', true], ['avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.32.png', false], - ])); + ]); $expected = new \OC_Image(\OC::$SERVERROOT . '/tests/data/testavatar.png'); $expected2 = new \OC_Image(\OC::$SERVERROOT . '/tests/data/testavatar.png'); $expected2->resize(32); $this->storage->method('file_get_contents') - ->will($this->returnCallback( - function ($path) use ($expected, $expected2) { - if ($path === 'avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.png') { - return $expected->data(); - } - if ($path === 'avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.32.png') { - return $expected2->data(); - } - throw new \OCP\Files\NotFoundException(); + ->willReturnCallback(function ($path) use ($expected, $expected2) { + if ($path === 'avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.png') { + return $expected->data(); } - )); + if ($path === 'avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.32.png') { + return $expected2->data(); + } + throw new \OCP\Files\NotFoundException(); + }); $this->storage->expects($this->once()) ->method('file_put_contents') ->with('avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.32.png', $expected2->data()); - $this->assertEquals($expected2->data(), $this->avatar->get(32)->data()); + $avatar = $this->mockAvatar(); + $this->assertEquals($expected2->data(), $avatar->get(32)->data()); } - public function testExistsNo() { - $this->assertFalse($this->avatar->exists()); + public function testExistsNo(): void { + $avatar = $this->mockAvatar(); + $this->assertFalse($avatar->exists()); } - public function testExistsJPG() { + public function testExistsJPG(): void { $this->storage->method('file_exists') - ->will($this->returnValueMap([ + ->willReturnMap([ ['avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.jpg', true], ['avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.png', false], - ])); - $this->assertTrue($this->avatar->exists()); + ]); + $avatar = $this->mockAvatar(); + $this->assertTrue($avatar->exists()); } - public function testExistsPNG() { + public function testExistsPNG(): void { $this->storage->method('file_exists') - ->will($this->returnValueMap([ + ->willReturnMap([ ['avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.jpg', false], ['avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.png', true], - ])); - $this->assertTrue($this->avatar->exists()); + ]); + $avatar = $this->mockAvatar(); + $this->assertTrue($avatar->exists()); } - public function testExistsStorageNotAvailable() { + public function testExistsStorageNotAvailable(): void { $this->storage->method('file_exists') ->willThrowException(new StorageNotAvailableException()); - $this->assertFalse($this->avatar->exists()); + $avatar = $this->mockAvatar(); + $this->assertFalse($avatar->exists()); } - public function testSetAvatar() { + public function testSetAvatar(): void { $this->storage->expects($this->once())->method('rmdir') ->with('avatars/d4/1d/8cd98f00b204e9800998ecf8427e'); @@ -162,6 +160,14 @@ public function testSetAvatar() { // One on remove and once on setting the new avatar $this->user->expects($this->exactly(2))->method('triggerChange'); - $this->avatar->set($image->data()); + $avatar = $this->mockAvatar(); + $avatar->set($image->data()); + } + + private function mockAvatar(): Avatar { + /** @var \OCP\IL10N | \PHPUnit\Framework\MockObject\MockObject $l */ + $l = $this->createMock(IL10N::class); + $l->method('t')->will($this->returnArgument(0)); + return new \OC\Avatar($this->storage, $l, $this->user, $this->createMock(ILogger::class)); } } diff --git a/tests/lib/Files/ViewTest.php b/tests/lib/Files/ViewTest.php index 61b15a2dd5f6..d2c897cdbb13 100644 --- a/tests/lib/Files/ViewTest.php +++ b/tests/lib/Files/ViewTest.php @@ -85,6 +85,7 @@ class ViewTest extends TestCase { /** @var \OC\AllConfig */ private $config; + private bool $shallThrow; protected function setUp(): void { parent::setUp(); diff --git a/tests/lib/ImageTest.php b/tests/lib/ImageTest.php index eb43bcf1b40c..9f38f762db86 100644 --- a/tests/lib/ImageTest.php +++ b/tests/lib/ImageTest.php @@ -9,6 +9,7 @@ namespace Test; use OC; +use OC\Files\Stream\Close; class ImageTest extends \Test\TestCase { public static function tearDownAfterClass(): void { @@ -352,7 +353,7 @@ public function exifDataBigStreamProvider() { public function testExifDataBigStream($targetFile) { $img = new \OC_Image(); - \OC\Files\Stream\Close::registerCallback($targetFile, function () { + Close::registerCallback($targetFile, function () { }); $stream = \fopen("close://{$targetFile}", 'rb'); From b9b720146eab7a0c4d5592a351b793708a1e0bf9 Mon Sep 17 00:00:00 2001 From: Phil Davis Date: Wed, 20 Sep 2023 18:15:02 +0545 Subject: [PATCH 04/42] ci: adjust .drone.star format --- .drone.star | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.star b/.drone.star index ed62428f3f1f..55c09773e58c 100644 --- a/.drone.star +++ b/.drone.star @@ -64,7 +64,7 @@ config = { "phpunit": { "mostDatabases": { "phpVersions": [ - DEFAULT_PHP_VERSION + DEFAULT_PHP_VERSION, ], # Gather coverage for all databases except Oracle "coverage": True, From 75ca904090d52a04b528c0111c654c6ace14a53b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Mon, 25 Sep 2023 14:53:48 +0200 Subject: [PATCH 05/42] feat: fix phpstan & phan --- apps/files_external/3rdparty/composer.json | 36 + apps/files_external/3rdparty/composer.lock | 1221 ++++++++++++++++ .../3rdparty/composer/autoload_classmap.php | 374 +++++ .../3rdparty/composer/autoload_psr4.php | 18 + .../3rdparty/composer/autoload_static.php | 455 ++++++ .../3rdparty/composer/installed.json | 1234 +++++++++++++++++ .../3rdparty/composer/installed.php | 145 ++ .../3rdparty/composer/platform_check.php | 26 + .../src/CredentialSource/AwsNativeSource.php | 360 +++++ .../ExternalAccountCredentials.php | 241 ++++ .../3rdparty/monolog/monolog/CHANGELOG.md | 709 ++++++++++ .../3rdparty/monolog/monolog/README.md | 118 ++ .../3rdparty/monolog/monolog/composer.json | 77 + .../Monolog/Attribute/AsMonologProcessor.php | 38 + .../monolog/src/Monolog/DateTimeImmutable.php | 46 + .../monolog/src/Monolog/ErrorHandler.php | 278 ++++ .../Monolog/Formatter/ChromePHPFormatter.php | 87 ++ .../Monolog/Formatter/ElasticaFormatter.php | 89 ++ .../Formatter/ElasticsearchFormatter.php | 88 ++ .../Monolog/Formatter/FlowdockFormatter.php | 106 ++ .../Monolog/Formatter/FluentdFormatter.php | 92 ++ .../Monolog/Formatter/FormatterInterface.php | 38 + .../Formatter/GelfMessageFormatter.php | 148 ++ .../Formatter/GoogleCloudLoggingFormatter.php | 40 + .../src/Monolog/Formatter/HtmlFormatter.php | 143 ++ .../src/Monolog/Formatter/JsonFormatter.php | 213 +++ .../src/Monolog/Formatter/LineFormatter.php | 244 ++++ .../src/Monolog/Formatter/LogglyFormatter.php | 47 + .../Monolog/Formatter/LogmaticFormatter.php | 64 + .../Monolog/Formatter/LogstashFormatter.php | 102 ++ .../Monolog/Formatter/MongoDBFormatter.php | 160 +++ .../Monolog/Formatter/NormalizerFormatter.php | 305 ++++ .../src/Monolog/Formatter/ScalarFormatter.php | 49 + .../src/Monolog/Formatter/SyslogFormatter.php | 66 + .../Monolog/Formatter/WildfireFormatter.php | 139 ++ .../src/Monolog/Handler/AbstractHandler.php | 102 ++ .../Handler/AbstractProcessingHandler.php | 60 + .../Monolog/Handler/AbstractSyslogHandler.php | 95 ++ .../src/Monolog/Handler/AmqpHandler.php | 159 +++ .../Monolog/Handler/BrowserConsoleHandler.php | 301 ++++ .../src/Monolog/Handler/BufferHandler.php | 165 +++ .../src/Monolog/Handler/ChromePHPHandler.php | 192 +++ .../src/Monolog/Handler/CouchDBHandler.php | 97 ++ .../src/Monolog/Handler/CubeHandler.php | 167 +++ .../monolog/src/Monolog/Handler/Curl/Util.php | 69 + .../Monolog/Handler/DeduplicationHandler.php | 166 +++ .../Handler/DoctrineCouchDBHandler.php | 47 + .../src/Monolog/Handler/DynamoDbHandler.php | 80 ++ .../src/Monolog/Handler/ElasticaHandler.php | 142 ++ .../Monolog/Handler/ElasticsearchHandler.php | 230 +++ .../src/Monolog/Handler/ErrorLogHandler.php | 93 ++ .../Monolog/Handler/FallbackGroupHandler.php | 68 + .../src/Monolog/Handler/FilterHandler.php | 201 +++ .../ActivationStrategyInterface.php | 27 + .../ChannelLevelActivationStrategy.php | 69 + .../ErrorLevelActivationStrategy.php | 42 + .../Monolog/Handler/FingersCrossedHandler.php | 242 ++++ .../src/Monolog/Handler/FirePHPHandler.php | 174 +++ .../src/Monolog/Handler/FleepHookHandler.php | 132 ++ .../src/Monolog/Handler/FlowdockHandler.php | 127 ++ .../Handler/FormattableHandlerInterface.php | 34 + .../Handler/FormattableHandlerTrait.php | 57 + .../src/Monolog/Handler/GelfHandler.php | 58 + .../src/Monolog/Handler/GroupHandler.php | 130 ++ .../monolog/src/Monolog/Handler/Handler.php | 62 + .../src/Monolog/Handler/HandlerInterface.php | 76 + .../src/Monolog/Handler/HandlerWrapper.php | 134 ++ .../src/Monolog/Handler/IFTTTHandler.php | 75 + .../src/Monolog/Handler/InsightOpsHandler.php | 74 + .../src/Monolog/Handler/LogEntriesHandler.php | 68 + .../src/Monolog/Handler/LogglyHandler.php | 155 +++ .../src/Monolog/Handler/LogmaticHandler.php | 98 ++ .../src/Monolog/Handler/MailHandler.php | 91 ++ .../src/Monolog/Handler/MandrillHandler.php | 83 ++ .../src/Monolog/Handler/MongoDBHandler.php | 82 ++ .../Monolog/Handler/NativeMailerHandler.php | 167 +++ .../src/Monolog/Handler/NewRelicHandler.php | 180 +++ .../src/Monolog/Handler/NoopHandler.php | 42 + .../src/Monolog/Handler/NullHandler.php | 56 + .../src/Monolog/Handler/OverflowHandler.php | 139 ++ .../src/Monolog/Handler/PHPConsoleHandler.php | 303 ++++ .../src/Monolog/Handler/ProcessHandler.php | 186 +++ .../Handler/ProcessableHandlerInterface.php | 43 + .../Handler/ProcessableHandlerTrait.php | 70 + .../src/Monolog/Handler/PsrHandler.php | 87 ++ .../src/Monolog/Handler/PushoverHandler.php | 242 ++++ .../src/Monolog/Handler/RedisHandler.php | 94 ++ .../Monolog/Handler/RedisPubSubHandler.php | 65 + .../src/Monolog/Handler/RollbarHandler.php | 133 ++ .../Monolog/Handler/RotatingFileHandler.php | 214 +++ .../src/Monolog/Handler/SamplingHandler.php | 121 ++ .../src/Monolog/Handler/SendGridHandler.php | 100 ++ .../src/Monolog/Handler/Slack/SlackRecord.php | 367 +++++ .../src/Monolog/Handler/SlackHandler.php | 250 ++++ .../Monolog/Handler/SlackWebhookHandler.php | 131 ++ .../src/Monolog/Handler/SocketHandler.php | 429 ++++++ .../src/Monolog/Handler/SqsHandler.php | 61 + .../src/Monolog/Handler/StreamHandler.php | 202 +++ .../Monolog/Handler/SymfonyMailerHandler.php | 109 ++ .../src/Monolog/Handler/SyslogHandler.php | 64 + .../Monolog/Handler/SyslogUdp/UdpSocket.php | 77 + .../src/Monolog/Handler/SyslogUdpHandler.php | 152 ++ .../Monolog/Handler/TelegramBotHandler.php | 279 ++++ .../src/Monolog/Handler/TestHandler.php | 195 +++ .../Handler/WebRequestRecognizerTrait.php | 23 + .../Handler/WhatFailureGroupHandler.php | 80 ++ .../Monolog/Handler/ZendMonitorHandler.php | 90 ++ .../monolog/monolog/src/Monolog/Level.php | 209 +++ .../monolog/monolog/src/Monolog/LogRecord.php | 124 ++ .../monolog/monolog/src/Monolog/Logger.php | 735 ++++++++++ .../Processor/ClosureContextProcessor.php | 51 + .../src/Monolog/Processor/GitProcessor.php | 75 + .../Monolog/Processor/HostnameProcessor.php | 37 + .../Processor/IntrospectionProcessor.php | 122 ++ .../Processor/LoadAverageProcessor.php | 66 + .../Processor/MemoryPeakUsageProcessor.php | 39 + .../src/Monolog/Processor/MemoryProcessor.php | 60 + .../Processor/MemoryUsageProcessor.php | 39 + .../Monolog/Processor/MercurialProcessor.php | 75 + .../Monolog/Processor/ProcessIdProcessor.php | 32 + .../Monolog/Processor/ProcessorInterface.php | 27 + .../Processor/PsrLogMessageProcessor.php | 87 ++ .../src/Monolog/Processor/TagProcessor.php | 63 + .../src/Monolog/Processor/UidProcessor.php | 67 + .../src/Monolog/Processor/WebProcessor.php | 112 ++ .../monolog/monolog/src/Monolog/Registry.php | 133 ++ .../src/Monolog/ResettableInterface.php | 31 + .../monolog/src/Monolog/SignalHandler.php | 113 ++ .../monolog/src/Monolog/Test/TestCase.php | 82 ++ .../monolog/monolog/src/Monolog/Utils.php | 274 ++++ .../3rdparty/psr/cache/README.md | 12 + .../3rdparty/psr/cache/composer.json | 25 + .../3rdparty/psr/cache/src/CacheException.php | 10 + .../psr/cache/src/CacheItemInterface.php | 105 ++ .../psr/cache/src/CacheItemPoolInterface.php | 138 ++ .../3rdparty/psr/log/composer.json | 26 + .../3rdparty/psr/log/src/AbstractLogger.php | 15 + .../psr/log/src/InvalidArgumentException.php | 7 + .../3rdparty/psr/log/src/LogLevel.php | 18 + .../psr/log/src/LoggerAwareInterface.php | 18 + .../3rdparty/psr/log/src/LoggerAwareTrait.php | 26 + .../3rdparty/psr/log/src/LoggerInterface.php | 125 ++ .../3rdparty/psr/log/src/LoggerTrait.php | 142 ++ .../3rdparty/psr/log/src/NullLogger.php | 30 + .../Migrations/Version20220329110116.php | 1 + core/Command/Integrity/SignApp.php | 1 + core/Command/Integrity/SignCore.php | 1 + lib/private/Files/Cache/CacheEntry.php | 14 +- lib/private/Files/FileInfo.php | 22 +- lib/private/Files/Stream/Encryption.php | 7 +- lib/private/Image/BmpToResource.php | 3 +- lib/private/IntegrityCheck/Checker.php | 1 + .../ExcludeFileByNameFilterIterator.php | 5 +- .../ExcludeFoldersByPathFilterIterator.php | 5 +- lib/private/Memcache/ArrayCache.php | 20 +- lib/private/Session/CryptoSessionData.php | 53 +- .../User/Sync/BackendUsersIterator.php | 21 +- lib/private/User/Sync/SeenUsersIterator.php | 16 +- lib/private/User/Sync/UsersIterator.php | 14 +- lib/private/legacy/image.php | 29 +- 160 files changed, 20310 insertions(+), 124 deletions(-) create mode 100644 apps/files_external/3rdparty/composer.json create mode 100644 apps/files_external/3rdparty/composer.lock create mode 100644 apps/files_external/3rdparty/composer/autoload_classmap.php create mode 100644 apps/files_external/3rdparty/composer/autoload_psr4.php create mode 100644 apps/files_external/3rdparty/composer/autoload_static.php create mode 100644 apps/files_external/3rdparty/composer/installed.json create mode 100644 apps/files_external/3rdparty/composer/installed.php create mode 100644 apps/files_external/3rdparty/composer/platform_check.php create mode 100644 apps/files_external/3rdparty/google/auth/src/CredentialSource/AwsNativeSource.php create mode 100644 apps/files_external/3rdparty/google/auth/src/Credentials/ExternalAccountCredentials.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/CHANGELOG.md create mode 100644 apps/files_external/3rdparty/monolog/monolog/README.md create mode 100644 apps/files_external/3rdparty/monolog/monolog/composer.json create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Attribute/AsMonologProcessor.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/DateTimeImmutable.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/ErrorHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/GoogleCloudLoggingFormatter.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LineFormatter.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/SyslogFormatter.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AbstractHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AmqpHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/BufferHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/CubeHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/Curl/Util.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ElasticsearchHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FilterHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/GelfHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/GroupHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/Handler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/HandlerInterface.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/LogglyHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/MailHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/MandrillHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NoopHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NullHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/OverflowHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ProcessHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/PsrHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/PushoverHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RedisHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RollbarHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SamplingHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SendGridHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SlackHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SocketHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SqsHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/StreamHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SyslogHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/TestHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Level.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/LogRecord.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Logger.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/ClosureContextProcessor.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/GitProcessor.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/LoadAverageProcessor.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/TagProcessor.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/UidProcessor.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/WebProcessor.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Registry.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/ResettableInterface.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/SignalHandler.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Test/TestCase.php create mode 100644 apps/files_external/3rdparty/monolog/monolog/src/Monolog/Utils.php create mode 100644 apps/files_external/3rdparty/psr/cache/README.md create mode 100644 apps/files_external/3rdparty/psr/cache/composer.json create mode 100644 apps/files_external/3rdparty/psr/cache/src/CacheException.php create mode 100644 apps/files_external/3rdparty/psr/cache/src/CacheItemInterface.php create mode 100644 apps/files_external/3rdparty/psr/cache/src/CacheItemPoolInterface.php create mode 100644 apps/files_external/3rdparty/psr/log/composer.json create mode 100644 apps/files_external/3rdparty/psr/log/src/AbstractLogger.php create mode 100644 apps/files_external/3rdparty/psr/log/src/InvalidArgumentException.php create mode 100644 apps/files_external/3rdparty/psr/log/src/LogLevel.php create mode 100644 apps/files_external/3rdparty/psr/log/src/LoggerAwareInterface.php create mode 100644 apps/files_external/3rdparty/psr/log/src/LoggerAwareTrait.php create mode 100644 apps/files_external/3rdparty/psr/log/src/LoggerInterface.php create mode 100644 apps/files_external/3rdparty/psr/log/src/LoggerTrait.php create mode 100644 apps/files_external/3rdparty/psr/log/src/NullLogger.php diff --git a/apps/files_external/3rdparty/composer.json b/apps/files_external/3rdparty/composer.json new file mode 100644 index 000000000000..01308429d100 --- /dev/null +++ b/apps/files_external/3rdparty/composer.json @@ -0,0 +1,36 @@ +{ + "name": "files_external/3rdparty", + "description": "3rdparty components for files_external", + "license": "MIT", + "config": { + "vendor-dir": ".", + "optimize-autoloader": true, + "classmap-authoritative": true + }, + "replace": { + "firebase/php-jwt": "^6.8", + "guzzlehttp/guzzle": "^7.7", + "guzzlehttp/psr7": "^2.5", + "phpseclib/phpseclib": ">=3.0.20" + }, + "require": { + "php": ">=8.2", + "icewind/smb": "3.6.0", + "icewind/streams": "0.7.7", + "google/apiclient": "2.15.1" + }, + "require-dev": { + "roave/security-advisories": "dev-latest" + }, + "autoload": { + "files": ["../lib/config.php"] + }, + "scripts": { + "pre-autoload-dump": "Google\\Task\\Composer::cleanup" + }, + "extra": { + "google/apiclient-services": [ + "Drive" + ] + } +} diff --git a/apps/files_external/3rdparty/composer.lock b/apps/files_external/3rdparty/composer.lock new file mode 100644 index 000000000000..503fac4259b3 --- /dev/null +++ b/apps/files_external/3rdparty/composer.lock @@ -0,0 +1,1221 @@ +{ + "_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": "1c79cb2afe5b9acca3c5e215d2482cbe", + "packages": [ + { + "name": "google/apiclient", + "version": "v2.15.1", + "source": { + "type": "git", + "url": "https://github.com/googleapis/google-api-php-client.git", + "reference": "7a95ed29e4b6c6859d2d22300c5455a92e2622ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/google-api-php-client/zipball/7a95ed29e4b6c6859d2d22300c5455a92e2622ad", + "reference": "7a95ed29e4b6c6859d2d22300c5455a92e2622ad", + "shasum": "" + }, + "require": { + "firebase/php-jwt": "~6.0", + "google/apiclient-services": "~0.200", + "google/auth": "^1.28", + "guzzlehttp/guzzle": "~6.5||~7.0", + "guzzlehttp/psr7": "^1.8.4||^2.2.1", + "monolog/monolog": "^2.9||^3.0", + "php": "^7.4|^8.0", + "phpseclib/phpseclib": "^3.0.19" + }, + "require-dev": { + "cache/filesystem-adapter": "^1.1", + "composer/composer": "^1.10.22", + "phpcompatibility/php-compatibility": "^9.2", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "^3.0", + "symfony/css-selector": "~2.1", + "symfony/dom-crawler": "~2.1" + }, + "suggest": { + "cache/filesystem-adapter": "For caching certs and tokens (using Google\\Client::setCache)" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "files": [ + "src/aliases.php" + ], + "psr-4": { + "Google\\": "src/" + }, + "classmap": [ + "src/aliases.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Client library for Google APIs", + "homepage": "http://developers.google.com/api-client-library/php", + "keywords": [ + "google" + ], + "support": { + "issues": "https://github.com/googleapis/google-api-php-client/issues", + "source": "https://github.com/googleapis/google-api-php-client/tree/v2.15.1" + }, + "time": "2023-09-13T21:46:39+00:00" + }, + { + "name": "google/apiclient-services", + "version": "v0.319.0", + "source": { + "type": "git", + "url": "https://github.com/googleapis/google-api-php-client-services.git", + "reference": "9206f4d8cfacbec3c494d68f1758b2c8ca5b179d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/9206f4d8cfacbec3c494d68f1758b2c8ca5b179d", + "reference": "9206f4d8cfacbec3c494d68f1758b2c8ca5b179d", + "shasum": "" + }, + "require": { + "php": "^7.4||^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7||^8.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "autoload.php" + ], + "psr-4": { + "Google\\Service\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Client library for Google APIs", + "homepage": "http://developers.google.com/api-client-library/php", + "keywords": [ + "google" + ], + "support": { + "issues": "https://github.com/googleapis/google-api-php-client-services/issues", + "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.319.0" + }, + "time": "2023-10-08T01:02:14+00:00" + }, + { + "name": "google/auth", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/googleapis/google-auth-library-php.git", + "reference": "22209fddd0c06f3f8e3cb4aade0b352aa00f9888" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/22209fddd0c06f3f8e3cb4aade0b352aa00f9888", + "reference": "22209fddd0c06f3f8e3cb4aade0b352aa00f9888", + "shasum": "" + }, + "require": { + "firebase/php-jwt": "^6.0", + "guzzlehttp/guzzle": "^6.2.1|^7.0", + "guzzlehttp/psr7": "^2.4.5", + "php": "^7.4||^8.0", + "psr/cache": "^1.0||^2.0||^3.0", + "psr/http-message": "^1.1||^2.0" + }, + "require-dev": { + "guzzlehttp/promises": "^2.0", + "kelvinmo/simplejwt": "0.7.1", + "phpseclib/phpseclib": "^3.0", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.0.0", + "sebastian/comparator": ">=1.2.3", + "squizlabs/php_codesniffer": "^3.5" + }, + "suggest": { + "phpseclib/phpseclib": "May be used in place of OpenSSL for signing strings or for token management. Please require version ^2." + }, + "type": "library", + "autoload": { + "psr-4": { + "Google\\Auth\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Google Auth Library for PHP", + "homepage": "http://github.com/google/google-auth-library-php", + "keywords": [ + "Authentication", + "google", + "oauth2" + ], + "support": { + "docs": "https://googleapis.github.io/google-auth-library-php/main/", + "issues": "https://github.com/googleapis/google-auth-library-php/issues", + "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.31.0" + }, + "time": "2023-10-05T20:39:00+00:00" + }, + { + "name": "icewind/smb", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/icewind1991/SMB.git", + "reference": "e0e86b16640f5892dd00408ed50ad18357dac6c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/icewind1991/SMB/zipball/e0e86b16640f5892dd00408ed50ad18357dac6c1", + "reference": "e0e86b16640f5892dd00408ed50ad18357dac6c1", + "shasum": "" + }, + "require": { + "icewind/streams": ">=0.7.3", + "php": ">=7.2" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "phpstan/phpstan": "^0.12.57", + "phpunit/phpunit": "^8.5|^9.3.8", + "psalm/phar": "^4.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Icewind\\SMB\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Robin Appelman", + "email": "icewind@owncloud.com" + } + ], + "description": "php wrapper for smbclient and libsmbclient-php", + "support": { + "issues": "https://github.com/icewind1991/SMB/issues", + "source": "https://github.com/icewind1991/SMB/tree/v3.6.0" + }, + "time": "2023-08-10T13:17:39+00:00" + }, + { + "name": "icewind/streams", + "version": "v0.7.7", + "source": { + "type": "git", + "url": "https://github.com/icewind1991/Streams.git", + "reference": "64200fd7cfcc7f550c3c695c48d8fd8bba97fecb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/icewind1991/Streams/zipball/64200fd7cfcc7f550c3c695c48d8fd8bba97fecb", + "reference": "64200fd7cfcc7f550c3c695c48d8fd8bba97fecb", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^9" + }, + "type": "library", + "autoload": { + "psr-4": { + "Icewind\\Streams\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Robin Appelman", + "email": "icewind@owncloud.com" + } + ], + "description": "A set of generic stream wrappers", + "support": { + "issues": "https://github.com/icewind1991/Streams/issues", + "source": "https://github.com/icewind1991/Streams/tree/v0.7.7" + }, + "time": "2023-03-16T14:52:25+00:00" + }, + { + "name": "monolog/monolog", + "version": "3.4.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "e2392369686d420ca32df3803de28b5d6f76867d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/e2392369686d420ca32df3803de28b5d6f76867d", + "reference": "e2392369686d420ca32df3803de28b5d6f76867d", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-strict-rules": "^1.4", + "phpunit/phpunit": "^10.1", + "predis/predis": "^1.1 || ^2", + "ruflin/elastica": "^7", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.4.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2023-06-21T08:46:11+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], + "packages-dev": [ + { + "name": "roave/security-advisories", + "version": "dev-latest", + "source": { + "type": "git", + "url": "https://github.com/Roave/SecurityAdvisories.git", + "reference": "85afa852eeecec97cec35d987dcfbc602c025620" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/85afa852eeecec97cec35d987dcfbc602c025620", + "reference": "85afa852eeecec97cec35d987dcfbc602c025620", + "shasum": "" + }, + "conflict": { + "3f/pygmentize": "<1.2", + "admidio/admidio": "<4.2.11", + "adodb/adodb-php": "<=5.20.20|>=5.21,<=5.21.3", + "aheinze/cockpit": "<2.2", + "aimeos/aimeos-typo3": "<19.10.12|>=20,<20.10.5", + "akaunting/akaunting": "<2.1.13", + "akeneo/pim-community-dev": "<5.0.119|>=6,<6.0.53", + "alextselegidis/easyappointments": "<1.5", + "alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1", + "amazing/media2click": ">=1,<1.3.3", + "amphp/artax": "<1.0.6|>=2,<2.0.6", + "amphp/http": "<1.0.1", + "amphp/http-client": ">=4,<4.4", + "anchorcms/anchor-cms": "<=0.12.7", + "andreapollastri/cipi": "<=3.1.15", + "andrewhaine/silverstripe-form-capture": ">=0.2,<=0.2.3|>=1,<1.0.2|>=2,<2.2.5", + "apache-solr-for-typo3/solr": "<2.8.3", + "apereo/phpcas": "<1.6", + "api-platform/core": ">=2.2,<2.2.10|>=2.3,<2.3.6|>=2.6,<2.7.10|>=3,<3.0.12|>=3.1,<3.1.3", + "appwrite/server-ce": "<=1.2.1", + "arc/web": "<3", + "area17/twill": "<1.2.5|>=2,<2.5.3", + "artesaos/seotools": "<0.17.2", + "asymmetricrypt/asymmetricrypt": "<9.9.99", + "athlon1600/php-proxy": "<=5.1", + "athlon1600/php-proxy-app": "<=3", + "austintoddj/canvas": "<=3.4.2", + "automad/automad": "<1.8", + "awesome-support/awesome-support": "<=6.0.7", + "aws/aws-sdk-php": ">=3,<3.2.1", + "azuracast/azuracast": "<0.18.3", + "backdrop/backdrop": "<1.24.2", + "backpack/crud": "<3.4.9", + "badaso/core": "<2.7", + "bagisto/bagisto": "<0.1.5", + "barrelstrength/sprout-base-email": "<1.2.7", + "barrelstrength/sprout-forms": "<3.9", + "barryvdh/laravel-translation-manager": "<0.6.2", + "barzahlen/barzahlen-php": "<2.0.1", + "baserproject/basercms": "<4.7.5", + "bassjobsen/bootstrap-3-typeahead": ">4.0.2", + "bigfork/silverstripe-form-capture": ">=3,<3.1.1", + "billz/raspap-webgui": "<=2.9.2", + "bk2k/bootstrap-package": ">=7.1,<7.1.2|>=8,<8.0.8|>=9,<9.0.4|>=9.1,<9.1.3|>=10,<10.0.10|>=11,<11.0.3", + "bmarshall511/wordpress_zero_spam": "<5.2.13", + "bolt/bolt": "<3.7.2", + "bolt/core": "<=4.2", + "bottelet/flarepoint": "<2.2.1", + "brightlocal/phpwhois": "<=4.2.5", + "brotkrueml/codehighlight": "<2.7", + "brotkrueml/schema": "<1.13.1|>=2,<2.5.1", + "brotkrueml/typo3-matomo-integration": "<1.3.2", + "buddypress/buddypress": "<7.2.1", + "bugsnag/bugsnag-laravel": "<2.0.2", + "bytefury/crater": "<6.0.2", + "cachethq/cachet": "<2.5.1", + "cakephp/cakephp": "<3.10.3|>=4,<4.0.10|>=4.1,<4.1.4|>=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10", + "cakephp/database": ">=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10", + "cardgate/magento2": "<2.0.33", + "cardgate/woocommerce": "<=3.1.15", + "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4", + "cartalyst/sentry": "<=2.1.6", + "catfan/medoo": "<1.7.5", + "cecil/cecil": "<7.47.1", + "centreon/centreon": "<22.10.0.0-beta1", + "cesnet/simplesamlphp-module-proxystatistics": "<3.1", + "chriskacerguis/codeigniter-restserver": "<=2.7.1", + "civicrm/civicrm-core": ">=4.2,<4.2.9|>=4.3,<4.3.3", + "cockpit-hq/cockpit": "<=2.6.3", + "codeception/codeception": "<3.1.3|>=4,<4.1.22", + "codeigniter/framework": "<3.1.9", + "codeigniter4/framework": "<4.3.5", + "codeigniter4/shield": "<1.0.0.0-beta4", + "codiad/codiad": "<=2.8.4", + "composer/composer": "<1.10.27|>=2,<2.2.22|>=2.3,<2.6.4", + "concrete5/concrete5": "<=9.2.1", + "concrete5/core": "<8.5.8|>=9,<9.1", + "contao-components/mediaelement": ">=2.14.2,<2.21.1", + "contao/contao": ">=4,<4.4.56|>=4.5,<4.9.40|>=4.10,<4.11.7|>=4.13,<4.13.21|>=5.1,<5.1.4", + "contao/core": ">=2,<3.5.39", + "contao/core-bundle": "<4.9.42|>=4.10,<4.13.28|>=5,<5.1.10", + "contao/listing-bundle": ">=4,<4.4.8", + "contao/managed-edition": "<=1.5", + "cosenary/instagram": "<=2.3", + "craftcms/cms": "<=4.4.14", + "croogo/croogo": "<4", + "cuyz/valinor": "<0.12", + "czproject/git-php": "<4.0.3", + "darylldoyle/safe-svg": "<1.9.10", + "datadog/dd-trace": ">=0.30,<0.30.2", + "datatables/datatables": "<1.10.10", + "david-garcia/phpwhois": "<=4.3.1", + "dbrisinajumi/d2files": "<1", + "dcat/laravel-admin": "<=2.1.3.0-beta", + "derhansen/fe_change_pwd": "<2.0.5|>=3,<3.0.3", + "derhansen/sf_event_mgt": "<4.3.1|>=5,<5.1.1", + "desperado/xml-bundle": "<=0.1.7", + "directmailteam/direct-mail": "<5.2.4", + "doctrine/annotations": ">=1,<1.2.7", + "doctrine/cache": ">=1,<1.3.2|>=1.4,<1.4.2", + "doctrine/common": ">=2,<2.4.3|>=2.5,<2.5.1", + "doctrine/dbal": ">=2,<2.0.8|>=2.1,<2.1.2|>=3,<3.1.4", + "doctrine/doctrine-bundle": "<1.5.2", + "doctrine/doctrine-module": "<=0.7.1", + "doctrine/mongodb-odm": ">=1,<1.0.2", + "doctrine/mongodb-odm-bundle": ">=2,<3.0.1", + "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1|>=2.8.3,<2.8.4", + "dolibarr/dolibarr": "<18", + "dompdf/dompdf": "<2.0.2|==2.0.2", + "drupal/core": "<9.4.14|>=9.5,<9.5.8|>=10,<10.0.8", + "drupal/drupal": ">=6,<6.38|>=7,<7.80|>=8,<8.9.16|>=9,<9.1.12|>=9.2,<9.2.4", + "dweeves/magmi": "<=0.7.24", + "ecodev/newsletter": "<=4", + "ectouch/ectouch": "<=2.7.2", + "elefant/cms": "<2.0.7", + "elgg/elgg": "<3.3.24|>=4,<4.0.5", + "encore/laravel-admin": "<=1.8.19", + "endroid/qr-code-bundle": "<3.4.2", + "enshrined/svg-sanitize": "<0.15", + "erusev/parsedown": "<1.7.2", + "ether/logs": "<3.0.4", + "exceedone/exment": "<4.4.3|>=5,<5.0.3", + "exceedone/laravel-admin": "<2.2.3|==3", + "ezsystems/demobundle": ">=5.4,<5.4.6.1-dev", + "ezsystems/ez-support-tools": ">=2.2,<2.2.3", + "ezsystems/ezdemo-ls-extension": ">=5.4,<5.4.2.1-dev", + "ezsystems/ezfind-ls": ">=5.3,<5.3.6.1-dev|>=5.4,<5.4.11.1-dev|>=2017.12,<2017.12.0.1-dev", + "ezsystems/ezplatform": "<=1.13.6|>=2,<=2.5.24", + "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.29|>=2.3,<2.3.26", + "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2.1|>=5,<5.0.1|>=5.1,<5.1.1", + "ezsystems/ezplatform-graphql": ">=1.0.0.0-RC1-dev,<1.0.13|>=2.0.0.0-beta1,<2.3.12", + "ezsystems/ezplatform-kernel": "<1.2.5.1-dev|>=1.3,<1.3.26", + "ezsystems/ezplatform-rest": ">=1.2,<=1.2.2|>=1.3,<1.3.8", + "ezsystems/ezplatform-richtext": ">=2.3,<2.3.7.1-dev", + "ezsystems/ezplatform-user": ">=1,<1.0.1", + "ezsystems/ezpublish-kernel": "<6.13.8.2-dev|>=7,<7.5.30", + "ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.6,<=2019.03.5.1", + "ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3", + "ezsystems/repository-forms": ">=2.3,<2.3.2.1-dev|>=2.5,<2.5.15", + "ezyang/htmlpurifier": "<4.1.1", + "facade/ignition": "<1.16.15|>=2,<2.4.2|>=2.5,<2.5.2", + "facturascripts/facturascripts": "<=2022.08", + "feehi/cms": "<=2.1.1", + "feehi/feehicms": "<=2.1.1", + "fenom/fenom": "<=2.12.1", + "filegator/filegator": "<7.8", + "firebase/php-jwt": "<6", + "fixpunkt/fp-masterquiz": "<2.2.1|>=3,<3.5.2", + "fixpunkt/fp-newsletter": "<1.1.1|>=2,<2.1.2|>=2.2,<3.2.6", + "flarum/core": "<1.8", + "flarum/framework": "<1.8", + "flarum/mentions": "<1.6.3", + "flarum/sticky": ">=0.1.0.0-beta14,<=0.1.0.0-beta15", + "flarum/tags": "<=0.1.0.0-beta13", + "fluidtypo3/vhs": "<5.1.1", + "fof/byobu": ">=0.3.0.0-beta2,<1.1.7", + "fof/upload": "<1.2.3", + "fooman/tcpdf": "<6.2.22", + "forkcms/forkcms": "<5.11.1", + "fossar/tcpdf-parser": "<6.2.22", + "francoisjacquet/rosariosis": "<11", + "frappant/frp-form-answers": "<3.1.2|>=4,<4.0.2", + "friendsofsymfony/oauth2-php": "<1.3", + "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2", + "friendsofsymfony/user-bundle": ">=1.2,<1.3.5", + "friendsoftypo3/mediace": ">=7.6.2,<7.6.5", + "friendsoftypo3/openid": ">=4.5,<4.5.31|>=4.7,<4.7.16|>=6,<6.0.11|>=6.1,<6.1.6", + "froala/wysiwyg-editor": "<3.2.7|>=4.0.1,<=4.1.1", + "froxlor/froxlor": "<2.1", + "fuel/core": "<1.8.1", + "funadmin/funadmin": "<=3.2|>=3.3.2,<=3.3.3", + "gaoming13/wechat-php-sdk": "<=1.10.2", + "genix/cms": "<=1.1.11", + "getgrav/grav": "<=1.7.42.1", + "getkirby/cms": "<3.5.8.3-dev|>=3.6,<3.6.6.3-dev|>=3.7,<3.7.5.2-dev|>=3.8,<3.8.4.1-dev|>=3.9,<3.9.6", + "getkirby/kirby": "<=2.5.12", + "getkirby/panel": "<2.5.14", + "getkirby/starterkit": "<=3.7.0.2", + "gilacms/gila": "<=1.11.4", + "gleez/cms": "<=1.2|==2", + "globalpayments/php-sdk": "<2", + "gogentooss/samlbase": "<1.2.7", + "google/protobuf": "<3.15", + "gos/web-socket-bundle": "<1.10.4|>=2,<2.6.1|>=3,<3.3", + "gree/jose": "<2.2.1", + "gregwar/rst": "<1.0.3", + "grumpydictator/firefly-iii": "<6", + "gugoan/economizzer": "<=0.9.0.0-beta1", + "guzzlehttp/guzzle": "<6.5.8|>=7,<7.4.5", + "guzzlehttp/psr7": "<1.9.1|>=2,<2.4.5", + "haffner/jh_captcha": "<=2.1.3|>=3,<=3.0.2", + "harvesthq/chosen": "<1.8.7", + "helloxz/imgurl": "<=2.31", + "hhxsv5/laravel-s": "<3.7.36", + "hillelcoren/invoice-ninja": "<5.3.35", + "himiklab/yii2-jqgrid-widget": "<1.0.8", + "hjue/justwriting": "<=1", + "hov/jobfair": "<1.0.13|>=2,<2.0.2", + "httpsoft/http-message": "<1.0.12", + "hyn/multi-tenant": ">=5.6,<5.7.2", + "ibexa/admin-ui": ">=4.2,<4.2.3", + "ibexa/core": ">=4,<4.0.7|>=4.1,<4.1.4|>=4.2,<4.2.3", + "ibexa/graphql": ">=2.5,<2.5.31|>=3.3,<3.3.28|>=4.2,<4.2.3", + "ibexa/post-install": "<=1.0.4", + "ibexa/user": ">=4,<4.4.3", + "icecoder/icecoder": "<=8.1", + "idno/known": "<=1.3.1", + "illuminate/auth": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.10", + "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<=4.1.99999|>=4.2,<=4.2.99999|>=5,<=5.0.99999|>=5.1,<=5.1.99999|>=5.2,<=5.2.99999|>=5.3,<=5.3.99999|>=5.4,<=5.4.99999|>=5.5,<=5.5.49|>=5.6,<=5.6.99999|>=5.7,<=5.7.99999|>=5.8,<=5.8.99999|>=6,<6.18.31|>=7,<7.22.4", + "illuminate/database": "<6.20.26|>=7,<7.30.5|>=8,<8.40", + "illuminate/encryption": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.40|>=5.6,<5.6.15", + "illuminate/view": "<6.20.42|>=7,<7.30.6|>=8,<8.75", + "impresscms/impresscms": "<=1.4.5", + "in2code/femanager": "<5.5.3|>=6,<6.3.4|>=7,<7.2.2", + "in2code/ipandlanguageredirect": "<5.1.2", + "in2code/lux": "<17.6.1|>=18,<24.0.2", + "innologi/typo3-appointments": "<2.0.6", + "intelliants/subrion": "<4.2.2", + "islandora/islandora": ">=2,<2.4.1", + "ivankristianto/phpwhois": "<=4.3", + "jackalope/jackalope-doctrine-dbal": "<1.7.4", + "james-heinrich/getid3": "<1.9.21", + "james-heinrich/phpthumb": "<1.7.12", + "jasig/phpcas": "<1.3.3", + "jcbrand/converse.js": "<3.3.3", + "joomla/application": "<1.0.13", + "joomla/archive": "<1.1.12|>=2,<2.0.1", + "joomla/filesystem": "<1.6.2|>=2,<2.0.1", + "joomla/filter": "<1.4.4|>=2,<2.0.1", + "joomla/framework": ">=2.5.4,<=3.8.12", + "joomla/input": ">=2,<2.0.2", + "joomla/joomla-cms": ">=2.5,<3.9.12", + "joomla/session": "<1.3.1", + "joyqi/hyper-down": "<=2.4.27", + "jsdecena/laracom": "<2.0.9", + "jsmitty12/phpwhois": "<5.1", + "kazist/phpwhois": "<=4.2.6", + "kelvinmo/simplexrd": "<3.1.1", + "kevinpapst/kimai2": "<1.16.7", + "khodakhah/nodcms": "<=3", + "kimai/kimai": "<1.1", + "kitodo/presentation": "<3.2.3|>=3.3,<3.3.4", + "klaviyo/magento2-extension": ">=1,<3", + "knplabs/knp-snappy": "<=1.4.2", + "kohana/core": "<3.3.3", + "krayin/laravel-crm": "<1.2.2", + "kreait/firebase-php": ">=3.2,<3.8.1", + "la-haute-societe/tcpdf": "<6.2.22", + "laminas/laminas-diactoros": "<2.18.1|==2.19|==2.20|==2.21|==2.22|==2.23|>=2.24,<2.24.2|>=2.25,<2.25.2", + "laminas/laminas-form": "<2.17.1|>=3,<3.0.2|>=3.1,<3.1.1", + "laminas/laminas-http": "<2.14.2", + "laravel/fortify": "<1.11.1", + "laravel/framework": "<6.20.44|>=7,<7.30.6|>=8,<8.75", + "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", + "latte/latte": "<2.10.8", + "lavalite/cms": "<=9", + "lcobucci/jwt": ">=3.4,<3.4.6|>=4,<4.0.4|>=4.1,<4.1.5", + "league/commonmark": "<0.18.3", + "league/flysystem": "<1.1.4|>=2,<2.1.1", + "league/oauth2-server": ">=8.3.2,<8.4.2|>=8.5,<8.5.3", + "lexik/jwt-authentication-bundle": "<2.10.7|>=2.11,<2.11.3", + "librenms/librenms": "<2017.08.18", + "liftkit/database": "<2.13.2", + "limesurvey/limesurvey": "<3.27.19", + "livehelperchat/livehelperchat": "<=3.91", + "livewire/livewire": ">2.2.4,<2.2.6", + "lms/routes": "<2.1.1", + "localizationteam/l10nmgr": "<7.4|>=8,<8.7|>=9,<9.2", + "luyadev/yii-helpers": "<1.2.1", + "magento/community-edition": "<=2.4", + "magento/magento1ce": "<1.9.4.3-dev", + "magento/magento1ee": ">=1,<1.14.4.3-dev", + "magento/product-community-edition": ">=2,<2.2.10|>=2.3,<2.3.2.0-patch2", + "maikuolan/phpmussel": ">=1,<1.6", + "mantisbt/mantisbt": "<=2.25.5", + "marcwillmann/turn": "<0.3.3", + "matyhtf/framework": "<3.0.6", + "mautic/core": "<4.3", + "mediawiki/core": ">=1.27,<1.27.6|>=1.29,<1.29.3|>=1.30,<1.30.2|>=1.31,<1.31.9|>=1.32,<1.32.6|>=1.32.99,<1.33.3|>=1.33.99,<1.34.3|>=1.34.99,<1.35", + "mediawiki/matomo": "<2.4.3", + "melisplatform/melis-asset-manager": "<5.0.1", + "melisplatform/melis-cms": "<5.0.1", + "melisplatform/melis-front": "<5.0.1", + "mezzio/mezzio-swoole": "<3.7|>=4,<4.3", + "mgallegos/laravel-jqgrid": "<=1.3", + "microweber/microweber": "<=1.3.4", + "miniorange/miniorange-saml": "<1.4.3", + "mittwald/typo3_forum": "<1.2.1", + "mobiledetect/mobiledetectlib": "<2.8.32", + "modx/revolution": "<=2.8.3.0-patch", + "mojo42/jirafeau": "<4.4", + "mongodb/mongodb": ">=1,<1.9.2", + "monolog/monolog": ">=1.8,<1.12", + "moodle/moodle": "<4.2.0.0-RC2-dev|==4.2", + "movim/moxl": ">=0.8,<=0.10", + "mpdf/mpdf": "<=7.1.7", + "mustache/mustache": ">=2,<2.14.1", + "namshi/jose": "<2.2", + "neoan3-apps/template": "<1.1.1", + "neorazorx/facturascripts": "<2022.04", + "neos/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", + "neos/form": ">=1.2,<4.3.3|>=5,<5.0.9|>=5.1,<5.1.3", + "neos/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.9.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<5.3.10|>=7,<7.0.9|>=7.1,<7.1.7|>=7.2,<7.2.6|>=7.3,<7.3.4|>=8,<8.0.2", + "neos/neos-ui": "<=8.3.3", + "neos/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5", + "netgen/tagsbundle": ">=3.4,<3.4.11|>=4,<4.0.15", + "nette/application": ">=2,<2.0.19|>=2.1,<2.1.13|>=2.2,<2.2.10|>=2.3,<2.3.14|>=2.4,<2.4.16|>=3,<3.0.6", + "nette/nette": ">=2,<2.0.19|>=2.1,<2.1.13", + "nilsteampassnet/teampass": "<3.0.10", + "notrinos/notrinos-erp": "<=0.7", + "noumo/easyii": "<=0.9", + "nukeviet/nukeviet": "<4.5.02", + "nyholm/psr7": "<1.6.1", + "nystudio107/craft-seomatic": "<3.4.12", + "nzo/url-encryptor-bundle": ">=4,<4.3.2|>=5,<5.0.1", + "october/backend": "<1.1.2", + "october/cms": "<1.0.469|==1.0.469|==1.0.471|==1.1.1", + "october/october": "<=3.4.4", + "october/rain": "<1.0.472|>=1.1,<1.1.2", + "october/system": "<1.0.476|>=1.1,<1.1.12|>=2,<2.2.34|>=3,<3.0.66", + "onelogin/php-saml": "<2.10.4", + "oneup/uploader-bundle": "<1.9.3|>=2,<2.1.5", + "open-web-analytics/open-web-analytics": "<1.7.4", + "opencart/opencart": "<=3.0.3.7|>=4,<4.0.2.3-dev", + "openid/php-openid": "<2.3", + "openmage/magento-lts": "<=19.5|>=20,<=20.1", + "opensource-workshop/connect-cms": "<1.7.2|>=2,<2.3.2", + "orchid/platform": ">=9,<9.4.4|>=14.0.0.0-alpha4,<14.5", + "oro/commerce": ">=4.1,<5.0.6", + "oro/crm": ">=1.7,<1.7.4|>=3.1,<4.1.17|>=4.2,<4.2.7", + "oro/platform": ">=1.7,<1.7.4|>=3.1,<3.1.29|>=4.1,<4.1.17|>=4.2,<4.2.8", + "oxid-esales/oxideshop-ce": "<4.5", + "packbackbooks/lti-1-3-php-library": "<5", + "padraic/humbug_get_contents": "<1.1.2", + "pagarme/pagarme-php": "<3", + "pagekit/pagekit": "<=1.0.18", + "paragonie/random_compat": "<2", + "passbolt/passbolt_api": "<2.11", + "paypal/merchant-sdk-php": "<3.12", + "pear/archive_tar": "<1.4.14", + "pear/crypt_gpg": "<1.6.7", + "pear/pear": "<=1.10.1", + "pegasus/google-for-jobs": "<1.5.1|>=2,<2.1.1", + "personnummer/personnummer": "<3.0.2", + "phanan/koel": "<5.1.4", + "php-mod/curl": "<2.3.2", + "phpbb/phpbb": "<3.2.10|>=3.3,<3.3.1", + "phpfastcache/phpfastcache": "<6.1.5|>=7,<7.1.2|>=8,<8.0.7", + "phpmailer/phpmailer": "<6.5", + "phpmussel/phpmussel": ">=1,<1.6", + "phpmyadmin/phpmyadmin": "<5.2.1", + "phpmyfaq/phpmyfaq": "<=3.1.7", + "phpoffice/phpexcel": "<1.8", + "phpoffice/phpspreadsheet": "<1.16", + "phpseclib/phpseclib": "<2.0.31|>=3,<3.0.19", + "phpservermon/phpservermon": "<3.6", + "phpsysinfo/phpsysinfo": "<3.2.5", + "phpunit/phpunit": ">=4.8.19,<4.8.28|>=5,<5.6.3", + "phpwhois/phpwhois": "<=4.2.5", + "phpxmlrpc/extras": "<0.6.1", + "phpxmlrpc/phpxmlrpc": "<4.9.2", + "pi/pi": "<=2.5", + "pimcore/admin-ui-classic-bundle": "<1.1.2", + "pimcore/customer-management-framework-bundle": "<3.4.2", + "pimcore/data-hub": "<1.2.4", + "pimcore/demo": "<10.3", + "pimcore/perspective-editor": "<1.5.1", + "pimcore/pimcore": "<10.6.8", + "pixelfed/pixelfed": "<=0.11.4", + "pocketmine/bedrock-protocol": "<8.0.2", + "pocketmine/pocketmine-mp": "<=4.23|>=5,<5.3.1", + "pressbooks/pressbooks": "<5.18", + "prestashop/autoupgrade": ">=4,<4.10.1", + "prestashop/blockwishlist": ">=2,<2.1.1", + "prestashop/contactform": ">=1.0.1,<4.3", + "prestashop/gamification": "<2.3.2", + "prestashop/prestashop": "<8.1.2", + "prestashop/productcomments": "<5.0.2", + "prestashop/ps_emailsubscription": "<2.6.1", + "prestashop/ps_facetedsearch": "<3.4.1", + "prestashop/ps_linklist": "<3.1", + "privatebin/privatebin": "<1.4", + "processwire/processwire": "<=3.0.200", + "propel/propel": ">=2.0.0.0-alpha1,<=2.0.0.0-alpha7", + "propel/propel1": ">=1,<=1.7.1", + "pterodactyl/panel": "<1.7", + "ptheofan/yii2-statemachine": ">=2.0.0.0-RC1-dev,<=2", + "ptrofimov/beanstalk_console": "<1.7.14", + "pusher/pusher-php-server": "<2.2.1", + "pwweb/laravel-core": "<=0.3.6.0-beta", + "pyrocms/pyrocms": "<=3.9.1", + "rainlab/blog-plugin": "<1.4.1", + "rainlab/debugbar-plugin": "<3.1", + "rainlab/user-plugin": "<=1.4.5", + "rankmath/seo-by-rank-math": "<=1.0.95", + "rap2hpoutre/laravel-log-viewer": "<0.13", + "react/http": ">=0.7,<1.9", + "really-simple-plugins/complianz-gdpr": "<6.4.2", + "remdex/livehelperchat": "<3.99", + "rmccue/requests": ">=1.6,<1.8", + "robrichards/xmlseclibs": "<3.0.4", + "roots/soil": "<4.1", + "rudloff/alltube": "<3.0.3", + "s-cart/core": "<6.9", + "s-cart/s-cart": "<6.9", + "sabberworm/php-css-parser": ">=1,<1.0.1|>=2,<2.0.1|>=3,<3.0.1|>=4,<4.0.1|>=5,<5.0.9|>=5.1,<5.1.3|>=5.2,<5.2.1|>=6,<6.0.2|>=7,<7.0.4|>=8,<8.0.1|>=8.1,<8.1.1|>=8.2,<8.2.1|>=8.3,<8.3.1", + "sabre/dav": "<1.7.11|>=1.8,<1.8.9", + "scheb/two-factor-bundle": "<3.26|>=4,<4.11", + "sensiolabs/connect": "<4.2.3", + "serluck/phpwhois": "<=4.2.6", + "sfroemken/url_redirect": "<=1.2.1", + "sheng/yiicms": "<=1.2", + "shopware/core": "<=6.4.20", + "shopware/platform": "<=6.4.20", + "shopware/production": "<=6.3.5.2", + "shopware/shopware": "<=5.7.17", + "shopware/storefront": "<=6.4.8.1", + "shopxo/shopxo": "<2.2.6", + "showdoc/showdoc": "<2.10.4", + "silverstripe-australia/advancedreports": ">=1,<=2", + "silverstripe/admin": "<1.13.6", + "silverstripe/assets": ">=1,<1.11.1", + "silverstripe/cms": "<4.11.3", + "silverstripe/comments": ">=1.3,<1.9.99|>=2,<2.9.99|>=3,<3.1.1", + "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", + "silverstripe/framework": "<4.13.14|>=5,<5.0.13", + "silverstripe/graphql": "<3.5.2|>=4.0.0.0-alpha1,<4.0.0.0-alpha2|>=4.1.1,<4.1.2|>=4.2.2,<4.2.3", + "silverstripe/hybridsessions": ">=1,<2.4.1|>=2.5,<2.5.1", + "silverstripe/recipe-cms": ">=4.5,<4.5.3", + "silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1", + "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4", + "silverstripe/silverstripe-omnipay": "<2.5.2|>=3,<3.0.2|>=3.1,<3.1.4|>=3.2,<3.2.1", + "silverstripe/subsites": ">=2,<2.6.1", + "silverstripe/taxonomy": ">=1.3,<1.3.1|>=2,<2.0.1", + "silverstripe/userforms": "<3", + "silverstripe/versioned-admin": ">=1,<1.11.1", + "simple-updates/phpwhois": "<=1", + "simplesamlphp/saml2": "<1.15.4|>=2,<2.3.8|>=3,<3.1.4", + "simplesamlphp/simplesamlphp": "<1.18.6", + "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", + "simplesamlphp/simplesamlphp-module-openid": "<1", + "simplesamlphp/simplesamlphp-module-openidprovider": "<0.9", + "simplito/elliptic-php": "<1.0.6", + "sitegeist/fluid-components": "<3.5", + "sjbr/sr-freecap": "<2.4.6|>=2.5,<2.5.3", + "slim/psr7": "<1.4.1|>=1.5,<1.5.1|>=1.6,<1.6.1", + "slim/slim": "<2.6", + "slub/slub-events": "<3.0.3", + "smarty/smarty": "<3.1.48|>=4,<4.3.1", + "snipe/snipe-it": "<=6.0.14", + "socalnick/scn-social-auth": "<1.15.2", + "socialiteproviders/steam": "<1.1", + "spatie/browsershot": "<3.57.4", + "spipu/html2pdf": "<5.2.8", + "spoon/library": "<1.4.1", + "spoonity/tcpdf": "<6.2.22", + "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", + "ssddanbrown/bookstack": "<22.02.3", + "statamic/cms": "<4.10", + "stormpath/sdk": "<9.9.99", + "studio-42/elfinder": "<2.1.62", + "subhh/libconnect": "<7.0.8|>=8,<8.1", + "sukohi/surpass": "<1", + "sulu/sulu": "<1.6.44|>=2,<2.2.18|>=2.3,<2.3.8|==2.4.0.0-RC1|>=2.5,<2.5.10", + "sumocoders/framework-user-bundle": "<1.4", + "swag/paypal": "<5.4.4", + "swiftmailer/swiftmailer": ">=4,<5.4.5", + "sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2", + "sylius/grid": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", + "sylius/grid-bundle": "<1.10.1", + "sylius/paypal-plugin": ">=1,<1.2.4|>=1.3,<1.3.1", + "sylius/resource-bundle": "<1.3.14|>=1.4,<1.4.7|>=1.5,<1.5.2|>=1.6,<1.6.4", + "sylius/sylius": "<1.9.10|>=1.10,<1.10.11|>=1.11,<1.11.2", + "symbiote/silverstripe-multivaluefield": ">=3,<3.0.99", + "symbiote/silverstripe-queuedjobs": ">=3,<3.0.2|>=3.1,<3.1.4|>=4,<4.0.7|>=4.1,<4.1.2|>=4.2,<4.2.4|>=4.3,<4.3.3|>=4.4,<4.4.3|>=4.5,<4.5.1|>=4.6,<4.6.4", + "symbiote/silverstripe-seed": "<6.0.3", + "symbiote/silverstripe-versionedfiles": "<=2.0.3", + "symfont/process": ">=0", + "symfony/cache": ">=3.1,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", + "symfony/dependency-injection": ">=2,<2.0.17|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/error-handler": ">=4.4,<4.4.4|>=5,<5.0.4", + "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1", + "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=5.3.14,<=5.3.14|>=5.4.3,<=5.4.3|>=6.0.3,<=6.0.3", + "symfony/http-foundation": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7", + "symfony/http-kernel": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", + "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", + "symfony/maker-bundle": ">=1.27,<1.29.2|>=1.30,<1.31.1", + "symfony/mime": ">=4.3,<4.3.8", + "symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/polyfill": ">=1,<1.10", + "symfony/polyfill-php55": ">=1,<1.10", + "symfony/proxy-manager-bridge": ">=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/routing": ">=2,<2.0.19", + "symfony/security": ">=2,<2.7.51|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.8", + "symfony/security-bundle": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", + "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.9", + "symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", + "symfony/security-guard": ">=2.8,<3.4.48|>=4,<4.4.23|>=5,<5.2.8", + "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.3.2", + "symfony/serializer": ">=2,<2.0.11|>=4.1,<4.4.35|>=5,<5.3.12", + "symfony/symfony": "<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", + "symfony/translation": ">=2,<2.0.17", + "symfony/ux-autocomplete": "<2.11.2", + "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3", + "symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8", + "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", + "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7", + "t3/dce": "<0.11.5|>=2.2,<2.6.2", + "t3g/svg-sanitizer": "<1.0.3", + "tastyigniter/tastyigniter": "<3.3", + "tcg/voyager": "<=1.4", + "tecnickcom/tcpdf": "<6.2.22", + "terminal42/contao-tablelookupwizard": "<3.3.5", + "thelia/backoffice-default-template": ">=2.1,<2.1.2", + "thelia/thelia": ">=2.1,<2.1.3", + "theonedemon/phpwhois": "<=4.2.5", + "thinkcmf/thinkcmf": "<=5.1.7", + "thorsten/phpmyfaq": "<3.2.0.0-beta2", + "tikiwiki/tiki-manager": "<=17.1", + "tinymce/tinymce": "<5.10.7|>=6,<6.3.1", + "tinymighty/wiki-seo": "<1.2.2", + "titon/framework": "<9.9.99", + "tobiasbg/tablepress": "<=2.0.0.0-RC1", + "topthink/framework": "<6.0.14", + "topthink/think": "<=6.1.1", + "topthink/thinkphp": "<=3.2.3", + "tpwd/ke_search": "<4.0.3|>=4.1,<4.6.6|>=5,<5.0.2", + "tribalsystems/zenario": "<=9.4.59197", + "truckersmp/phpwhois": "<=4.3.1", + "ttskch/pagination-service-provider": "<1", + "twig/twig": "<1.44.7|>=2,<2.15.3|>=3,<3.4.3", + "typo3/cms": "<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2", + "typo3/cms-backend": ">=7,<=7.6.50|>=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1", + "typo3/cms-core": "<8.7.51|>=9,<9.5.42|>=10,<10.4.39|>=11,<11.5.30|>=12,<12.4.4", + "typo3/cms-extbase": "<6.2.24|>=7,<7.6.8|==8.1.1", + "typo3/cms-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1", + "typo3/cms-rte-ckeditor": ">=9.5,<9.5.42|>=10,<10.4.39|>=11,<11.5.30", + "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", + "typo3/html-sanitizer": ">=1,<1.5.1|>=2,<2.1.2", + "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.3.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<3.3.23|>=4,<4.0.17|>=4.1,<4.1.16|>=4.2,<4.2.12|>=4.3,<4.3.3", + "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1", + "typo3/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5", + "typo3fluid/fluid": ">=2,<2.0.8|>=2.1,<2.1.7|>=2.2,<2.2.4|>=2.3,<2.3.7|>=2.4,<2.4.4|>=2.5,<2.5.11|>=2.6,<2.6.10", + "ua-parser/uap-php": "<3.8", + "uasoft-indonesia/badaso": "<=2.9.7", + "unisharp/laravel-filemanager": "<=2.5.1", + "userfrosting/userfrosting": ">=0.3.1,<4.6.3", + "usmanhalalit/pixie": "<1.0.3|>=2,<2.0.2", + "uvdesk/community-skeleton": "<=1.1.1", + "vanilla/safecurl": "<0.9.2", + "verot/class.upload.php": "<=1.0.3|>=2,<=2.0.4", + "vova07/yii2-fileapi-widget": "<0.1.9", + "vrana/adminer": "<4.8.1", + "waldhacker/hcaptcha": "<2.1.2", + "wallabag/tcpdf": "<6.2.22", + "wallabag/wallabag": "<2.6.7", + "wanglelecc/laracms": "<=1.0.3", + "web-auth/webauthn-framework": ">=3.3,<3.3.4", + "webbuilders-group/silverstripe-kapost-bridge": "<0.4", + "webcoast/deferred-image-processing": "<1.0.2", + "webklex/laravel-imap": "<5.3", + "webklex/php-imap": "<5.3", + "webpa/webpa": "<3.1.2", + "wikibase/wikibase": "<=1.39.3", + "wikimedia/parsoid": "<0.12.2", + "willdurand/js-translation-bundle": "<2.1.1", + "wintercms/winter": "<1.2.3", + "woocommerce/woocommerce": "<6.6", + "wp-cli/wp-cli": "<2.5", + "wp-graphql/wp-graphql": "<=1.14.5", + "wpanel/wpanel4-cms": "<=4.3.1", + "wpcloud/wp-stateless": "<3.2", + "wwbn/avideo": "<=12.4", + "xataface/xataface": "<3", + "xpressengine/xpressengine": "<3.0.15", + "yeswiki/yeswiki": "<4.1", + "yetiforce/yetiforce-crm": "<=6.4", + "yidashi/yii2cmf": "<=2", + "yii2mod/yii2-cms": "<1.9.2", + "yiisoft/yii": "<1.1.27", + "yiisoft/yii2": "<2.0.38", + "yiisoft/yii2-bootstrap": "<2.0.4", + "yiisoft/yii2-dev": "<2.0.43", + "yiisoft/yii2-elasticsearch": "<2.0.5", + "yiisoft/yii2-gii": "<=2.2.4", + "yiisoft/yii2-jui": "<2.0.4", + "yiisoft/yii2-redis": "<2.0.8", + "yikesinc/yikes-inc-easy-mailchimp-extender": "<6.8.6", + "yoast-seo-for-typo3/yoast_seo": "<7.2.3", + "yourls/yourls": "<=1.8.2", + "zencart/zencart": "<=1.5.7.0-beta", + "zendesk/zendesk_api_client_php": "<2.2.11", + "zendframework/zend-cache": ">=2.4,<2.4.8|>=2.5,<2.5.3", + "zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2", + "zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2", + "zendframework/zend-db": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.10|>=2.3,<2.3.5", + "zendframework/zend-developer-tools": ">=1.2.2,<1.2.3", + "zendframework/zend-diactoros": "<1.8.4", + "zendframework/zend-feed": "<2.10.3", + "zendframework/zend-form": ">=2,<2.2.7|>=2.3,<2.3.1", + "zendframework/zend-http": "<2.8.1", + "zendframework/zend-json": ">=2.1,<2.1.6|>=2.2,<2.2.6", + "zendframework/zend-ldap": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.8|>=2.3,<2.3.3", + "zendframework/zend-mail": ">=2,<2.4.11|>=2.5,<2.7.2", + "zendframework/zend-navigation": ">=2,<2.2.7|>=2.3,<2.3.1", + "zendframework/zend-session": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.9|>=2.3,<2.3.4", + "zendframework/zend-validator": ">=2.3,<2.3.6", + "zendframework/zend-view": ">=2,<2.2.7|>=2.3,<2.3.1", + "zendframework/zend-xmlrpc": ">=2.1,<2.1.6|>=2.2,<2.2.6", + "zendframework/zendframework": "<=3", + "zendframework/zendframework1": "<1.12.20", + "zendframework/zendopenid": "<2.0.2", + "zendframework/zendrest": "<2.0.2", + "zendframework/zendservice-amazon": "<2.0.3", + "zendframework/zendservice-api": "<1", + "zendframework/zendservice-audioscrobbler": "<2.0.2", + "zendframework/zendservice-nirvanix": "<2.0.2", + "zendframework/zendservice-slideshare": "<2.0.2", + "zendframework/zendservice-technorati": "<2.0.2", + "zendframework/zendservice-windowsazure": "<2.0.2", + "zendframework/zendxml": "<1.0.1", + "zenstruck/collection": "<0.2.1", + "zetacomponents/mail": "<1.8.2", + "zf-commons/zfc-user": "<1.2.2", + "zfcampus/zf-apigility-doctrine": ">=1,<1.0.3", + "zfr/zfr-oauth2-server-module": "<0.1.2", + "zoujingli/thinkadmin": "<6.0.22" + }, + "default-branch": true, + "type": "metapackage", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "role": "maintainer" + }, + { + "name": "Ilya Tribusean", + "email": "slash3b@gmail.com", + "role": "maintainer" + } + ], + "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", + "keywords": [ + "dev" + ], + "support": { + "issues": "https://github.com/Roave/SecurityAdvisories/issues", + "source": "https://github.com/Roave/SecurityAdvisories/tree/latest" + }, + "funding": [ + { + "url": "https://github.com/Ocramius", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/roave/security-advisories", + "type": "tidelift" + } + ], + "time": "2023-10-06T19:04:00+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "roave/security-advisories": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.2" + }, + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/apps/files_external/3rdparty/composer/autoload_classmap.php b/apps/files_external/3rdparty/composer/autoload_classmap.php new file mode 100644 index 000000000000..7f1c2cabd02e --- /dev/null +++ b/apps/files_external/3rdparty/composer/autoload_classmap.php @@ -0,0 +1,374 @@ + $vendorDir . '/composer/InstalledVersions.php', + 'Google\\AccessToken\\Revoke' => $vendorDir . '/google/apiclient/src/AccessToken/Revoke.php', + 'Google\\AccessToken\\Verify' => $vendorDir . '/google/apiclient/src/AccessToken/Verify.php', + 'Google\\AuthHandler\\AuthHandlerFactory' => $vendorDir . '/google/apiclient/src/AuthHandler/AuthHandlerFactory.php', + 'Google\\AuthHandler\\Guzzle6AuthHandler' => $vendorDir . '/google/apiclient/src/AuthHandler/Guzzle6AuthHandler.php', + 'Google\\AuthHandler\\Guzzle7AuthHandler' => $vendorDir . '/google/apiclient/src/AuthHandler/Guzzle7AuthHandler.php', + 'Google\\Auth\\AccessToken' => $vendorDir . '/google/auth/src/AccessToken.php', + 'Google\\Auth\\ApplicationDefaultCredentials' => $vendorDir . '/google/auth/src/ApplicationDefaultCredentials.php', + 'Google\\Auth\\CacheTrait' => $vendorDir . '/google/auth/src/CacheTrait.php', + 'Google\\Auth\\Cache\\InvalidArgumentException' => $vendorDir . '/google/auth/src/Cache/InvalidArgumentException.php', + 'Google\\Auth\\Cache\\Item' => $vendorDir . '/google/auth/src/Cache/Item.php', + 'Google\\Auth\\Cache\\MemoryCacheItemPool' => $vendorDir . '/google/auth/src/Cache/MemoryCacheItemPool.php', + 'Google\\Auth\\Cache\\SysVCacheItemPool' => $vendorDir . '/google/auth/src/Cache/SysVCacheItemPool.php', + 'Google\\Auth\\Cache\\TypedItem' => $vendorDir . '/google/auth/src/Cache/TypedItem.php', + 'Google\\Auth\\CredentialSource\\AwsNativeSource' => $vendorDir . '/google/auth/src/CredentialSource/AwsNativeSource.php', + 'Google\\Auth\\CredentialSource\\FileSource' => $vendorDir . '/google/auth/src/CredentialSource/FileSource.php', + 'Google\\Auth\\CredentialSource\\UrlSource' => $vendorDir . '/google/auth/src/CredentialSource/UrlSource.php', + 'Google\\Auth\\CredentialsLoader' => $vendorDir . '/google/auth/src/CredentialsLoader.php', + 'Google\\Auth\\Credentials\\AppIdentityCredentials' => $vendorDir . '/google/auth/src/Credentials/AppIdentityCredentials.php', + 'Google\\Auth\\Credentials\\ExternalAccountCredentials' => $vendorDir . '/google/auth/src/Credentials/ExternalAccountCredentials.php', + 'Google\\Auth\\Credentials\\GCECredentials' => $vendorDir . '/google/auth/src/Credentials/GCECredentials.php', + 'Google\\Auth\\Credentials\\IAMCredentials' => $vendorDir . '/google/auth/src/Credentials/IAMCredentials.php', + 'Google\\Auth\\Credentials\\ImpersonatedServiceAccountCredentials' => $vendorDir . '/google/auth/src/Credentials/ImpersonatedServiceAccountCredentials.php', + 'Google\\Auth\\Credentials\\InsecureCredentials' => $vendorDir . '/google/auth/src/Credentials/InsecureCredentials.php', + 'Google\\Auth\\Credentials\\ServiceAccountCredentials' => $vendorDir . '/google/auth/src/Credentials/ServiceAccountCredentials.php', + 'Google\\Auth\\Credentials\\ServiceAccountJwtAccessCredentials' => $vendorDir . '/google/auth/src/Credentials/ServiceAccountJwtAccessCredentials.php', + 'Google\\Auth\\Credentials\\UserRefreshCredentials' => $vendorDir . '/google/auth/src/Credentials/UserRefreshCredentials.php', + 'Google\\Auth\\ExternalAccountCredentialSourceInterface' => $vendorDir . '/google/auth/src/ExternalAccountCredentialSourceInterface.php', + 'Google\\Auth\\FetchAuthTokenCache' => $vendorDir . '/google/auth/src/FetchAuthTokenCache.php', + 'Google\\Auth\\FetchAuthTokenInterface' => $vendorDir . '/google/auth/src/FetchAuthTokenInterface.php', + 'Google\\Auth\\GCECache' => $vendorDir . '/google/auth/src/GCECache.php', + 'Google\\Auth\\GetQuotaProjectInterface' => $vendorDir . '/google/auth/src/GetQuotaProjectInterface.php', + 'Google\\Auth\\HttpHandler\\Guzzle6HttpHandler' => $vendorDir . '/google/auth/src/HttpHandler/Guzzle6HttpHandler.php', + 'Google\\Auth\\HttpHandler\\Guzzle7HttpHandler' => $vendorDir . '/google/auth/src/HttpHandler/Guzzle7HttpHandler.php', + 'Google\\Auth\\HttpHandler\\HttpClientCache' => $vendorDir . '/google/auth/src/HttpHandler/HttpClientCache.php', + 'Google\\Auth\\HttpHandler\\HttpHandlerFactory' => $vendorDir . '/google/auth/src/HttpHandler/HttpHandlerFactory.php', + 'Google\\Auth\\Iam' => $vendorDir . '/google/auth/src/Iam.php', + 'Google\\Auth\\IamSignerTrait' => $vendorDir . '/google/auth/src/IamSignerTrait.php', + 'Google\\Auth\\Middleware\\AuthTokenMiddleware' => $vendorDir . '/google/auth/src/Middleware/AuthTokenMiddleware.php', + 'Google\\Auth\\Middleware\\ProxyAuthTokenMiddleware' => $vendorDir . '/google/auth/src/Middleware/ProxyAuthTokenMiddleware.php', + 'Google\\Auth\\Middleware\\ScopedAccessTokenMiddleware' => $vendorDir . '/google/auth/src/Middleware/ScopedAccessTokenMiddleware.php', + 'Google\\Auth\\Middleware\\SimpleMiddleware' => $vendorDir . '/google/auth/src/Middleware/SimpleMiddleware.php', + 'Google\\Auth\\OAuth2' => $vendorDir . '/google/auth/src/OAuth2.php', + 'Google\\Auth\\ProjectIdProviderInterface' => $vendorDir . '/google/auth/src/ProjectIdProviderInterface.php', + 'Google\\Auth\\ServiceAccountSignerTrait' => $vendorDir . '/google/auth/src/ServiceAccountSignerTrait.php', + 'Google\\Auth\\SignBlobInterface' => $vendorDir . '/google/auth/src/SignBlobInterface.php', + 'Google\\Auth\\UpdateMetadataInterface' => $vendorDir . '/google/auth/src/UpdateMetadataInterface.php', + 'Google\\Auth\\UpdateMetadataTrait' => $vendorDir . '/google/auth/src/UpdateMetadataTrait.php', + 'Google\\Client' => $vendorDir . '/google/apiclient/src/Client.php', + 'Google\\Collection' => $vendorDir . '/google/apiclient/src/Collection.php', + 'Google\\Exception' => $vendorDir . '/google/apiclient/src/Exception.php', + 'Google\\Http\\Batch' => $vendorDir . '/google/apiclient/src/Http/Batch.php', + 'Google\\Http\\MediaFileUpload' => $vendorDir . '/google/apiclient/src/Http/MediaFileUpload.php', + 'Google\\Http\\REST' => $vendorDir . '/google/apiclient/src/Http/REST.php', + 'Google\\Model' => $vendorDir . '/google/apiclient/src/Model.php', + 'Google\\Service' => $vendorDir . '/google/apiclient/src/Service.php', + 'Google\\Service\\Drive' => $vendorDir . '/google/apiclient-services/src/Drive.php', + 'Google\\Service\\Drive\\About' => $vendorDir . '/google/apiclient-services/src/Drive/About.php', + 'Google\\Service\\Drive\\AboutDriveThemes' => $vendorDir . '/google/apiclient-services/src/Drive/AboutDriveThemes.php', + 'Google\\Service\\Drive\\AboutStorageQuota' => $vendorDir . '/google/apiclient-services/src/Drive/AboutStorageQuota.php', + 'Google\\Service\\Drive\\AboutTeamDriveThemes' => $vendorDir . '/google/apiclient-services/src/Drive/AboutTeamDriveThemes.php', + 'Google\\Service\\Drive\\Change' => $vendorDir . '/google/apiclient-services/src/Drive/Change.php', + 'Google\\Service\\Drive\\ChangeList' => $vendorDir . '/google/apiclient-services/src/Drive/ChangeList.php', + 'Google\\Service\\Drive\\Channel' => $vendorDir . '/google/apiclient-services/src/Drive/Channel.php', + 'Google\\Service\\Drive\\Comment' => $vendorDir . '/google/apiclient-services/src/Drive/Comment.php', + 'Google\\Service\\Drive\\CommentList' => $vendorDir . '/google/apiclient-services/src/Drive/CommentList.php', + 'Google\\Service\\Drive\\CommentQuotedFileContent' => $vendorDir . '/google/apiclient-services/src/Drive/CommentQuotedFileContent.php', + 'Google\\Service\\Drive\\ContentRestriction' => $vendorDir . '/google/apiclient-services/src/Drive/ContentRestriction.php', + 'Google\\Service\\Drive\\Drive' => $vendorDir . '/google/apiclient-services/src/Drive/Drive.php', + 'Google\\Service\\Drive\\DriveBackgroundImageFile' => $vendorDir . '/google/apiclient-services/src/Drive/DriveBackgroundImageFile.php', + 'Google\\Service\\Drive\\DriveCapabilities' => $vendorDir . '/google/apiclient-services/src/Drive/DriveCapabilities.php', + 'Google\\Service\\Drive\\DriveFile' => $vendorDir . '/google/apiclient-services/src/Drive/DriveFile.php', + 'Google\\Service\\Drive\\DriveFileCapabilities' => $vendorDir . '/google/apiclient-services/src/Drive/DriveFileCapabilities.php', + 'Google\\Service\\Drive\\DriveFileContentHints' => $vendorDir . '/google/apiclient-services/src/Drive/DriveFileContentHints.php', + 'Google\\Service\\Drive\\DriveFileContentHintsThumbnail' => $vendorDir . '/google/apiclient-services/src/Drive/DriveFileContentHintsThumbnail.php', + 'Google\\Service\\Drive\\DriveFileImageMediaMetadata' => $vendorDir . '/google/apiclient-services/src/Drive/DriveFileImageMediaMetadata.php', + 'Google\\Service\\Drive\\DriveFileImageMediaMetadataLocation' => $vendorDir . '/google/apiclient-services/src/Drive/DriveFileImageMediaMetadataLocation.php', + 'Google\\Service\\Drive\\DriveFileLabelInfo' => $vendorDir . '/google/apiclient-services/src/Drive/DriveFileLabelInfo.php', + 'Google\\Service\\Drive\\DriveFileLinkShareMetadata' => $vendorDir . '/google/apiclient-services/src/Drive/DriveFileLinkShareMetadata.php', + 'Google\\Service\\Drive\\DriveFileShortcutDetails' => $vendorDir . '/google/apiclient-services/src/Drive/DriveFileShortcutDetails.php', + 'Google\\Service\\Drive\\DriveFileVideoMediaMetadata' => $vendorDir . '/google/apiclient-services/src/Drive/DriveFileVideoMediaMetadata.php', + 'Google\\Service\\Drive\\DriveList' => $vendorDir . '/google/apiclient-services/src/Drive/DriveList.php', + 'Google\\Service\\Drive\\DriveRestrictions' => $vendorDir . '/google/apiclient-services/src/Drive/DriveRestrictions.php', + 'Google\\Service\\Drive\\FileList' => $vendorDir . '/google/apiclient-services/src/Drive/FileList.php', + 'Google\\Service\\Drive\\GeneratedIds' => $vendorDir . '/google/apiclient-services/src/Drive/GeneratedIds.php', + 'Google\\Service\\Drive\\Label' => $vendorDir . '/google/apiclient-services/src/Drive/Label.php', + 'Google\\Service\\Drive\\LabelField' => $vendorDir . '/google/apiclient-services/src/Drive/LabelField.php', + 'Google\\Service\\Drive\\LabelFieldModification' => $vendorDir . '/google/apiclient-services/src/Drive/LabelFieldModification.php', + 'Google\\Service\\Drive\\LabelList' => $vendorDir . '/google/apiclient-services/src/Drive/LabelList.php', + 'Google\\Service\\Drive\\LabelModification' => $vendorDir . '/google/apiclient-services/src/Drive/LabelModification.php', + 'Google\\Service\\Drive\\ModifyLabelsRequest' => $vendorDir . '/google/apiclient-services/src/Drive/ModifyLabelsRequest.php', + 'Google\\Service\\Drive\\ModifyLabelsResponse' => $vendorDir . '/google/apiclient-services/src/Drive/ModifyLabelsResponse.php', + 'Google\\Service\\Drive\\Permission' => $vendorDir . '/google/apiclient-services/src/Drive/Permission.php', + 'Google\\Service\\Drive\\PermissionList' => $vendorDir . '/google/apiclient-services/src/Drive/PermissionList.php', + 'Google\\Service\\Drive\\PermissionPermissionDetails' => $vendorDir . '/google/apiclient-services/src/Drive/PermissionPermissionDetails.php', + 'Google\\Service\\Drive\\PermissionTeamDrivePermissionDetails' => $vendorDir . '/google/apiclient-services/src/Drive/PermissionTeamDrivePermissionDetails.php', + 'Google\\Service\\Drive\\Reply' => $vendorDir . '/google/apiclient-services/src/Drive/Reply.php', + 'Google\\Service\\Drive\\ReplyList' => $vendorDir . '/google/apiclient-services/src/Drive/ReplyList.php', + 'Google\\Service\\Drive\\Resource\\About' => $vendorDir . '/google/apiclient-services/src/Drive/Resource/About.php', + 'Google\\Service\\Drive\\Resource\\Changes' => $vendorDir . '/google/apiclient-services/src/Drive/Resource/Changes.php', + 'Google\\Service\\Drive\\Resource\\Channels' => $vendorDir . '/google/apiclient-services/src/Drive/Resource/Channels.php', + 'Google\\Service\\Drive\\Resource\\Comments' => $vendorDir . '/google/apiclient-services/src/Drive/Resource/Comments.php', + 'Google\\Service\\Drive\\Resource\\Drives' => $vendorDir . '/google/apiclient-services/src/Drive/Resource/Drives.php', + 'Google\\Service\\Drive\\Resource\\Files' => $vendorDir . '/google/apiclient-services/src/Drive/Resource/Files.php', + 'Google\\Service\\Drive\\Resource\\Permissions' => $vendorDir . '/google/apiclient-services/src/Drive/Resource/Permissions.php', + 'Google\\Service\\Drive\\Resource\\Replies' => $vendorDir . '/google/apiclient-services/src/Drive/Resource/Replies.php', + 'Google\\Service\\Drive\\Resource\\Revisions' => $vendorDir . '/google/apiclient-services/src/Drive/Resource/Revisions.php', + 'Google\\Service\\Drive\\Resource\\Teamdrives' => $vendorDir . '/google/apiclient-services/src/Drive/Resource/Teamdrives.php', + 'Google\\Service\\Drive\\Revision' => $vendorDir . '/google/apiclient-services/src/Drive/Revision.php', + 'Google\\Service\\Drive\\RevisionList' => $vendorDir . '/google/apiclient-services/src/Drive/RevisionList.php', + 'Google\\Service\\Drive\\StartPageToken' => $vendorDir . '/google/apiclient-services/src/Drive/StartPageToken.php', + 'Google\\Service\\Drive\\TeamDrive' => $vendorDir . '/google/apiclient-services/src/Drive/TeamDrive.php', + 'Google\\Service\\Drive\\TeamDriveBackgroundImageFile' => $vendorDir . '/google/apiclient-services/src/Drive/TeamDriveBackgroundImageFile.php', + 'Google\\Service\\Drive\\TeamDriveCapabilities' => $vendorDir . '/google/apiclient-services/src/Drive/TeamDriveCapabilities.php', + 'Google\\Service\\Drive\\TeamDriveList' => $vendorDir . '/google/apiclient-services/src/Drive/TeamDriveList.php', + 'Google\\Service\\Drive\\TeamDriveRestrictions' => $vendorDir . '/google/apiclient-services/src/Drive/TeamDriveRestrictions.php', + 'Google\\Service\\Drive\\User' => $vendorDir . '/google/apiclient-services/src/Drive/User.php', + 'Google\\Service\\Exception' => $vendorDir . '/google/apiclient/src/Service/Exception.php', + 'Google\\Service\\Resource' => $vendorDir . '/google/apiclient/src/Service/Resource.php', + 'Google\\Task\\Composer' => $vendorDir . '/google/apiclient/src/Task/Composer.php', + 'Google\\Task\\Exception' => $vendorDir . '/google/apiclient/src/Task/Exception.php', + 'Google\\Task\\Retryable' => $vendorDir . '/google/apiclient/src/Task/Retryable.php', + 'Google\\Task\\Runner' => $vendorDir . '/google/apiclient/src/Task/Runner.php', + 'Google\\Utils\\UriTemplate' => $vendorDir . '/google/apiclient/src/Utils/UriTemplate.php', + 'Google_AccessToken_Revoke' => $vendorDir . '/google/apiclient/src/aliases.php', + 'Google_AccessToken_Verify' => $vendorDir . '/google/apiclient/src/aliases.php', + 'Google_AuthHandler_AuthHandlerFactory' => $vendorDir . '/google/apiclient/src/aliases.php', + 'Google_AuthHandler_Guzzle6AuthHandler' => $vendorDir . '/google/apiclient/src/aliases.php', + 'Google_AuthHandler_Guzzle7AuthHandler' => $vendorDir . '/google/apiclient/src/aliases.php', + 'Google_Client' => $vendorDir . '/google/apiclient/src/aliases.php', + 'Google_Collection' => $vendorDir . '/google/apiclient/src/aliases.php', + 'Google_Exception' => $vendorDir . '/google/apiclient/src/aliases.php', + 'Google_Http_Batch' => $vendorDir . '/google/apiclient/src/aliases.php', + 'Google_Http_MediaFileUpload' => $vendorDir . '/google/apiclient/src/aliases.php', + 'Google_Http_REST' => $vendorDir . '/google/apiclient/src/aliases.php', + 'Google_Model' => $vendorDir . '/google/apiclient/src/aliases.php', + 'Google_Service' => $vendorDir . '/google/apiclient/src/aliases.php', + 'Google_Service_Exception' => $vendorDir . '/google/apiclient/src/aliases.php', + 'Google_Service_Resource' => $vendorDir . '/google/apiclient/src/aliases.php', + 'Google_Task_Composer' => $vendorDir . '/google/apiclient/src/aliases.php', + 'Google_Task_Exception' => $vendorDir . '/google/apiclient/src/aliases.php', + 'Google_Task_Retryable' => $vendorDir . '/google/apiclient/src/aliases.php', + 'Google_Task_Runner' => $vendorDir . '/google/apiclient/src/aliases.php', + 'Google_Utils_UriTemplate' => $vendorDir . '/google/apiclient/src/aliases.php', + 'Icewind\\SMB\\ACL' => $vendorDir . '/icewind/smb/src/ACL.php', + 'Icewind\\SMB\\AbstractServer' => $vendorDir . '/icewind/smb/src/AbstractServer.php', + 'Icewind\\SMB\\AbstractShare' => $vendorDir . '/icewind/smb/src/AbstractShare.php', + 'Icewind\\SMB\\AnonymousAuth' => $vendorDir . '/icewind/smb/src/AnonymousAuth.php', + 'Icewind\\SMB\\BasicAuth' => $vendorDir . '/icewind/smb/src/BasicAuth.php', + 'Icewind\\SMB\\Change' => $vendorDir . '/icewind/smb/src/Change.php', + 'Icewind\\SMB\\Exception\\AccessDeniedException' => $vendorDir . '/icewind/smb/src/Exception/AccessDeniedException.php', + 'Icewind\\SMB\\Exception\\AlreadyExistsException' => $vendorDir . '/icewind/smb/src/Exception/AlreadyExistsException.php', + 'Icewind\\SMB\\Exception\\AuthenticationException' => $vendorDir . '/icewind/smb/src/Exception/AuthenticationException.php', + 'Icewind\\SMB\\Exception\\ConnectException' => $vendorDir . '/icewind/smb/src/Exception/ConnectException.php', + 'Icewind\\SMB\\Exception\\ConnectionAbortedException' => $vendorDir . '/icewind/smb/src/Exception/ConnectionAbortedException.php', + 'Icewind\\SMB\\Exception\\ConnectionException' => $vendorDir . '/icewind/smb/src/Exception/ConnectionException.php', + 'Icewind\\SMB\\Exception\\ConnectionRefusedException' => $vendorDir . '/icewind/smb/src/Exception/ConnectionRefusedException.php', + 'Icewind\\SMB\\Exception\\ConnectionResetException' => $vendorDir . '/icewind/smb/src/Exception/ConnectionResetException.php', + 'Icewind\\SMB\\Exception\\DependencyException' => $vendorDir . '/icewind/smb/src/Exception/DependencyException.php', + 'Icewind\\SMB\\Exception\\Exception' => $vendorDir . '/icewind/smb/src/Exception/Exception.php', + 'Icewind\\SMB\\Exception\\FileInUseException' => $vendorDir . '/icewind/smb/src/Exception/FileInUseException.php', + 'Icewind\\SMB\\Exception\\ForbiddenException' => $vendorDir . '/icewind/smb/src/Exception/ForbiddenException.php', + 'Icewind\\SMB\\Exception\\HostDownException' => $vendorDir . '/icewind/smb/src/Exception/HostDownException.php', + 'Icewind\\SMB\\Exception\\InvalidArgumentException' => $vendorDir . '/icewind/smb/src/Exception/InvalidArgumentException.php', + 'Icewind\\SMB\\Exception\\InvalidHostException' => $vendorDir . '/icewind/smb/src/Exception/InvalidHostException.php', + 'Icewind\\SMB\\Exception\\InvalidParameterException' => $vendorDir . '/icewind/smb/src/Exception/InvalidParameterException.php', + 'Icewind\\SMB\\Exception\\InvalidPathException' => $vendorDir . '/icewind/smb/src/Exception/InvalidPathException.php', + 'Icewind\\SMB\\Exception\\InvalidRequestException' => $vendorDir . '/icewind/smb/src/Exception/InvalidRequestException.php', + 'Icewind\\SMB\\Exception\\InvalidResourceException' => $vendorDir . '/icewind/smb/src/Exception/InvalidResourceException.php', + 'Icewind\\SMB\\Exception\\InvalidTicket' => $vendorDir . '/icewind/smb/src/Exception/InvalidTicket.php', + 'Icewind\\SMB\\Exception\\InvalidTypeException' => $vendorDir . '/icewind/smb/src/Exception/InvalidTypeException.php', + 'Icewind\\SMB\\Exception\\NoLoginServerException' => $vendorDir . '/icewind/smb/src/Exception/NoLoginServerException.php', + 'Icewind\\SMB\\Exception\\NoRouteToHostException' => $vendorDir . '/icewind/smb/src/Exception/NoRouteToHostException.php', + 'Icewind\\SMB\\Exception\\NotEmptyException' => $vendorDir . '/icewind/smb/src/Exception/NotEmptyException.php', + 'Icewind\\SMB\\Exception\\NotFoundException' => $vendorDir . '/icewind/smb/src/Exception/NotFoundException.php', + 'Icewind\\SMB\\Exception\\OutOfSpaceException' => $vendorDir . '/icewind/smb/src/Exception/OutOfSpaceException.php', + 'Icewind\\SMB\\Exception\\RevisionMismatchException' => $vendorDir . '/icewind/smb/src/Exception/RevisionMismatchException.php', + 'Icewind\\SMB\\Exception\\TimedOutException' => $vendorDir . '/icewind/smb/src/Exception/TimedOutException.php', + 'Icewind\\SMB\\IAuth' => $vendorDir . '/icewind/smb/src/IAuth.php', + 'Icewind\\SMB\\IFileInfo' => $vendorDir . '/icewind/smb/src/IFileInfo.php', + 'Icewind\\SMB\\INotifyHandler' => $vendorDir . '/icewind/smb/src/INotifyHandler.php', + 'Icewind\\SMB\\IOptions' => $vendorDir . '/icewind/smb/src/IOptions.php', + 'Icewind\\SMB\\IServer' => $vendorDir . '/icewind/smb/src/IServer.php', + 'Icewind\\SMB\\IShare' => $vendorDir . '/icewind/smb/src/IShare.php', + 'Icewind\\SMB\\ISystem' => $vendorDir . '/icewind/smb/src/ISystem.php', + 'Icewind\\SMB\\ITimeZoneProvider' => $vendorDir . '/icewind/smb/src/ITimeZoneProvider.php', + 'Icewind\\SMB\\KerberosApacheAuth' => $vendorDir . '/icewind/smb/src/KerberosApacheAuth.php', + 'Icewind\\SMB\\KerberosAuth' => $vendorDir . '/icewind/smb/src/KerberosAuth.php', + 'Icewind\\SMB\\KerberosTicket' => $vendorDir . '/icewind/smb/src/KerberosTicket.php', + 'Icewind\\SMB\\Native\\NativeFileInfo' => $vendorDir . '/icewind/smb/src/Native/NativeFileInfo.php', + 'Icewind\\SMB\\Native\\NativeReadStream' => $vendorDir . '/icewind/smb/src/Native/NativeReadStream.php', + 'Icewind\\SMB\\Native\\NativeServer' => $vendorDir . '/icewind/smb/src/Native/NativeServer.php', + 'Icewind\\SMB\\Native\\NativeShare' => $vendorDir . '/icewind/smb/src/Native/NativeShare.php', + 'Icewind\\SMB\\Native\\NativeState' => $vendorDir . '/icewind/smb/src/Native/NativeState.php', + 'Icewind\\SMB\\Native\\NativeStream' => $vendorDir . '/icewind/smb/src/Native/NativeStream.php', + 'Icewind\\SMB\\Native\\NativeWriteStream' => $vendorDir . '/icewind/smb/src/Native/NativeWriteStream.php', + 'Icewind\\SMB\\Options' => $vendorDir . '/icewind/smb/src/Options.php', + 'Icewind\\SMB\\ServerFactory' => $vendorDir . '/icewind/smb/src/ServerFactory.php', + 'Icewind\\SMB\\StringBuffer' => $vendorDir . '/icewind/smb/src/StringBuffer.php', + 'Icewind\\SMB\\System' => $vendorDir . '/icewind/smb/src/System.php', + 'Icewind\\SMB\\TimeZoneProvider' => $vendorDir . '/icewind/smb/src/TimeZoneProvider.php', + 'Icewind\\SMB\\Wrapped\\Connection' => $vendorDir . '/icewind/smb/src/Wrapped/Connection.php', + 'Icewind\\SMB\\Wrapped\\ErrorCodes' => $vendorDir . '/icewind/smb/src/Wrapped/ErrorCodes.php', + 'Icewind\\SMB\\Wrapped\\FileInfo' => $vendorDir . '/icewind/smb/src/Wrapped/FileInfo.php', + 'Icewind\\SMB\\Wrapped\\NotifyHandler' => $vendorDir . '/icewind/smb/src/Wrapped/NotifyHandler.php', + 'Icewind\\SMB\\Wrapped\\Parser' => $vendorDir . '/icewind/smb/src/Wrapped/Parser.php', + 'Icewind\\SMB\\Wrapped\\RawConnection' => $vendorDir . '/icewind/smb/src/Wrapped/RawConnection.php', + 'Icewind\\SMB\\Wrapped\\Server' => $vendorDir . '/icewind/smb/src/Wrapped/Server.php', + 'Icewind\\SMB\\Wrapped\\Share' => $vendorDir . '/icewind/smb/src/Wrapped/Share.php', + 'Icewind\\Streams\\CallbackWrapper' => $vendorDir . '/icewind/streams/src/CallbackWrapper.php', + 'Icewind\\Streams\\CountWrapper' => $vendorDir . '/icewind/streams/src/CountWrapper.php', + 'Icewind\\Streams\\Directory' => $vendorDir . '/icewind/streams/src/Directory.php', + 'Icewind\\Streams\\DirectoryFilter' => $vendorDir . '/icewind/streams/src/DirectoryFilter.php', + 'Icewind\\Streams\\DirectoryWrapper' => $vendorDir . '/icewind/streams/src/DirectoryWrapper.php', + 'Icewind\\Streams\\File' => $vendorDir . '/icewind/streams/src/File.php', + 'Icewind\\Streams\\HashWrapper' => $vendorDir . '/icewind/streams/src/HashWrapper.php', + 'Icewind\\Streams\\IteratorDirectory' => $vendorDir . '/icewind/streams/src/IteratorDirectory.php', + 'Icewind\\Streams\\NullWrapper' => $vendorDir . '/icewind/streams/src/NullWrapper.php', + 'Icewind\\Streams\\Path' => $vendorDir . '/icewind/streams/src/Path.php', + 'Icewind\\Streams\\PathWrapper' => $vendorDir . '/icewind/streams/src/PathWrapper.php', + 'Icewind\\Streams\\ReadHashWrapper' => $vendorDir . '/icewind/streams/src/ReadHashWrapper.php', + 'Icewind\\Streams\\RetryWrapper' => $vendorDir . '/icewind/streams/src/RetryWrapper.php', + 'Icewind\\Streams\\SeekableWrapper' => $vendorDir . '/icewind/streams/src/SeekableWrapper.php', + 'Icewind\\Streams\\Url' => $vendorDir . '/icewind/streams/src/Url.php', + 'Icewind\\Streams\\UrlCallback' => $vendorDir . '/icewind/streams/src/UrlCallback.php', + 'Icewind\\Streams\\Wrapper' => $vendorDir . '/icewind/streams/src/Wrapper.php', + 'Icewind\\Streams\\WrapperHandler' => $vendorDir . '/icewind/streams/src/WrapperHandler.php', + 'Icewind\\Streams\\WriteHashWrapper' => $vendorDir . '/icewind/streams/src/WriteHashWrapper.php', + 'Monolog\\Attribute\\AsMonologProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Attribute/AsMonologProcessor.php', + 'Monolog\\DateTimeImmutable' => $vendorDir . '/monolog/monolog/src/Monolog/DateTimeImmutable.php', + 'Monolog\\ErrorHandler' => $vendorDir . '/monolog/monolog/src/Monolog/ErrorHandler.php', + 'Monolog\\Formatter\\ChromePHPFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php', + 'Monolog\\Formatter\\ElasticaFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php', + 'Monolog\\Formatter\\ElasticsearchFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php', + 'Monolog\\Formatter\\FlowdockFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php', + 'Monolog\\Formatter\\FluentdFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php', + 'Monolog\\Formatter\\FormatterInterface' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php', + 'Monolog\\Formatter\\GelfMessageFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php', + 'Monolog\\Formatter\\GoogleCloudLoggingFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/GoogleCloudLoggingFormatter.php', + 'Monolog\\Formatter\\HtmlFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php', + 'Monolog\\Formatter\\JsonFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php', + 'Monolog\\Formatter\\LineFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/LineFormatter.php', + 'Monolog\\Formatter\\LogglyFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php', + 'Monolog\\Formatter\\LogmaticFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php', + 'Monolog\\Formatter\\LogstashFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php', + 'Monolog\\Formatter\\MongoDBFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php', + 'Monolog\\Formatter\\NormalizerFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php', + 'Monolog\\Formatter\\ScalarFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php', + 'Monolog\\Formatter\\SyslogFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/SyslogFormatter.php', + 'Monolog\\Formatter\\WildfireFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php', + 'Monolog\\Handler\\AbstractHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/AbstractHandler.php', + 'Monolog\\Handler\\AbstractProcessingHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php', + 'Monolog\\Handler\\AbstractSyslogHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php', + 'Monolog\\Handler\\AmqpHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/AmqpHandler.php', + 'Monolog\\Handler\\BrowserConsoleHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php', + 'Monolog\\Handler\\BufferHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/BufferHandler.php', + 'Monolog\\Handler\\ChromePHPHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php', + 'Monolog\\Handler\\CouchDBHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php', + 'Monolog\\Handler\\CubeHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/CubeHandler.php', + 'Monolog\\Handler\\Curl\\Util' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/Curl/Util.php', + 'Monolog\\Handler\\DeduplicationHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php', + 'Monolog\\Handler\\DoctrineCouchDBHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php', + 'Monolog\\Handler\\DynamoDbHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php', + 'Monolog\\Handler\\ElasticaHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php', + 'Monolog\\Handler\\ElasticsearchHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ElasticsearchHandler.php', + 'Monolog\\Handler\\ErrorLogHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php', + 'Monolog\\Handler\\FallbackGroupHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php', + 'Monolog\\Handler\\FilterHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FilterHandler.php', + 'Monolog\\Handler\\FingersCrossedHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php', + 'Monolog\\Handler\\FingersCrossed\\ActivationStrategyInterface' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php', + 'Monolog\\Handler\\FingersCrossed\\ChannelLevelActivationStrategy' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php', + 'Monolog\\Handler\\FingersCrossed\\ErrorLevelActivationStrategy' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php', + 'Monolog\\Handler\\FirePHPHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php', + 'Monolog\\Handler\\FleepHookHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php', + 'Monolog\\Handler\\FlowdockHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php', + 'Monolog\\Handler\\FormattableHandlerInterface' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php', + 'Monolog\\Handler\\FormattableHandlerTrait' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php', + 'Monolog\\Handler\\GelfHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/GelfHandler.php', + 'Monolog\\Handler\\GroupHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/GroupHandler.php', + 'Monolog\\Handler\\Handler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/Handler.php', + 'Monolog\\Handler\\HandlerInterface' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/HandlerInterface.php', + 'Monolog\\Handler\\HandlerWrapper' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php', + 'Monolog\\Handler\\IFTTTHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php', + 'Monolog\\Handler\\InsightOpsHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php', + 'Monolog\\Handler\\LogEntriesHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php', + 'Monolog\\Handler\\LogglyHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/LogglyHandler.php', + 'Monolog\\Handler\\LogmaticHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php', + 'Monolog\\Handler\\MailHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/MailHandler.php', + 'Monolog\\Handler\\MandrillHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/MandrillHandler.php', + 'Monolog\\Handler\\MissingExtensionException' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php', + 'Monolog\\Handler\\MongoDBHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php', + 'Monolog\\Handler\\NativeMailerHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php', + 'Monolog\\Handler\\NewRelicHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php', + 'Monolog\\Handler\\NoopHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/NoopHandler.php', + 'Monolog\\Handler\\NullHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/NullHandler.php', + 'Monolog\\Handler\\OverflowHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/OverflowHandler.php', + 'Monolog\\Handler\\PHPConsoleHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php', + 'Monolog\\Handler\\ProcessHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ProcessHandler.php', + 'Monolog\\Handler\\ProcessableHandlerInterface' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php', + 'Monolog\\Handler\\ProcessableHandlerTrait' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php', + 'Monolog\\Handler\\PsrHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/PsrHandler.php', + 'Monolog\\Handler\\PushoverHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/PushoverHandler.php', + 'Monolog\\Handler\\RedisHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/RedisHandler.php', + 'Monolog\\Handler\\RedisPubSubHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php', + 'Monolog\\Handler\\RollbarHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/RollbarHandler.php', + 'Monolog\\Handler\\RotatingFileHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php', + 'Monolog\\Handler\\SamplingHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SamplingHandler.php', + 'Monolog\\Handler\\SendGridHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SendGridHandler.php', + 'Monolog\\Handler\\SlackHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SlackHandler.php', + 'Monolog\\Handler\\SlackWebhookHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php', + 'Monolog\\Handler\\Slack\\SlackRecord' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php', + 'Monolog\\Handler\\SocketHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SocketHandler.php', + 'Monolog\\Handler\\SqsHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SqsHandler.php', + 'Monolog\\Handler\\StreamHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/StreamHandler.php', + 'Monolog\\Handler\\SymfonyMailerHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php', + 'Monolog\\Handler\\SyslogHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SyslogHandler.php', + 'Monolog\\Handler\\SyslogUdpHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php', + 'Monolog\\Handler\\SyslogUdp\\UdpSocket' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php', + 'Monolog\\Handler\\TelegramBotHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php', + 'Monolog\\Handler\\TestHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/TestHandler.php', + 'Monolog\\Handler\\WebRequestRecognizerTrait' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php', + 'Monolog\\Handler\\WhatFailureGroupHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php', + 'Monolog\\Handler\\ZendMonitorHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php', + 'Monolog\\Level' => $vendorDir . '/monolog/monolog/src/Monolog/Level.php', + 'Monolog\\LogRecord' => $vendorDir . '/monolog/monolog/src/Monolog/LogRecord.php', + 'Monolog\\Logger' => $vendorDir . '/monolog/monolog/src/Monolog/Logger.php', + 'Monolog\\Processor\\ClosureContextProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/ClosureContextProcessor.php', + 'Monolog\\Processor\\GitProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/GitProcessor.php', + 'Monolog\\Processor\\HostnameProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php', + 'Monolog\\Processor\\IntrospectionProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php', + 'Monolog\\Processor\\LoadAverageProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/LoadAverageProcessor.php', + 'Monolog\\Processor\\MemoryPeakUsageProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php', + 'Monolog\\Processor\\MemoryProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php', + 'Monolog\\Processor\\MemoryUsageProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php', + 'Monolog\\Processor\\MercurialProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php', + 'Monolog\\Processor\\ProcessIdProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php', + 'Monolog\\Processor\\ProcessorInterface' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php', + 'Monolog\\Processor\\PsrLogMessageProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php', + 'Monolog\\Processor\\TagProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/TagProcessor.php', + 'Monolog\\Processor\\UidProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/UidProcessor.php', + 'Monolog\\Processor\\WebProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/WebProcessor.php', + 'Monolog\\Registry' => $vendorDir . '/monolog/monolog/src/Monolog/Registry.php', + 'Monolog\\ResettableInterface' => $vendorDir . '/monolog/monolog/src/Monolog/ResettableInterface.php', + 'Monolog\\SignalHandler' => $vendorDir . '/monolog/monolog/src/Monolog/SignalHandler.php', + 'Monolog\\Test\\TestCase' => $vendorDir . '/monolog/monolog/src/Monolog/Test/TestCase.php', + 'Monolog\\Utils' => $vendorDir . '/monolog/monolog/src/Monolog/Utils.php', + 'Psr\\Cache\\CacheException' => $vendorDir . '/psr/cache/src/CacheException.php', + 'Psr\\Cache\\CacheItemInterface' => $vendorDir . '/psr/cache/src/CacheItemInterface.php', + 'Psr\\Cache\\CacheItemPoolInterface' => $vendorDir . '/psr/cache/src/CacheItemPoolInterface.php', + 'Psr\\Cache\\InvalidArgumentException' => $vendorDir . '/psr/cache/src/InvalidArgumentException.php', + 'Psr\\Http\\Message\\MessageInterface' => $vendorDir . '/psr/http-message/src/MessageInterface.php', + 'Psr\\Http\\Message\\RequestInterface' => $vendorDir . '/psr/http-message/src/RequestInterface.php', + 'Psr\\Http\\Message\\ResponseInterface' => $vendorDir . '/psr/http-message/src/ResponseInterface.php', + 'Psr\\Http\\Message\\ServerRequestInterface' => $vendorDir . '/psr/http-message/src/ServerRequestInterface.php', + 'Psr\\Http\\Message\\StreamInterface' => $vendorDir . '/psr/http-message/src/StreamInterface.php', + 'Psr\\Http\\Message\\UploadedFileInterface' => $vendorDir . '/psr/http-message/src/UploadedFileInterface.php', + 'Psr\\Http\\Message\\UriInterface' => $vendorDir . '/psr/http-message/src/UriInterface.php', + 'Psr\\Log\\AbstractLogger' => $vendorDir . '/psr/log/src/AbstractLogger.php', + 'Psr\\Log\\InvalidArgumentException' => $vendorDir . '/psr/log/src/InvalidArgumentException.php', + 'Psr\\Log\\LogLevel' => $vendorDir . '/psr/log/src/LogLevel.php', + 'Psr\\Log\\LoggerAwareInterface' => $vendorDir . '/psr/log/src/LoggerAwareInterface.php', + 'Psr\\Log\\LoggerAwareTrait' => $vendorDir . '/psr/log/src/LoggerAwareTrait.php', + 'Psr\\Log\\LoggerInterface' => $vendorDir . '/psr/log/src/LoggerInterface.php', + 'Psr\\Log\\LoggerTrait' => $vendorDir . '/psr/log/src/LoggerTrait.php', + 'Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/src/NullLogger.php', +); diff --git a/apps/files_external/3rdparty/composer/autoload_psr4.php b/apps/files_external/3rdparty/composer/autoload_psr4.php new file mode 100644 index 000000000000..963a0dcae47c --- /dev/null +++ b/apps/files_external/3rdparty/composer/autoload_psr4.php @@ -0,0 +1,18 @@ + array($vendorDir . '/psr/log/src'), + 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'), + 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), + 'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'), + 'Icewind\\Streams\\' => array($vendorDir . '/icewind/streams/src'), + 'Icewind\\SMB\\' => array($vendorDir . '/icewind/smb/src'), + 'Google\\Service\\' => array($vendorDir . '/google/apiclient-services/src'), + 'Google\\Auth\\' => array($vendorDir . '/google/auth/src'), + 'Google\\' => array($vendorDir . '/google/apiclient/src'), +); diff --git a/apps/files_external/3rdparty/composer/autoload_static.php b/apps/files_external/3rdparty/composer/autoload_static.php new file mode 100644 index 000000000000..b2444f6e9e61 --- /dev/null +++ b/apps/files_external/3rdparty/composer/autoload_static.php @@ -0,0 +1,455 @@ + __DIR__ . '/..' . '/google/apiclient-services/autoload.php', + 'a8d3953fd9959404dd22d3dfcd0a79f0' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php', + '66d1e6eade98ab5874edb7f59d55f619' => __DIR__ . '/..' . '/../lib/config.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'P' => + array ( + 'Psr\\Log\\' => 8, + 'Psr\\Http\\Message\\' => 17, + 'Psr\\Cache\\' => 10, + ), + 'M' => + array ( + 'Monolog\\' => 8, + ), + 'I' => + array ( + 'Icewind\\Streams\\' => 16, + 'Icewind\\SMB\\' => 12, + ), + 'G' => + array ( + 'Google\\Service\\' => 15, + 'Google\\Auth\\' => 12, + 'Google\\' => 7, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/src', + ), + 'Psr\\Http\\Message\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-message/src', + ), + 'Psr\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/cache/src', + ), + 'Monolog\\' => + array ( + 0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog', + ), + 'Icewind\\Streams\\' => + array ( + 0 => __DIR__ . '/..' . '/icewind/streams/src', + ), + 'Icewind\\SMB\\' => + array ( + 0 => __DIR__ . '/..' . '/icewind/smb/src', + ), + 'Google\\Service\\' => + array ( + 0 => __DIR__ . '/..' . '/google/apiclient-services/src', + ), + 'Google\\Auth\\' => + array ( + 0 => __DIR__ . '/..' . '/google/auth/src', + ), + 'Google\\' => + array ( + 0 => __DIR__ . '/..' . '/google/apiclient/src', + ), + ); + + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'Google\\AccessToken\\Revoke' => __DIR__ . '/..' . '/google/apiclient/src/AccessToken/Revoke.php', + 'Google\\AccessToken\\Verify' => __DIR__ . '/..' . '/google/apiclient/src/AccessToken/Verify.php', + 'Google\\AuthHandler\\AuthHandlerFactory' => __DIR__ . '/..' . '/google/apiclient/src/AuthHandler/AuthHandlerFactory.php', + 'Google\\AuthHandler\\Guzzle6AuthHandler' => __DIR__ . '/..' . '/google/apiclient/src/AuthHandler/Guzzle6AuthHandler.php', + 'Google\\AuthHandler\\Guzzle7AuthHandler' => __DIR__ . '/..' . '/google/apiclient/src/AuthHandler/Guzzle7AuthHandler.php', + 'Google\\Auth\\AccessToken' => __DIR__ . '/..' . '/google/auth/src/AccessToken.php', + 'Google\\Auth\\ApplicationDefaultCredentials' => __DIR__ . '/..' . '/google/auth/src/ApplicationDefaultCredentials.php', + 'Google\\Auth\\CacheTrait' => __DIR__ . '/..' . '/google/auth/src/CacheTrait.php', + 'Google\\Auth\\Cache\\InvalidArgumentException' => __DIR__ . '/..' . '/google/auth/src/Cache/InvalidArgumentException.php', + 'Google\\Auth\\Cache\\Item' => __DIR__ . '/..' . '/google/auth/src/Cache/Item.php', + 'Google\\Auth\\Cache\\MemoryCacheItemPool' => __DIR__ . '/..' . '/google/auth/src/Cache/MemoryCacheItemPool.php', + 'Google\\Auth\\Cache\\SysVCacheItemPool' => __DIR__ . '/..' . '/google/auth/src/Cache/SysVCacheItemPool.php', + 'Google\\Auth\\Cache\\TypedItem' => __DIR__ . '/..' . '/google/auth/src/Cache/TypedItem.php', + 'Google\\Auth\\CredentialSource\\AwsNativeSource' => __DIR__ . '/..' . '/google/auth/src/CredentialSource/AwsNativeSource.php', + 'Google\\Auth\\CredentialSource\\FileSource' => __DIR__ . '/..' . '/google/auth/src/CredentialSource/FileSource.php', + 'Google\\Auth\\CredentialSource\\UrlSource' => __DIR__ . '/..' . '/google/auth/src/CredentialSource/UrlSource.php', + 'Google\\Auth\\CredentialsLoader' => __DIR__ . '/..' . '/google/auth/src/CredentialsLoader.php', + 'Google\\Auth\\Credentials\\AppIdentityCredentials' => __DIR__ . '/..' . '/google/auth/src/Credentials/AppIdentityCredentials.php', + 'Google\\Auth\\Credentials\\ExternalAccountCredentials' => __DIR__ . '/..' . '/google/auth/src/Credentials/ExternalAccountCredentials.php', + 'Google\\Auth\\Credentials\\GCECredentials' => __DIR__ . '/..' . '/google/auth/src/Credentials/GCECredentials.php', + 'Google\\Auth\\Credentials\\IAMCredentials' => __DIR__ . '/..' . '/google/auth/src/Credentials/IAMCredentials.php', + 'Google\\Auth\\Credentials\\ImpersonatedServiceAccountCredentials' => __DIR__ . '/..' . '/google/auth/src/Credentials/ImpersonatedServiceAccountCredentials.php', + 'Google\\Auth\\Credentials\\InsecureCredentials' => __DIR__ . '/..' . '/google/auth/src/Credentials/InsecureCredentials.php', + 'Google\\Auth\\Credentials\\ServiceAccountCredentials' => __DIR__ . '/..' . '/google/auth/src/Credentials/ServiceAccountCredentials.php', + 'Google\\Auth\\Credentials\\ServiceAccountJwtAccessCredentials' => __DIR__ . '/..' . '/google/auth/src/Credentials/ServiceAccountJwtAccessCredentials.php', + 'Google\\Auth\\Credentials\\UserRefreshCredentials' => __DIR__ . '/..' . '/google/auth/src/Credentials/UserRefreshCredentials.php', + 'Google\\Auth\\ExternalAccountCredentialSourceInterface' => __DIR__ . '/..' . '/google/auth/src/ExternalAccountCredentialSourceInterface.php', + 'Google\\Auth\\FetchAuthTokenCache' => __DIR__ . '/..' . '/google/auth/src/FetchAuthTokenCache.php', + 'Google\\Auth\\FetchAuthTokenInterface' => __DIR__ . '/..' . '/google/auth/src/FetchAuthTokenInterface.php', + 'Google\\Auth\\GCECache' => __DIR__ . '/..' . '/google/auth/src/GCECache.php', + 'Google\\Auth\\GetQuotaProjectInterface' => __DIR__ . '/..' . '/google/auth/src/GetQuotaProjectInterface.php', + 'Google\\Auth\\HttpHandler\\Guzzle6HttpHandler' => __DIR__ . '/..' . '/google/auth/src/HttpHandler/Guzzle6HttpHandler.php', + 'Google\\Auth\\HttpHandler\\Guzzle7HttpHandler' => __DIR__ . '/..' . '/google/auth/src/HttpHandler/Guzzle7HttpHandler.php', + 'Google\\Auth\\HttpHandler\\HttpClientCache' => __DIR__ . '/..' . '/google/auth/src/HttpHandler/HttpClientCache.php', + 'Google\\Auth\\HttpHandler\\HttpHandlerFactory' => __DIR__ . '/..' . '/google/auth/src/HttpHandler/HttpHandlerFactory.php', + 'Google\\Auth\\Iam' => __DIR__ . '/..' . '/google/auth/src/Iam.php', + 'Google\\Auth\\IamSignerTrait' => __DIR__ . '/..' . '/google/auth/src/IamSignerTrait.php', + 'Google\\Auth\\Middleware\\AuthTokenMiddleware' => __DIR__ . '/..' . '/google/auth/src/Middleware/AuthTokenMiddleware.php', + 'Google\\Auth\\Middleware\\ProxyAuthTokenMiddleware' => __DIR__ . '/..' . '/google/auth/src/Middleware/ProxyAuthTokenMiddleware.php', + 'Google\\Auth\\Middleware\\ScopedAccessTokenMiddleware' => __DIR__ . '/..' . '/google/auth/src/Middleware/ScopedAccessTokenMiddleware.php', + 'Google\\Auth\\Middleware\\SimpleMiddleware' => __DIR__ . '/..' . '/google/auth/src/Middleware/SimpleMiddleware.php', + 'Google\\Auth\\OAuth2' => __DIR__ . '/..' . '/google/auth/src/OAuth2.php', + 'Google\\Auth\\ProjectIdProviderInterface' => __DIR__ . '/..' . '/google/auth/src/ProjectIdProviderInterface.php', + 'Google\\Auth\\ServiceAccountSignerTrait' => __DIR__ . '/..' . '/google/auth/src/ServiceAccountSignerTrait.php', + 'Google\\Auth\\SignBlobInterface' => __DIR__ . '/..' . '/google/auth/src/SignBlobInterface.php', + 'Google\\Auth\\UpdateMetadataInterface' => __DIR__ . '/..' . '/google/auth/src/UpdateMetadataInterface.php', + 'Google\\Auth\\UpdateMetadataTrait' => __DIR__ . '/..' . '/google/auth/src/UpdateMetadataTrait.php', + 'Google\\Client' => __DIR__ . '/..' . '/google/apiclient/src/Client.php', + 'Google\\Collection' => __DIR__ . '/..' . '/google/apiclient/src/Collection.php', + 'Google\\Exception' => __DIR__ . '/..' . '/google/apiclient/src/Exception.php', + 'Google\\Http\\Batch' => __DIR__ . '/..' . '/google/apiclient/src/Http/Batch.php', + 'Google\\Http\\MediaFileUpload' => __DIR__ . '/..' . '/google/apiclient/src/Http/MediaFileUpload.php', + 'Google\\Http\\REST' => __DIR__ . '/..' . '/google/apiclient/src/Http/REST.php', + 'Google\\Model' => __DIR__ . '/..' . '/google/apiclient/src/Model.php', + 'Google\\Service' => __DIR__ . '/..' . '/google/apiclient/src/Service.php', + 'Google\\Service\\Drive' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive.php', + 'Google\\Service\\Drive\\About' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/About.php', + 'Google\\Service\\Drive\\AboutDriveThemes' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/AboutDriveThemes.php', + 'Google\\Service\\Drive\\AboutStorageQuota' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/AboutStorageQuota.php', + 'Google\\Service\\Drive\\AboutTeamDriveThemes' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/AboutTeamDriveThemes.php', + 'Google\\Service\\Drive\\Change' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/Change.php', + 'Google\\Service\\Drive\\ChangeList' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/ChangeList.php', + 'Google\\Service\\Drive\\Channel' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/Channel.php', + 'Google\\Service\\Drive\\Comment' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/Comment.php', + 'Google\\Service\\Drive\\CommentList' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/CommentList.php', + 'Google\\Service\\Drive\\CommentQuotedFileContent' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/CommentQuotedFileContent.php', + 'Google\\Service\\Drive\\ContentRestriction' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/ContentRestriction.php', + 'Google\\Service\\Drive\\Drive' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/Drive.php', + 'Google\\Service\\Drive\\DriveBackgroundImageFile' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/DriveBackgroundImageFile.php', + 'Google\\Service\\Drive\\DriveCapabilities' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/DriveCapabilities.php', + 'Google\\Service\\Drive\\DriveFile' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/DriveFile.php', + 'Google\\Service\\Drive\\DriveFileCapabilities' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/DriveFileCapabilities.php', + 'Google\\Service\\Drive\\DriveFileContentHints' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/DriveFileContentHints.php', + 'Google\\Service\\Drive\\DriveFileContentHintsThumbnail' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/DriveFileContentHintsThumbnail.php', + 'Google\\Service\\Drive\\DriveFileImageMediaMetadata' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/DriveFileImageMediaMetadata.php', + 'Google\\Service\\Drive\\DriveFileImageMediaMetadataLocation' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/DriveFileImageMediaMetadataLocation.php', + 'Google\\Service\\Drive\\DriveFileLabelInfo' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/DriveFileLabelInfo.php', + 'Google\\Service\\Drive\\DriveFileLinkShareMetadata' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/DriveFileLinkShareMetadata.php', + 'Google\\Service\\Drive\\DriveFileShortcutDetails' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/DriveFileShortcutDetails.php', + 'Google\\Service\\Drive\\DriveFileVideoMediaMetadata' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/DriveFileVideoMediaMetadata.php', + 'Google\\Service\\Drive\\DriveList' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/DriveList.php', + 'Google\\Service\\Drive\\DriveRestrictions' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/DriveRestrictions.php', + 'Google\\Service\\Drive\\FileList' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/FileList.php', + 'Google\\Service\\Drive\\GeneratedIds' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/GeneratedIds.php', + 'Google\\Service\\Drive\\Label' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/Label.php', + 'Google\\Service\\Drive\\LabelField' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/LabelField.php', + 'Google\\Service\\Drive\\LabelFieldModification' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/LabelFieldModification.php', + 'Google\\Service\\Drive\\LabelList' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/LabelList.php', + 'Google\\Service\\Drive\\LabelModification' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/LabelModification.php', + 'Google\\Service\\Drive\\ModifyLabelsRequest' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/ModifyLabelsRequest.php', + 'Google\\Service\\Drive\\ModifyLabelsResponse' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/ModifyLabelsResponse.php', + 'Google\\Service\\Drive\\Permission' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/Permission.php', + 'Google\\Service\\Drive\\PermissionList' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/PermissionList.php', + 'Google\\Service\\Drive\\PermissionPermissionDetails' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/PermissionPermissionDetails.php', + 'Google\\Service\\Drive\\PermissionTeamDrivePermissionDetails' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/PermissionTeamDrivePermissionDetails.php', + 'Google\\Service\\Drive\\Reply' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/Reply.php', + 'Google\\Service\\Drive\\ReplyList' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/ReplyList.php', + 'Google\\Service\\Drive\\Resource\\About' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/Resource/About.php', + 'Google\\Service\\Drive\\Resource\\Changes' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/Resource/Changes.php', + 'Google\\Service\\Drive\\Resource\\Channels' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/Resource/Channels.php', + 'Google\\Service\\Drive\\Resource\\Comments' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/Resource/Comments.php', + 'Google\\Service\\Drive\\Resource\\Drives' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/Resource/Drives.php', + 'Google\\Service\\Drive\\Resource\\Files' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/Resource/Files.php', + 'Google\\Service\\Drive\\Resource\\Permissions' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/Resource/Permissions.php', + 'Google\\Service\\Drive\\Resource\\Replies' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/Resource/Replies.php', + 'Google\\Service\\Drive\\Resource\\Revisions' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/Resource/Revisions.php', + 'Google\\Service\\Drive\\Resource\\Teamdrives' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/Resource/Teamdrives.php', + 'Google\\Service\\Drive\\Revision' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/Revision.php', + 'Google\\Service\\Drive\\RevisionList' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/RevisionList.php', + 'Google\\Service\\Drive\\StartPageToken' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/StartPageToken.php', + 'Google\\Service\\Drive\\TeamDrive' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/TeamDrive.php', + 'Google\\Service\\Drive\\TeamDriveBackgroundImageFile' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/TeamDriveBackgroundImageFile.php', + 'Google\\Service\\Drive\\TeamDriveCapabilities' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/TeamDriveCapabilities.php', + 'Google\\Service\\Drive\\TeamDriveList' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/TeamDriveList.php', + 'Google\\Service\\Drive\\TeamDriveRestrictions' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/TeamDriveRestrictions.php', + 'Google\\Service\\Drive\\User' => __DIR__ . '/..' . '/google/apiclient-services/src/Drive/User.php', + 'Google\\Service\\Exception' => __DIR__ . '/..' . '/google/apiclient/src/Service/Exception.php', + 'Google\\Service\\Resource' => __DIR__ . '/..' . '/google/apiclient/src/Service/Resource.php', + 'Google\\Task\\Composer' => __DIR__ . '/..' . '/google/apiclient/src/Task/Composer.php', + 'Google\\Task\\Exception' => __DIR__ . '/..' . '/google/apiclient/src/Task/Exception.php', + 'Google\\Task\\Retryable' => __DIR__ . '/..' . '/google/apiclient/src/Task/Retryable.php', + 'Google\\Task\\Runner' => __DIR__ . '/..' . '/google/apiclient/src/Task/Runner.php', + 'Google\\Utils\\UriTemplate' => __DIR__ . '/..' . '/google/apiclient/src/Utils/UriTemplate.php', + 'Google_AccessToken_Revoke' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php', + 'Google_AccessToken_Verify' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php', + 'Google_AuthHandler_AuthHandlerFactory' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php', + 'Google_AuthHandler_Guzzle6AuthHandler' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php', + 'Google_AuthHandler_Guzzle7AuthHandler' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php', + 'Google_Client' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php', + 'Google_Collection' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php', + 'Google_Exception' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php', + 'Google_Http_Batch' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php', + 'Google_Http_MediaFileUpload' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php', + 'Google_Http_REST' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php', + 'Google_Model' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php', + 'Google_Service' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php', + 'Google_Service_Exception' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php', + 'Google_Service_Resource' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php', + 'Google_Task_Composer' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php', + 'Google_Task_Exception' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php', + 'Google_Task_Retryable' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php', + 'Google_Task_Runner' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php', + 'Google_Utils_UriTemplate' => __DIR__ . '/..' . '/google/apiclient/src/aliases.php', + 'Icewind\\SMB\\ACL' => __DIR__ . '/..' . '/icewind/smb/src/ACL.php', + 'Icewind\\SMB\\AbstractServer' => __DIR__ . '/..' . '/icewind/smb/src/AbstractServer.php', + 'Icewind\\SMB\\AbstractShare' => __DIR__ . '/..' . '/icewind/smb/src/AbstractShare.php', + 'Icewind\\SMB\\AnonymousAuth' => __DIR__ . '/..' . '/icewind/smb/src/AnonymousAuth.php', + 'Icewind\\SMB\\BasicAuth' => __DIR__ . '/..' . '/icewind/smb/src/BasicAuth.php', + 'Icewind\\SMB\\Change' => __DIR__ . '/..' . '/icewind/smb/src/Change.php', + 'Icewind\\SMB\\Exception\\AccessDeniedException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/AccessDeniedException.php', + 'Icewind\\SMB\\Exception\\AlreadyExistsException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/AlreadyExistsException.php', + 'Icewind\\SMB\\Exception\\AuthenticationException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/AuthenticationException.php', + 'Icewind\\SMB\\Exception\\ConnectException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/ConnectException.php', + 'Icewind\\SMB\\Exception\\ConnectionAbortedException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/ConnectionAbortedException.php', + 'Icewind\\SMB\\Exception\\ConnectionException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/ConnectionException.php', + 'Icewind\\SMB\\Exception\\ConnectionRefusedException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/ConnectionRefusedException.php', + 'Icewind\\SMB\\Exception\\ConnectionResetException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/ConnectionResetException.php', + 'Icewind\\SMB\\Exception\\DependencyException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/DependencyException.php', + 'Icewind\\SMB\\Exception\\Exception' => __DIR__ . '/..' . '/icewind/smb/src/Exception/Exception.php', + 'Icewind\\SMB\\Exception\\FileInUseException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/FileInUseException.php', + 'Icewind\\SMB\\Exception\\ForbiddenException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/ForbiddenException.php', + 'Icewind\\SMB\\Exception\\HostDownException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/HostDownException.php', + 'Icewind\\SMB\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/InvalidArgumentException.php', + 'Icewind\\SMB\\Exception\\InvalidHostException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/InvalidHostException.php', + 'Icewind\\SMB\\Exception\\InvalidParameterException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/InvalidParameterException.php', + 'Icewind\\SMB\\Exception\\InvalidPathException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/InvalidPathException.php', + 'Icewind\\SMB\\Exception\\InvalidRequestException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/InvalidRequestException.php', + 'Icewind\\SMB\\Exception\\InvalidResourceException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/InvalidResourceException.php', + 'Icewind\\SMB\\Exception\\InvalidTicket' => __DIR__ . '/..' . '/icewind/smb/src/Exception/InvalidTicket.php', + 'Icewind\\SMB\\Exception\\InvalidTypeException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/InvalidTypeException.php', + 'Icewind\\SMB\\Exception\\NoLoginServerException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/NoLoginServerException.php', + 'Icewind\\SMB\\Exception\\NoRouteToHostException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/NoRouteToHostException.php', + 'Icewind\\SMB\\Exception\\NotEmptyException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/NotEmptyException.php', + 'Icewind\\SMB\\Exception\\NotFoundException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/NotFoundException.php', + 'Icewind\\SMB\\Exception\\OutOfSpaceException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/OutOfSpaceException.php', + 'Icewind\\SMB\\Exception\\RevisionMismatchException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/RevisionMismatchException.php', + 'Icewind\\SMB\\Exception\\TimedOutException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/TimedOutException.php', + 'Icewind\\SMB\\IAuth' => __DIR__ . '/..' . '/icewind/smb/src/IAuth.php', + 'Icewind\\SMB\\IFileInfo' => __DIR__ . '/..' . '/icewind/smb/src/IFileInfo.php', + 'Icewind\\SMB\\INotifyHandler' => __DIR__ . '/..' . '/icewind/smb/src/INotifyHandler.php', + 'Icewind\\SMB\\IOptions' => __DIR__ . '/..' . '/icewind/smb/src/IOptions.php', + 'Icewind\\SMB\\IServer' => __DIR__ . '/..' . '/icewind/smb/src/IServer.php', + 'Icewind\\SMB\\IShare' => __DIR__ . '/..' . '/icewind/smb/src/IShare.php', + 'Icewind\\SMB\\ISystem' => __DIR__ . '/..' . '/icewind/smb/src/ISystem.php', + 'Icewind\\SMB\\ITimeZoneProvider' => __DIR__ . '/..' . '/icewind/smb/src/ITimeZoneProvider.php', + 'Icewind\\SMB\\KerberosApacheAuth' => __DIR__ . '/..' . '/icewind/smb/src/KerberosApacheAuth.php', + 'Icewind\\SMB\\KerberosAuth' => __DIR__ . '/..' . '/icewind/smb/src/KerberosAuth.php', + 'Icewind\\SMB\\KerberosTicket' => __DIR__ . '/..' . '/icewind/smb/src/KerberosTicket.php', + 'Icewind\\SMB\\Native\\NativeFileInfo' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeFileInfo.php', + 'Icewind\\SMB\\Native\\NativeReadStream' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeReadStream.php', + 'Icewind\\SMB\\Native\\NativeServer' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeServer.php', + 'Icewind\\SMB\\Native\\NativeShare' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeShare.php', + 'Icewind\\SMB\\Native\\NativeState' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeState.php', + 'Icewind\\SMB\\Native\\NativeStream' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeStream.php', + 'Icewind\\SMB\\Native\\NativeWriteStream' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeWriteStream.php', + 'Icewind\\SMB\\Options' => __DIR__ . '/..' . '/icewind/smb/src/Options.php', + 'Icewind\\SMB\\ServerFactory' => __DIR__ . '/..' . '/icewind/smb/src/ServerFactory.php', + 'Icewind\\SMB\\StringBuffer' => __DIR__ . '/..' . '/icewind/smb/src/StringBuffer.php', + 'Icewind\\SMB\\System' => __DIR__ . '/..' . '/icewind/smb/src/System.php', + 'Icewind\\SMB\\TimeZoneProvider' => __DIR__ . '/..' . '/icewind/smb/src/TimeZoneProvider.php', + 'Icewind\\SMB\\Wrapped\\Connection' => __DIR__ . '/..' . '/icewind/smb/src/Wrapped/Connection.php', + 'Icewind\\SMB\\Wrapped\\ErrorCodes' => __DIR__ . '/..' . '/icewind/smb/src/Wrapped/ErrorCodes.php', + 'Icewind\\SMB\\Wrapped\\FileInfo' => __DIR__ . '/..' . '/icewind/smb/src/Wrapped/FileInfo.php', + 'Icewind\\SMB\\Wrapped\\NotifyHandler' => __DIR__ . '/..' . '/icewind/smb/src/Wrapped/NotifyHandler.php', + 'Icewind\\SMB\\Wrapped\\Parser' => __DIR__ . '/..' . '/icewind/smb/src/Wrapped/Parser.php', + 'Icewind\\SMB\\Wrapped\\RawConnection' => __DIR__ . '/..' . '/icewind/smb/src/Wrapped/RawConnection.php', + 'Icewind\\SMB\\Wrapped\\Server' => __DIR__ . '/..' . '/icewind/smb/src/Wrapped/Server.php', + 'Icewind\\SMB\\Wrapped\\Share' => __DIR__ . '/..' . '/icewind/smb/src/Wrapped/Share.php', + 'Icewind\\Streams\\CallbackWrapper' => __DIR__ . '/..' . '/icewind/streams/src/CallbackWrapper.php', + 'Icewind\\Streams\\CountWrapper' => __DIR__ . '/..' . '/icewind/streams/src/CountWrapper.php', + 'Icewind\\Streams\\Directory' => __DIR__ . '/..' . '/icewind/streams/src/Directory.php', + 'Icewind\\Streams\\DirectoryFilter' => __DIR__ . '/..' . '/icewind/streams/src/DirectoryFilter.php', + 'Icewind\\Streams\\DirectoryWrapper' => __DIR__ . '/..' . '/icewind/streams/src/DirectoryWrapper.php', + 'Icewind\\Streams\\File' => __DIR__ . '/..' . '/icewind/streams/src/File.php', + 'Icewind\\Streams\\HashWrapper' => __DIR__ . '/..' . '/icewind/streams/src/HashWrapper.php', + 'Icewind\\Streams\\IteratorDirectory' => __DIR__ . '/..' . '/icewind/streams/src/IteratorDirectory.php', + 'Icewind\\Streams\\NullWrapper' => __DIR__ . '/..' . '/icewind/streams/src/NullWrapper.php', + 'Icewind\\Streams\\Path' => __DIR__ . '/..' . '/icewind/streams/src/Path.php', + 'Icewind\\Streams\\PathWrapper' => __DIR__ . '/..' . '/icewind/streams/src/PathWrapper.php', + 'Icewind\\Streams\\ReadHashWrapper' => __DIR__ . '/..' . '/icewind/streams/src/ReadHashWrapper.php', + 'Icewind\\Streams\\RetryWrapper' => __DIR__ . '/..' . '/icewind/streams/src/RetryWrapper.php', + 'Icewind\\Streams\\SeekableWrapper' => __DIR__ . '/..' . '/icewind/streams/src/SeekableWrapper.php', + 'Icewind\\Streams\\Url' => __DIR__ . '/..' . '/icewind/streams/src/Url.php', + 'Icewind\\Streams\\UrlCallback' => __DIR__ . '/..' . '/icewind/streams/src/UrlCallback.php', + 'Icewind\\Streams\\Wrapper' => __DIR__ . '/..' . '/icewind/streams/src/Wrapper.php', + 'Icewind\\Streams\\WrapperHandler' => __DIR__ . '/..' . '/icewind/streams/src/WrapperHandler.php', + 'Icewind\\Streams\\WriteHashWrapper' => __DIR__ . '/..' . '/icewind/streams/src/WriteHashWrapper.php', + 'Monolog\\Attribute\\AsMonologProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Attribute/AsMonologProcessor.php', + 'Monolog\\DateTimeImmutable' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/DateTimeImmutable.php', + 'Monolog\\ErrorHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/ErrorHandler.php', + 'Monolog\\Formatter\\ChromePHPFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php', + 'Monolog\\Formatter\\ElasticaFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php', + 'Monolog\\Formatter\\ElasticsearchFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php', + 'Monolog\\Formatter\\FlowdockFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php', + 'Monolog\\Formatter\\FluentdFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php', + 'Monolog\\Formatter\\FormatterInterface' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php', + 'Monolog\\Formatter\\GelfMessageFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php', + 'Monolog\\Formatter\\GoogleCloudLoggingFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/GoogleCloudLoggingFormatter.php', + 'Monolog\\Formatter\\HtmlFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php', + 'Monolog\\Formatter\\JsonFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php', + 'Monolog\\Formatter\\LineFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/LineFormatter.php', + 'Monolog\\Formatter\\LogglyFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php', + 'Monolog\\Formatter\\LogmaticFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php', + 'Monolog\\Formatter\\LogstashFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php', + 'Monolog\\Formatter\\MongoDBFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php', + 'Monolog\\Formatter\\NormalizerFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php', + 'Monolog\\Formatter\\ScalarFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php', + 'Monolog\\Formatter\\SyslogFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/SyslogFormatter.php', + 'Monolog\\Formatter\\WildfireFormatter' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php', + 'Monolog\\Handler\\AbstractHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/AbstractHandler.php', + 'Monolog\\Handler\\AbstractProcessingHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php', + 'Monolog\\Handler\\AbstractSyslogHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php', + 'Monolog\\Handler\\AmqpHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/AmqpHandler.php', + 'Monolog\\Handler\\BrowserConsoleHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php', + 'Monolog\\Handler\\BufferHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/BufferHandler.php', + 'Monolog\\Handler\\ChromePHPHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php', + 'Monolog\\Handler\\CouchDBHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php', + 'Monolog\\Handler\\CubeHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/CubeHandler.php', + 'Monolog\\Handler\\Curl\\Util' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/Curl/Util.php', + 'Monolog\\Handler\\DeduplicationHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php', + 'Monolog\\Handler\\DoctrineCouchDBHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php', + 'Monolog\\Handler\\DynamoDbHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php', + 'Monolog\\Handler\\ElasticaHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php', + 'Monolog\\Handler\\ElasticsearchHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/ElasticsearchHandler.php', + 'Monolog\\Handler\\ErrorLogHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php', + 'Monolog\\Handler\\FallbackGroupHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php', + 'Monolog\\Handler\\FilterHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FilterHandler.php', + 'Monolog\\Handler\\FingersCrossedHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php', + 'Monolog\\Handler\\FingersCrossed\\ActivationStrategyInterface' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php', + 'Monolog\\Handler\\FingersCrossed\\ChannelLevelActivationStrategy' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php', + 'Monolog\\Handler\\FingersCrossed\\ErrorLevelActivationStrategy' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php', + 'Monolog\\Handler\\FirePHPHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php', + 'Monolog\\Handler\\FleepHookHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php', + 'Monolog\\Handler\\FlowdockHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php', + 'Monolog\\Handler\\FormattableHandlerInterface' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php', + 'Monolog\\Handler\\FormattableHandlerTrait' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php', + 'Monolog\\Handler\\GelfHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/GelfHandler.php', + 'Monolog\\Handler\\GroupHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/GroupHandler.php', + 'Monolog\\Handler\\Handler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/Handler.php', + 'Monolog\\Handler\\HandlerInterface' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/HandlerInterface.php', + 'Monolog\\Handler\\HandlerWrapper' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php', + 'Monolog\\Handler\\IFTTTHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php', + 'Monolog\\Handler\\InsightOpsHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php', + 'Monolog\\Handler\\LogEntriesHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php', + 'Monolog\\Handler\\LogglyHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/LogglyHandler.php', + 'Monolog\\Handler\\LogmaticHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php', + 'Monolog\\Handler\\MailHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/MailHandler.php', + 'Monolog\\Handler\\MandrillHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/MandrillHandler.php', + 'Monolog\\Handler\\MissingExtensionException' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php', + 'Monolog\\Handler\\MongoDBHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php', + 'Monolog\\Handler\\NativeMailerHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php', + 'Monolog\\Handler\\NewRelicHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php', + 'Monolog\\Handler\\NoopHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/NoopHandler.php', + 'Monolog\\Handler\\NullHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/NullHandler.php', + 'Monolog\\Handler\\OverflowHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/OverflowHandler.php', + 'Monolog\\Handler\\PHPConsoleHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php', + 'Monolog\\Handler\\ProcessHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/ProcessHandler.php', + 'Monolog\\Handler\\ProcessableHandlerInterface' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php', + 'Monolog\\Handler\\ProcessableHandlerTrait' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php', + 'Monolog\\Handler\\PsrHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/PsrHandler.php', + 'Monolog\\Handler\\PushoverHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/PushoverHandler.php', + 'Monolog\\Handler\\RedisHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/RedisHandler.php', + 'Monolog\\Handler\\RedisPubSubHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php', + 'Monolog\\Handler\\RollbarHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/RollbarHandler.php', + 'Monolog\\Handler\\RotatingFileHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php', + 'Monolog\\Handler\\SamplingHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SamplingHandler.php', + 'Monolog\\Handler\\SendGridHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SendGridHandler.php', + 'Monolog\\Handler\\SlackHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SlackHandler.php', + 'Monolog\\Handler\\SlackWebhookHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php', + 'Monolog\\Handler\\Slack\\SlackRecord' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php', + 'Monolog\\Handler\\SocketHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SocketHandler.php', + 'Monolog\\Handler\\SqsHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SqsHandler.php', + 'Monolog\\Handler\\StreamHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/StreamHandler.php', + 'Monolog\\Handler\\SymfonyMailerHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php', + 'Monolog\\Handler\\SyslogHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SyslogHandler.php', + 'Monolog\\Handler\\SyslogUdpHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php', + 'Monolog\\Handler\\SyslogUdp\\UdpSocket' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php', + 'Monolog\\Handler\\TelegramBotHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php', + 'Monolog\\Handler\\TestHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/TestHandler.php', + 'Monolog\\Handler\\WebRequestRecognizerTrait' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php', + 'Monolog\\Handler\\WhatFailureGroupHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php', + 'Monolog\\Handler\\ZendMonitorHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php', + 'Monolog\\Level' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Level.php', + 'Monolog\\LogRecord' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/LogRecord.php', + 'Monolog\\Logger' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Logger.php', + 'Monolog\\Processor\\ClosureContextProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/ClosureContextProcessor.php', + 'Monolog\\Processor\\GitProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/GitProcessor.php', + 'Monolog\\Processor\\HostnameProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php', + 'Monolog\\Processor\\IntrospectionProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php', + 'Monolog\\Processor\\LoadAverageProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/LoadAverageProcessor.php', + 'Monolog\\Processor\\MemoryPeakUsageProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php', + 'Monolog\\Processor\\MemoryProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php', + 'Monolog\\Processor\\MemoryUsageProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php', + 'Monolog\\Processor\\MercurialProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php', + 'Monolog\\Processor\\ProcessIdProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php', + 'Monolog\\Processor\\ProcessorInterface' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php', + 'Monolog\\Processor\\PsrLogMessageProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php', + 'Monolog\\Processor\\TagProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/TagProcessor.php', + 'Monolog\\Processor\\UidProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/UidProcessor.php', + 'Monolog\\Processor\\WebProcessor' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Processor/WebProcessor.php', + 'Monolog\\Registry' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Registry.php', + 'Monolog\\ResettableInterface' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/ResettableInterface.php', + 'Monolog\\SignalHandler' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/SignalHandler.php', + 'Monolog\\Test\\TestCase' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Test/TestCase.php', + 'Monolog\\Utils' => __DIR__ . '/..' . '/monolog/monolog/src/Monolog/Utils.php', + 'Psr\\Cache\\CacheException' => __DIR__ . '/..' . '/psr/cache/src/CacheException.php', + 'Psr\\Cache\\CacheItemInterface' => __DIR__ . '/..' . '/psr/cache/src/CacheItemInterface.php', + 'Psr\\Cache\\CacheItemPoolInterface' => __DIR__ . '/..' . '/psr/cache/src/CacheItemPoolInterface.php', + 'Psr\\Cache\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/cache/src/InvalidArgumentException.php', + 'Psr\\Http\\Message\\MessageInterface' => __DIR__ . '/..' . '/psr/http-message/src/MessageInterface.php', + 'Psr\\Http\\Message\\RequestInterface' => __DIR__ . '/..' . '/psr/http-message/src/RequestInterface.php', + 'Psr\\Http\\Message\\ResponseInterface' => __DIR__ . '/..' . '/psr/http-message/src/ResponseInterface.php', + 'Psr\\Http\\Message\\ServerRequestInterface' => __DIR__ . '/..' . '/psr/http-message/src/ServerRequestInterface.php', + 'Psr\\Http\\Message\\StreamInterface' => __DIR__ . '/..' . '/psr/http-message/src/StreamInterface.php', + 'Psr\\Http\\Message\\UploadedFileInterface' => __DIR__ . '/..' . '/psr/http-message/src/UploadedFileInterface.php', + 'Psr\\Http\\Message\\UriInterface' => __DIR__ . '/..' . '/psr/http-message/src/UriInterface.php', + 'Psr\\Log\\AbstractLogger' => __DIR__ . '/..' . '/psr/log/src/AbstractLogger.php', + 'Psr\\Log\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/log/src/InvalidArgumentException.php', + 'Psr\\Log\\LogLevel' => __DIR__ . '/..' . '/psr/log/src/LogLevel.php', + 'Psr\\Log\\LoggerAwareInterface' => __DIR__ . '/..' . '/psr/log/src/LoggerAwareInterface.php', + 'Psr\\Log\\LoggerAwareTrait' => __DIR__ . '/..' . '/psr/log/src/LoggerAwareTrait.php', + 'Psr\\Log\\LoggerInterface' => __DIR__ . '/..' . '/psr/log/src/LoggerInterface.php', + 'Psr\\Log\\LoggerTrait' => __DIR__ . '/..' . '/psr/log/src/LoggerTrait.php', + 'Psr\\Log\\NullLogger' => __DIR__ . '/..' . '/psr/log/src/NullLogger.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/apps/files_external/3rdparty/composer/installed.json b/apps/files_external/3rdparty/composer/installed.json new file mode 100644 index 000000000000..a312f9636ed9 --- /dev/null +++ b/apps/files_external/3rdparty/composer/installed.json @@ -0,0 +1,1234 @@ +{ + "packages": [ + { + "name": "google/apiclient", + "version": "v2.15.1", + "version_normalized": "2.15.1.0", + "source": { + "type": "git", + "url": "https://github.com/googleapis/google-api-php-client.git", + "reference": "7a95ed29e4b6c6859d2d22300c5455a92e2622ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/google-api-php-client/zipball/7a95ed29e4b6c6859d2d22300c5455a92e2622ad", + "reference": "7a95ed29e4b6c6859d2d22300c5455a92e2622ad", + "shasum": "" + }, + "require": { + "firebase/php-jwt": "~6.0", + "google/apiclient-services": "~0.200", + "google/auth": "^1.28", + "guzzlehttp/guzzle": "~6.5||~7.0", + "guzzlehttp/psr7": "^1.8.4||^2.2.1", + "monolog/monolog": "^2.9||^3.0", + "php": "^7.4|^8.0", + "phpseclib/phpseclib": "^3.0.19" + }, + "require-dev": { + "cache/filesystem-adapter": "^1.1", + "composer/composer": "^1.10.22", + "phpcompatibility/php-compatibility": "^9.2", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "^3.0", + "symfony/css-selector": "~2.1", + "symfony/dom-crawler": "~2.1" + }, + "suggest": { + "cache/filesystem-adapter": "For caching certs and tokens (using Google\\Client::setCache)" + }, + "time": "2023-09-13T21:46:39+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "src/aliases.php" + ], + "psr-4": { + "Google\\": "src/" + }, + "classmap": [ + "src/aliases.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Client library for Google APIs", + "homepage": "http://developers.google.com/api-client-library/php", + "keywords": [ + "google" + ], + "support": { + "issues": "https://github.com/googleapis/google-api-php-client/issues", + "source": "https://github.com/googleapis/google-api-php-client/tree/v2.15.1" + }, + "install-path": "../google/apiclient" + }, + { + "name": "google/apiclient-services", + "version": "v0.319.0", + "version_normalized": "0.319.0.0", + "source": { + "type": "git", + "url": "https://github.com/googleapis/google-api-php-client-services.git", + "reference": "9206f4d8cfacbec3c494d68f1758b2c8ca5b179d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/9206f4d8cfacbec3c494d68f1758b2c8ca5b179d", + "reference": "9206f4d8cfacbec3c494d68f1758b2c8ca5b179d", + "shasum": "" + }, + "require": { + "php": "^7.4||^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7||^8.5.13" + }, + "time": "2023-10-08T01:02:14+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "autoload.php" + ], + "psr-4": { + "Google\\Service\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Client library for Google APIs", + "homepage": "http://developers.google.com/api-client-library/php", + "keywords": [ + "google" + ], + "support": { + "issues": "https://github.com/googleapis/google-api-php-client-services/issues", + "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.319.0" + }, + "install-path": "../google/apiclient-services" + }, + { + "name": "google/auth", + "version": "v1.31.0", + "version_normalized": "1.31.0.0", + "source": { + "type": "git", + "url": "https://github.com/googleapis/google-auth-library-php.git", + "reference": "22209fddd0c06f3f8e3cb4aade0b352aa00f9888" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/22209fddd0c06f3f8e3cb4aade0b352aa00f9888", + "reference": "22209fddd0c06f3f8e3cb4aade0b352aa00f9888", + "shasum": "" + }, + "require": { + "firebase/php-jwt": "^6.0", + "guzzlehttp/guzzle": "^6.2.1|^7.0", + "guzzlehttp/psr7": "^2.4.5", + "php": "^7.4||^8.0", + "psr/cache": "^1.0||^2.0||^3.0", + "psr/http-message": "^1.1||^2.0" + }, + "require-dev": { + "guzzlehttp/promises": "^2.0", + "kelvinmo/simplejwt": "0.7.1", + "phpseclib/phpseclib": "^3.0", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.0.0", + "sebastian/comparator": ">=1.2.3", + "squizlabs/php_codesniffer": "^3.5" + }, + "suggest": { + "phpseclib/phpseclib": "May be used in place of OpenSSL for signing strings or for token management. Please require version ^2." + }, + "time": "2023-10-05T20:39:00+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Google\\Auth\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Google Auth Library for PHP", + "homepage": "http://github.com/google/google-auth-library-php", + "keywords": [ + "Authentication", + "google", + "oauth2" + ], + "support": { + "docs": "https://googleapis.github.io/google-auth-library-php/main/", + "issues": "https://github.com/googleapis/google-auth-library-php/issues", + "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.31.0" + }, + "install-path": "../google/auth" + }, + { + "name": "icewind/smb", + "version": "v3.6.0", + "version_normalized": "3.6.0.0", + "source": { + "type": "git", + "url": "https://github.com/icewind1991/SMB.git", + "reference": "e0e86b16640f5892dd00408ed50ad18357dac6c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/icewind1991/SMB/zipball/e0e86b16640f5892dd00408ed50ad18357dac6c1", + "reference": "e0e86b16640f5892dd00408ed50ad18357dac6c1", + "shasum": "" + }, + "require": { + "icewind/streams": ">=0.7.3", + "php": ">=7.2" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "phpstan/phpstan": "^0.12.57", + "phpunit/phpunit": "^8.5|^9.3.8", + "psalm/phar": "^4.3" + }, + "time": "2023-08-10T13:17:39+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Icewind\\SMB\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Robin Appelman", + "email": "icewind@owncloud.com" + } + ], + "description": "php wrapper for smbclient and libsmbclient-php", + "support": { + "issues": "https://github.com/icewind1991/SMB/issues", + "source": "https://github.com/icewind1991/SMB/tree/v3.6.0" + }, + "install-path": "../icewind/smb" + }, + { + "name": "icewind/streams", + "version": "v0.7.7", + "version_normalized": "0.7.7.0", + "source": { + "type": "git", + "url": "https://github.com/icewind1991/Streams.git", + "reference": "64200fd7cfcc7f550c3c695c48d8fd8bba97fecb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/icewind1991/Streams/zipball/64200fd7cfcc7f550c3c695c48d8fd8bba97fecb", + "reference": "64200fd7cfcc7f550c3c695c48d8fd8bba97fecb", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^9" + }, + "time": "2023-03-16T14:52:25+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Icewind\\Streams\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Robin Appelman", + "email": "icewind@owncloud.com" + } + ], + "description": "A set of generic stream wrappers", + "support": { + "issues": "https://github.com/icewind1991/Streams/issues", + "source": "https://github.com/icewind1991/Streams/tree/v0.7.7" + }, + "install-path": "../icewind/streams" + }, + { + "name": "monolog/monolog", + "version": "3.4.0", + "version_normalized": "3.4.0.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "e2392369686d420ca32df3803de28b5d6f76867d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/e2392369686d420ca32df3803de28b5d6f76867d", + "reference": "e2392369686d420ca32df3803de28b5d6f76867d", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-strict-rules": "^1.4", + "phpunit/phpunit": "^10.1", + "predis/predis": "^1.1 || ^2", + "ruflin/elastica": "^7", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "time": "2023-06-21T08:46:11+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.4.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "install-path": "../monolog/monolog" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "version_normalized": "3.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "time": "2021-02-03T23:26:27+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "install-path": "../psr/cache" + }, + { + "name": "psr/http-message", + "version": "2.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "time": "2023-04-04T09:54:51+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "install-path": "../psr/http-message" + }, + { + "name": "psr/log", + "version": "3.0.0", + "version_normalized": "3.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "time": "2021-07-14T16:46:02+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "install-path": "../psr/log" + }, + { + "name": "roave/security-advisories", + "version": "dev-latest", + "version_normalized": "dev-latest", + "source": { + "type": "git", + "url": "https://github.com/Roave/SecurityAdvisories.git", + "reference": "85afa852eeecec97cec35d987dcfbc602c025620" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/85afa852eeecec97cec35d987dcfbc602c025620", + "reference": "85afa852eeecec97cec35d987dcfbc602c025620", + "shasum": "" + }, + "conflict": { + "3f/pygmentize": "<1.2", + "admidio/admidio": "<4.2.11", + "adodb/adodb-php": "<=5.20.20|>=5.21,<=5.21.3", + "aheinze/cockpit": "<2.2", + "aimeos/aimeos-typo3": "<19.10.12|>=20,<20.10.5", + "akaunting/akaunting": "<2.1.13", + "akeneo/pim-community-dev": "<5.0.119|>=6,<6.0.53", + "alextselegidis/easyappointments": "<1.5", + "alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1", + "amazing/media2click": ">=1,<1.3.3", + "amphp/artax": "<1.0.6|>=2,<2.0.6", + "amphp/http": "<1.0.1", + "amphp/http-client": ">=4,<4.4", + "anchorcms/anchor-cms": "<=0.12.7", + "andreapollastri/cipi": "<=3.1.15", + "andrewhaine/silverstripe-form-capture": ">=0.2,<=0.2.3|>=1,<1.0.2|>=2,<2.2.5", + "apache-solr-for-typo3/solr": "<2.8.3", + "apereo/phpcas": "<1.6", + "api-platform/core": ">=2.2,<2.2.10|>=2.3,<2.3.6|>=2.6,<2.7.10|>=3,<3.0.12|>=3.1,<3.1.3", + "appwrite/server-ce": "<=1.2.1", + "arc/web": "<3", + "area17/twill": "<1.2.5|>=2,<2.5.3", + "artesaos/seotools": "<0.17.2", + "asymmetricrypt/asymmetricrypt": "<9.9.99", + "athlon1600/php-proxy": "<=5.1", + "athlon1600/php-proxy-app": "<=3", + "austintoddj/canvas": "<=3.4.2", + "automad/automad": "<1.8", + "awesome-support/awesome-support": "<=6.0.7", + "aws/aws-sdk-php": ">=3,<3.2.1", + "azuracast/azuracast": "<0.18.3", + "backdrop/backdrop": "<1.24.2", + "backpack/crud": "<3.4.9", + "badaso/core": "<2.7", + "bagisto/bagisto": "<0.1.5", + "barrelstrength/sprout-base-email": "<1.2.7", + "barrelstrength/sprout-forms": "<3.9", + "barryvdh/laravel-translation-manager": "<0.6.2", + "barzahlen/barzahlen-php": "<2.0.1", + "baserproject/basercms": "<4.7.5", + "bassjobsen/bootstrap-3-typeahead": ">4.0.2", + "bigfork/silverstripe-form-capture": ">=3,<3.1.1", + "billz/raspap-webgui": "<=2.9.2", + "bk2k/bootstrap-package": ">=7.1,<7.1.2|>=8,<8.0.8|>=9,<9.0.4|>=9.1,<9.1.3|>=10,<10.0.10|>=11,<11.0.3", + "bmarshall511/wordpress_zero_spam": "<5.2.13", + "bolt/bolt": "<3.7.2", + "bolt/core": "<=4.2", + "bottelet/flarepoint": "<2.2.1", + "brightlocal/phpwhois": "<=4.2.5", + "brotkrueml/codehighlight": "<2.7", + "brotkrueml/schema": "<1.13.1|>=2,<2.5.1", + "brotkrueml/typo3-matomo-integration": "<1.3.2", + "buddypress/buddypress": "<7.2.1", + "bugsnag/bugsnag-laravel": "<2.0.2", + "bytefury/crater": "<6.0.2", + "cachethq/cachet": "<2.5.1", + "cakephp/cakephp": "<3.10.3|>=4,<4.0.10|>=4.1,<4.1.4|>=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10", + "cakephp/database": ">=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10", + "cardgate/magento2": "<2.0.33", + "cardgate/woocommerce": "<=3.1.15", + "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4", + "cartalyst/sentry": "<=2.1.6", + "catfan/medoo": "<1.7.5", + "cecil/cecil": "<7.47.1", + "centreon/centreon": "<22.10.0.0-beta1", + "cesnet/simplesamlphp-module-proxystatistics": "<3.1", + "chriskacerguis/codeigniter-restserver": "<=2.7.1", + "civicrm/civicrm-core": ">=4.2,<4.2.9|>=4.3,<4.3.3", + "cockpit-hq/cockpit": "<=2.6.3", + "codeception/codeception": "<3.1.3|>=4,<4.1.22", + "codeigniter/framework": "<3.1.9", + "codeigniter4/framework": "<4.3.5", + "codeigniter4/shield": "<1.0.0.0-beta4", + "codiad/codiad": "<=2.8.4", + "composer/composer": "<1.10.27|>=2,<2.2.22|>=2.3,<2.6.4", + "concrete5/concrete5": "<=9.2.1", + "concrete5/core": "<8.5.8|>=9,<9.1", + "contao-components/mediaelement": ">=2.14.2,<2.21.1", + "contao/contao": ">=4,<4.4.56|>=4.5,<4.9.40|>=4.10,<4.11.7|>=4.13,<4.13.21|>=5.1,<5.1.4", + "contao/core": ">=2,<3.5.39", + "contao/core-bundle": "<4.9.42|>=4.10,<4.13.28|>=5,<5.1.10", + "contao/listing-bundle": ">=4,<4.4.8", + "contao/managed-edition": "<=1.5", + "cosenary/instagram": "<=2.3", + "craftcms/cms": "<=4.4.14", + "croogo/croogo": "<4", + "cuyz/valinor": "<0.12", + "czproject/git-php": "<4.0.3", + "darylldoyle/safe-svg": "<1.9.10", + "datadog/dd-trace": ">=0.30,<0.30.2", + "datatables/datatables": "<1.10.10", + "david-garcia/phpwhois": "<=4.3.1", + "dbrisinajumi/d2files": "<1", + "dcat/laravel-admin": "<=2.1.3.0-beta", + "derhansen/fe_change_pwd": "<2.0.5|>=3,<3.0.3", + "derhansen/sf_event_mgt": "<4.3.1|>=5,<5.1.1", + "desperado/xml-bundle": "<=0.1.7", + "directmailteam/direct-mail": "<5.2.4", + "doctrine/annotations": ">=1,<1.2.7", + "doctrine/cache": ">=1,<1.3.2|>=1.4,<1.4.2", + "doctrine/common": ">=2,<2.4.3|>=2.5,<2.5.1", + "doctrine/dbal": ">=2,<2.0.8|>=2.1,<2.1.2|>=3,<3.1.4", + "doctrine/doctrine-bundle": "<1.5.2", + "doctrine/doctrine-module": "<=0.7.1", + "doctrine/mongodb-odm": ">=1,<1.0.2", + "doctrine/mongodb-odm-bundle": ">=2,<3.0.1", + "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1|>=2.8.3,<2.8.4", + "dolibarr/dolibarr": "<18", + "dompdf/dompdf": "<2.0.2|==2.0.2", + "drupal/core": "<9.4.14|>=9.5,<9.5.8|>=10,<10.0.8", + "drupal/drupal": ">=6,<6.38|>=7,<7.80|>=8,<8.9.16|>=9,<9.1.12|>=9.2,<9.2.4", + "dweeves/magmi": "<=0.7.24", + "ecodev/newsletter": "<=4", + "ectouch/ectouch": "<=2.7.2", + "elefant/cms": "<2.0.7", + "elgg/elgg": "<3.3.24|>=4,<4.0.5", + "encore/laravel-admin": "<=1.8.19", + "endroid/qr-code-bundle": "<3.4.2", + "enshrined/svg-sanitize": "<0.15", + "erusev/parsedown": "<1.7.2", + "ether/logs": "<3.0.4", + "exceedone/exment": "<4.4.3|>=5,<5.0.3", + "exceedone/laravel-admin": "<2.2.3|==3", + "ezsystems/demobundle": ">=5.4,<5.4.6.1-dev", + "ezsystems/ez-support-tools": ">=2.2,<2.2.3", + "ezsystems/ezdemo-ls-extension": ">=5.4,<5.4.2.1-dev", + "ezsystems/ezfind-ls": ">=5.3,<5.3.6.1-dev|>=5.4,<5.4.11.1-dev|>=2017.12,<2017.12.0.1-dev", + "ezsystems/ezplatform": "<=1.13.6|>=2,<=2.5.24", + "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.29|>=2.3,<2.3.26", + "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2.1|>=5,<5.0.1|>=5.1,<5.1.1", + "ezsystems/ezplatform-graphql": ">=1.0.0.0-RC1-dev,<1.0.13|>=2.0.0.0-beta1,<2.3.12", + "ezsystems/ezplatform-kernel": "<1.2.5.1-dev|>=1.3,<1.3.26", + "ezsystems/ezplatform-rest": ">=1.2,<=1.2.2|>=1.3,<1.3.8", + "ezsystems/ezplatform-richtext": ">=2.3,<2.3.7.1-dev", + "ezsystems/ezplatform-user": ">=1,<1.0.1", + "ezsystems/ezpublish-kernel": "<6.13.8.2-dev|>=7,<7.5.30", + "ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.6,<=2019.03.5.1", + "ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3", + "ezsystems/repository-forms": ">=2.3,<2.3.2.1-dev|>=2.5,<2.5.15", + "ezyang/htmlpurifier": "<4.1.1", + "facade/ignition": "<1.16.15|>=2,<2.4.2|>=2.5,<2.5.2", + "facturascripts/facturascripts": "<=2022.08", + "feehi/cms": "<=2.1.1", + "feehi/feehicms": "<=2.1.1", + "fenom/fenom": "<=2.12.1", + "filegator/filegator": "<7.8", + "firebase/php-jwt": "<6", + "fixpunkt/fp-masterquiz": "<2.2.1|>=3,<3.5.2", + "fixpunkt/fp-newsletter": "<1.1.1|>=2,<2.1.2|>=2.2,<3.2.6", + "flarum/core": "<1.8", + "flarum/framework": "<1.8", + "flarum/mentions": "<1.6.3", + "flarum/sticky": ">=0.1.0.0-beta14,<=0.1.0.0-beta15", + "flarum/tags": "<=0.1.0.0-beta13", + "fluidtypo3/vhs": "<5.1.1", + "fof/byobu": ">=0.3.0.0-beta2,<1.1.7", + "fof/upload": "<1.2.3", + "fooman/tcpdf": "<6.2.22", + "forkcms/forkcms": "<5.11.1", + "fossar/tcpdf-parser": "<6.2.22", + "francoisjacquet/rosariosis": "<11", + "frappant/frp-form-answers": "<3.1.2|>=4,<4.0.2", + "friendsofsymfony/oauth2-php": "<1.3", + "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2", + "friendsofsymfony/user-bundle": ">=1.2,<1.3.5", + "friendsoftypo3/mediace": ">=7.6.2,<7.6.5", + "friendsoftypo3/openid": ">=4.5,<4.5.31|>=4.7,<4.7.16|>=6,<6.0.11|>=6.1,<6.1.6", + "froala/wysiwyg-editor": "<3.2.7|>=4.0.1,<=4.1.1", + "froxlor/froxlor": "<2.1", + "fuel/core": "<1.8.1", + "funadmin/funadmin": "<=3.2|>=3.3.2,<=3.3.3", + "gaoming13/wechat-php-sdk": "<=1.10.2", + "genix/cms": "<=1.1.11", + "getgrav/grav": "<=1.7.42.1", + "getkirby/cms": "<3.5.8.3-dev|>=3.6,<3.6.6.3-dev|>=3.7,<3.7.5.2-dev|>=3.8,<3.8.4.1-dev|>=3.9,<3.9.6", + "getkirby/kirby": "<=2.5.12", + "getkirby/panel": "<2.5.14", + "getkirby/starterkit": "<=3.7.0.2", + "gilacms/gila": "<=1.11.4", + "gleez/cms": "<=1.2|==2", + "globalpayments/php-sdk": "<2", + "gogentooss/samlbase": "<1.2.7", + "google/protobuf": "<3.15", + "gos/web-socket-bundle": "<1.10.4|>=2,<2.6.1|>=3,<3.3", + "gree/jose": "<2.2.1", + "gregwar/rst": "<1.0.3", + "grumpydictator/firefly-iii": "<6", + "gugoan/economizzer": "<=0.9.0.0-beta1", + "guzzlehttp/guzzle": "<6.5.8|>=7,<7.4.5", + "guzzlehttp/psr7": "<1.9.1|>=2,<2.4.5", + "haffner/jh_captcha": "<=2.1.3|>=3,<=3.0.2", + "harvesthq/chosen": "<1.8.7", + "helloxz/imgurl": "<=2.31", + "hhxsv5/laravel-s": "<3.7.36", + "hillelcoren/invoice-ninja": "<5.3.35", + "himiklab/yii2-jqgrid-widget": "<1.0.8", + "hjue/justwriting": "<=1", + "hov/jobfair": "<1.0.13|>=2,<2.0.2", + "httpsoft/http-message": "<1.0.12", + "hyn/multi-tenant": ">=5.6,<5.7.2", + "ibexa/admin-ui": ">=4.2,<4.2.3", + "ibexa/core": ">=4,<4.0.7|>=4.1,<4.1.4|>=4.2,<4.2.3", + "ibexa/graphql": ">=2.5,<2.5.31|>=3.3,<3.3.28|>=4.2,<4.2.3", + "ibexa/post-install": "<=1.0.4", + "ibexa/user": ">=4,<4.4.3", + "icecoder/icecoder": "<=8.1", + "idno/known": "<=1.3.1", + "illuminate/auth": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.10", + "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<=4.1.99999|>=4.2,<=4.2.99999|>=5,<=5.0.99999|>=5.1,<=5.1.99999|>=5.2,<=5.2.99999|>=5.3,<=5.3.99999|>=5.4,<=5.4.99999|>=5.5,<=5.5.49|>=5.6,<=5.6.99999|>=5.7,<=5.7.99999|>=5.8,<=5.8.99999|>=6,<6.18.31|>=7,<7.22.4", + "illuminate/database": "<6.20.26|>=7,<7.30.5|>=8,<8.40", + "illuminate/encryption": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.40|>=5.6,<5.6.15", + "illuminate/view": "<6.20.42|>=7,<7.30.6|>=8,<8.75", + "impresscms/impresscms": "<=1.4.5", + "in2code/femanager": "<5.5.3|>=6,<6.3.4|>=7,<7.2.2", + "in2code/ipandlanguageredirect": "<5.1.2", + "in2code/lux": "<17.6.1|>=18,<24.0.2", + "innologi/typo3-appointments": "<2.0.6", + "intelliants/subrion": "<4.2.2", + "islandora/islandora": ">=2,<2.4.1", + "ivankristianto/phpwhois": "<=4.3", + "jackalope/jackalope-doctrine-dbal": "<1.7.4", + "james-heinrich/getid3": "<1.9.21", + "james-heinrich/phpthumb": "<1.7.12", + "jasig/phpcas": "<1.3.3", + "jcbrand/converse.js": "<3.3.3", + "joomla/application": "<1.0.13", + "joomla/archive": "<1.1.12|>=2,<2.0.1", + "joomla/filesystem": "<1.6.2|>=2,<2.0.1", + "joomla/filter": "<1.4.4|>=2,<2.0.1", + "joomla/framework": ">=2.5.4,<=3.8.12", + "joomla/input": ">=2,<2.0.2", + "joomla/joomla-cms": ">=2.5,<3.9.12", + "joomla/session": "<1.3.1", + "joyqi/hyper-down": "<=2.4.27", + "jsdecena/laracom": "<2.0.9", + "jsmitty12/phpwhois": "<5.1", + "kazist/phpwhois": "<=4.2.6", + "kelvinmo/simplexrd": "<3.1.1", + "kevinpapst/kimai2": "<1.16.7", + "khodakhah/nodcms": "<=3", + "kimai/kimai": "<1.1", + "kitodo/presentation": "<3.2.3|>=3.3,<3.3.4", + "klaviyo/magento2-extension": ">=1,<3", + "knplabs/knp-snappy": "<=1.4.2", + "kohana/core": "<3.3.3", + "krayin/laravel-crm": "<1.2.2", + "kreait/firebase-php": ">=3.2,<3.8.1", + "la-haute-societe/tcpdf": "<6.2.22", + "laminas/laminas-diactoros": "<2.18.1|==2.19|==2.20|==2.21|==2.22|==2.23|>=2.24,<2.24.2|>=2.25,<2.25.2", + "laminas/laminas-form": "<2.17.1|>=3,<3.0.2|>=3.1,<3.1.1", + "laminas/laminas-http": "<2.14.2", + "laravel/fortify": "<1.11.1", + "laravel/framework": "<6.20.44|>=7,<7.30.6|>=8,<8.75", + "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", + "latte/latte": "<2.10.8", + "lavalite/cms": "<=9", + "lcobucci/jwt": ">=3.4,<3.4.6|>=4,<4.0.4|>=4.1,<4.1.5", + "league/commonmark": "<0.18.3", + "league/flysystem": "<1.1.4|>=2,<2.1.1", + "league/oauth2-server": ">=8.3.2,<8.4.2|>=8.5,<8.5.3", + "lexik/jwt-authentication-bundle": "<2.10.7|>=2.11,<2.11.3", + "librenms/librenms": "<2017.08.18", + "liftkit/database": "<2.13.2", + "limesurvey/limesurvey": "<3.27.19", + "livehelperchat/livehelperchat": "<=3.91", + "livewire/livewire": ">2.2.4,<2.2.6", + "lms/routes": "<2.1.1", + "localizationteam/l10nmgr": "<7.4|>=8,<8.7|>=9,<9.2", + "luyadev/yii-helpers": "<1.2.1", + "magento/community-edition": "<=2.4", + "magento/magento1ce": "<1.9.4.3-dev", + "magento/magento1ee": ">=1,<1.14.4.3-dev", + "magento/product-community-edition": ">=2,<2.2.10|>=2.3,<2.3.2.0-patch2", + "maikuolan/phpmussel": ">=1,<1.6", + "mantisbt/mantisbt": "<=2.25.5", + "marcwillmann/turn": "<0.3.3", + "matyhtf/framework": "<3.0.6", + "mautic/core": "<4.3", + "mediawiki/core": ">=1.27,<1.27.6|>=1.29,<1.29.3|>=1.30,<1.30.2|>=1.31,<1.31.9|>=1.32,<1.32.6|>=1.32.99,<1.33.3|>=1.33.99,<1.34.3|>=1.34.99,<1.35", + "mediawiki/matomo": "<2.4.3", + "melisplatform/melis-asset-manager": "<5.0.1", + "melisplatform/melis-cms": "<5.0.1", + "melisplatform/melis-front": "<5.0.1", + "mezzio/mezzio-swoole": "<3.7|>=4,<4.3", + "mgallegos/laravel-jqgrid": "<=1.3", + "microweber/microweber": "<=1.3.4", + "miniorange/miniorange-saml": "<1.4.3", + "mittwald/typo3_forum": "<1.2.1", + "mobiledetect/mobiledetectlib": "<2.8.32", + "modx/revolution": "<=2.8.3.0-patch", + "mojo42/jirafeau": "<4.4", + "mongodb/mongodb": ">=1,<1.9.2", + "monolog/monolog": ">=1.8,<1.12", + "moodle/moodle": "<4.2.0.0-RC2-dev|==4.2", + "movim/moxl": ">=0.8,<=0.10", + "mpdf/mpdf": "<=7.1.7", + "mustache/mustache": ">=2,<2.14.1", + "namshi/jose": "<2.2", + "neoan3-apps/template": "<1.1.1", + "neorazorx/facturascripts": "<2022.04", + "neos/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", + "neos/form": ">=1.2,<4.3.3|>=5,<5.0.9|>=5.1,<5.1.3", + "neos/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.9.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<5.3.10|>=7,<7.0.9|>=7.1,<7.1.7|>=7.2,<7.2.6|>=7.3,<7.3.4|>=8,<8.0.2", + "neos/neos-ui": "<=8.3.3", + "neos/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5", + "netgen/tagsbundle": ">=3.4,<3.4.11|>=4,<4.0.15", + "nette/application": ">=2,<2.0.19|>=2.1,<2.1.13|>=2.2,<2.2.10|>=2.3,<2.3.14|>=2.4,<2.4.16|>=3,<3.0.6", + "nette/nette": ">=2,<2.0.19|>=2.1,<2.1.13", + "nilsteampassnet/teampass": "<3.0.10", + "notrinos/notrinos-erp": "<=0.7", + "noumo/easyii": "<=0.9", + "nukeviet/nukeviet": "<4.5.02", + "nyholm/psr7": "<1.6.1", + "nystudio107/craft-seomatic": "<3.4.12", + "nzo/url-encryptor-bundle": ">=4,<4.3.2|>=5,<5.0.1", + "october/backend": "<1.1.2", + "october/cms": "<1.0.469|==1.0.469|==1.0.471|==1.1.1", + "october/october": "<=3.4.4", + "october/rain": "<1.0.472|>=1.1,<1.1.2", + "october/system": "<1.0.476|>=1.1,<1.1.12|>=2,<2.2.34|>=3,<3.0.66", + "onelogin/php-saml": "<2.10.4", + "oneup/uploader-bundle": "<1.9.3|>=2,<2.1.5", + "open-web-analytics/open-web-analytics": "<1.7.4", + "opencart/opencart": "<=3.0.3.7|>=4,<4.0.2.3-dev", + "openid/php-openid": "<2.3", + "openmage/magento-lts": "<=19.5|>=20,<=20.1", + "opensource-workshop/connect-cms": "<1.7.2|>=2,<2.3.2", + "orchid/platform": ">=9,<9.4.4|>=14.0.0.0-alpha4,<14.5", + "oro/commerce": ">=4.1,<5.0.6", + "oro/crm": ">=1.7,<1.7.4|>=3.1,<4.1.17|>=4.2,<4.2.7", + "oro/platform": ">=1.7,<1.7.4|>=3.1,<3.1.29|>=4.1,<4.1.17|>=4.2,<4.2.8", + "oxid-esales/oxideshop-ce": "<4.5", + "packbackbooks/lti-1-3-php-library": "<5", + "padraic/humbug_get_contents": "<1.1.2", + "pagarme/pagarme-php": "<3", + "pagekit/pagekit": "<=1.0.18", + "paragonie/random_compat": "<2", + "passbolt/passbolt_api": "<2.11", + "paypal/merchant-sdk-php": "<3.12", + "pear/archive_tar": "<1.4.14", + "pear/crypt_gpg": "<1.6.7", + "pear/pear": "<=1.10.1", + "pegasus/google-for-jobs": "<1.5.1|>=2,<2.1.1", + "personnummer/personnummer": "<3.0.2", + "phanan/koel": "<5.1.4", + "php-mod/curl": "<2.3.2", + "phpbb/phpbb": "<3.2.10|>=3.3,<3.3.1", + "phpfastcache/phpfastcache": "<6.1.5|>=7,<7.1.2|>=8,<8.0.7", + "phpmailer/phpmailer": "<6.5", + "phpmussel/phpmussel": ">=1,<1.6", + "phpmyadmin/phpmyadmin": "<5.2.1", + "phpmyfaq/phpmyfaq": "<=3.1.7", + "phpoffice/phpexcel": "<1.8", + "phpoffice/phpspreadsheet": "<1.16", + "phpseclib/phpseclib": "<2.0.31|>=3,<3.0.19", + "phpservermon/phpservermon": "<3.6", + "phpsysinfo/phpsysinfo": "<3.2.5", + "phpunit/phpunit": ">=4.8.19,<4.8.28|>=5,<5.6.3", + "phpwhois/phpwhois": "<=4.2.5", + "phpxmlrpc/extras": "<0.6.1", + "phpxmlrpc/phpxmlrpc": "<4.9.2", + "pi/pi": "<=2.5", + "pimcore/admin-ui-classic-bundle": "<1.1.2", + "pimcore/customer-management-framework-bundle": "<3.4.2", + "pimcore/data-hub": "<1.2.4", + "pimcore/demo": "<10.3", + "pimcore/perspective-editor": "<1.5.1", + "pimcore/pimcore": "<10.6.8", + "pixelfed/pixelfed": "<=0.11.4", + "pocketmine/bedrock-protocol": "<8.0.2", + "pocketmine/pocketmine-mp": "<=4.23|>=5,<5.3.1", + "pressbooks/pressbooks": "<5.18", + "prestashop/autoupgrade": ">=4,<4.10.1", + "prestashop/blockwishlist": ">=2,<2.1.1", + "prestashop/contactform": ">=1.0.1,<4.3", + "prestashop/gamification": "<2.3.2", + "prestashop/prestashop": "<8.1.2", + "prestashop/productcomments": "<5.0.2", + "prestashop/ps_emailsubscription": "<2.6.1", + "prestashop/ps_facetedsearch": "<3.4.1", + "prestashop/ps_linklist": "<3.1", + "privatebin/privatebin": "<1.4", + "processwire/processwire": "<=3.0.200", + "propel/propel": ">=2.0.0.0-alpha1,<=2.0.0.0-alpha7", + "propel/propel1": ">=1,<=1.7.1", + "pterodactyl/panel": "<1.7", + "ptheofan/yii2-statemachine": ">=2.0.0.0-RC1-dev,<=2", + "ptrofimov/beanstalk_console": "<1.7.14", + "pusher/pusher-php-server": "<2.2.1", + "pwweb/laravel-core": "<=0.3.6.0-beta", + "pyrocms/pyrocms": "<=3.9.1", + "rainlab/blog-plugin": "<1.4.1", + "rainlab/debugbar-plugin": "<3.1", + "rainlab/user-plugin": "<=1.4.5", + "rankmath/seo-by-rank-math": "<=1.0.95", + "rap2hpoutre/laravel-log-viewer": "<0.13", + "react/http": ">=0.7,<1.9", + "really-simple-plugins/complianz-gdpr": "<6.4.2", + "remdex/livehelperchat": "<3.99", + "rmccue/requests": ">=1.6,<1.8", + "robrichards/xmlseclibs": "<3.0.4", + "roots/soil": "<4.1", + "rudloff/alltube": "<3.0.3", + "s-cart/core": "<6.9", + "s-cart/s-cart": "<6.9", + "sabberworm/php-css-parser": ">=1,<1.0.1|>=2,<2.0.1|>=3,<3.0.1|>=4,<4.0.1|>=5,<5.0.9|>=5.1,<5.1.3|>=5.2,<5.2.1|>=6,<6.0.2|>=7,<7.0.4|>=8,<8.0.1|>=8.1,<8.1.1|>=8.2,<8.2.1|>=8.3,<8.3.1", + "sabre/dav": "<1.7.11|>=1.8,<1.8.9", + "scheb/two-factor-bundle": "<3.26|>=4,<4.11", + "sensiolabs/connect": "<4.2.3", + "serluck/phpwhois": "<=4.2.6", + "sfroemken/url_redirect": "<=1.2.1", + "sheng/yiicms": "<=1.2", + "shopware/core": "<=6.4.20", + "shopware/platform": "<=6.4.20", + "shopware/production": "<=6.3.5.2", + "shopware/shopware": "<=5.7.17", + "shopware/storefront": "<=6.4.8.1", + "shopxo/shopxo": "<2.2.6", + "showdoc/showdoc": "<2.10.4", + "silverstripe-australia/advancedreports": ">=1,<=2", + "silverstripe/admin": "<1.13.6", + "silverstripe/assets": ">=1,<1.11.1", + "silverstripe/cms": "<4.11.3", + "silverstripe/comments": ">=1.3,<1.9.99|>=2,<2.9.99|>=3,<3.1.1", + "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", + "silverstripe/framework": "<4.13.14|>=5,<5.0.13", + "silverstripe/graphql": "<3.5.2|>=4.0.0.0-alpha1,<4.0.0.0-alpha2|>=4.1.1,<4.1.2|>=4.2.2,<4.2.3", + "silverstripe/hybridsessions": ">=1,<2.4.1|>=2.5,<2.5.1", + "silverstripe/recipe-cms": ">=4.5,<4.5.3", + "silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1", + "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4", + "silverstripe/silverstripe-omnipay": "<2.5.2|>=3,<3.0.2|>=3.1,<3.1.4|>=3.2,<3.2.1", + "silverstripe/subsites": ">=2,<2.6.1", + "silverstripe/taxonomy": ">=1.3,<1.3.1|>=2,<2.0.1", + "silverstripe/userforms": "<3", + "silverstripe/versioned-admin": ">=1,<1.11.1", + "simple-updates/phpwhois": "<=1", + "simplesamlphp/saml2": "<1.15.4|>=2,<2.3.8|>=3,<3.1.4", + "simplesamlphp/simplesamlphp": "<1.18.6", + "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", + "simplesamlphp/simplesamlphp-module-openid": "<1", + "simplesamlphp/simplesamlphp-module-openidprovider": "<0.9", + "simplito/elliptic-php": "<1.0.6", + "sitegeist/fluid-components": "<3.5", + "sjbr/sr-freecap": "<2.4.6|>=2.5,<2.5.3", + "slim/psr7": "<1.4.1|>=1.5,<1.5.1|>=1.6,<1.6.1", + "slim/slim": "<2.6", + "slub/slub-events": "<3.0.3", + "smarty/smarty": "<3.1.48|>=4,<4.3.1", + "snipe/snipe-it": "<=6.0.14", + "socalnick/scn-social-auth": "<1.15.2", + "socialiteproviders/steam": "<1.1", + "spatie/browsershot": "<3.57.4", + "spipu/html2pdf": "<5.2.8", + "spoon/library": "<1.4.1", + "spoonity/tcpdf": "<6.2.22", + "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", + "ssddanbrown/bookstack": "<22.02.3", + "statamic/cms": "<4.10", + "stormpath/sdk": "<9.9.99", + "studio-42/elfinder": "<2.1.62", + "subhh/libconnect": "<7.0.8|>=8,<8.1", + "sukohi/surpass": "<1", + "sulu/sulu": "<1.6.44|>=2,<2.2.18|>=2.3,<2.3.8|==2.4.0.0-RC1|>=2.5,<2.5.10", + "sumocoders/framework-user-bundle": "<1.4", + "swag/paypal": "<5.4.4", + "swiftmailer/swiftmailer": ">=4,<5.4.5", + "sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2", + "sylius/grid": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", + "sylius/grid-bundle": "<1.10.1", + "sylius/paypal-plugin": ">=1,<1.2.4|>=1.3,<1.3.1", + "sylius/resource-bundle": "<1.3.14|>=1.4,<1.4.7|>=1.5,<1.5.2|>=1.6,<1.6.4", + "sylius/sylius": "<1.9.10|>=1.10,<1.10.11|>=1.11,<1.11.2", + "symbiote/silverstripe-multivaluefield": ">=3,<3.0.99", + "symbiote/silverstripe-queuedjobs": ">=3,<3.0.2|>=3.1,<3.1.4|>=4,<4.0.7|>=4.1,<4.1.2|>=4.2,<4.2.4|>=4.3,<4.3.3|>=4.4,<4.4.3|>=4.5,<4.5.1|>=4.6,<4.6.4", + "symbiote/silverstripe-seed": "<6.0.3", + "symbiote/silverstripe-versionedfiles": "<=2.0.3", + "symfont/process": ">=0", + "symfony/cache": ">=3.1,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", + "symfony/dependency-injection": ">=2,<2.0.17|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/error-handler": ">=4.4,<4.4.4|>=5,<5.0.4", + "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1", + "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=5.3.14,<=5.3.14|>=5.4.3,<=5.4.3|>=6.0.3,<=6.0.3", + "symfony/http-foundation": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7", + "symfony/http-kernel": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", + "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", + "symfony/maker-bundle": ">=1.27,<1.29.2|>=1.30,<1.31.1", + "symfony/mime": ">=4.3,<4.3.8", + "symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/polyfill": ">=1,<1.10", + "symfony/polyfill-php55": ">=1,<1.10", + "symfony/proxy-manager-bridge": ">=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/routing": ">=2,<2.0.19", + "symfony/security": ">=2,<2.7.51|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.8", + "symfony/security-bundle": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", + "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.9", + "symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", + "symfony/security-guard": ">=2.8,<3.4.48|>=4,<4.4.23|>=5,<5.2.8", + "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.3.2", + "symfony/serializer": ">=2,<2.0.11|>=4.1,<4.4.35|>=5,<5.3.12", + "symfony/symfony": "<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", + "symfony/translation": ">=2,<2.0.17", + "symfony/ux-autocomplete": "<2.11.2", + "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3", + "symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8", + "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", + "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7", + "t3/dce": "<0.11.5|>=2.2,<2.6.2", + "t3g/svg-sanitizer": "<1.0.3", + "tastyigniter/tastyigniter": "<3.3", + "tcg/voyager": "<=1.4", + "tecnickcom/tcpdf": "<6.2.22", + "terminal42/contao-tablelookupwizard": "<3.3.5", + "thelia/backoffice-default-template": ">=2.1,<2.1.2", + "thelia/thelia": ">=2.1,<2.1.3", + "theonedemon/phpwhois": "<=4.2.5", + "thinkcmf/thinkcmf": "<=5.1.7", + "thorsten/phpmyfaq": "<3.2.0.0-beta2", + "tikiwiki/tiki-manager": "<=17.1", + "tinymce/tinymce": "<5.10.7|>=6,<6.3.1", + "tinymighty/wiki-seo": "<1.2.2", + "titon/framework": "<9.9.99", + "tobiasbg/tablepress": "<=2.0.0.0-RC1", + "topthink/framework": "<6.0.14", + "topthink/think": "<=6.1.1", + "topthink/thinkphp": "<=3.2.3", + "tpwd/ke_search": "<4.0.3|>=4.1,<4.6.6|>=5,<5.0.2", + "tribalsystems/zenario": "<=9.4.59197", + "truckersmp/phpwhois": "<=4.3.1", + "ttskch/pagination-service-provider": "<1", + "twig/twig": "<1.44.7|>=2,<2.15.3|>=3,<3.4.3", + "typo3/cms": "<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2", + "typo3/cms-backend": ">=7,<=7.6.50|>=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1", + "typo3/cms-core": "<8.7.51|>=9,<9.5.42|>=10,<10.4.39|>=11,<11.5.30|>=12,<12.4.4", + "typo3/cms-extbase": "<6.2.24|>=7,<7.6.8|==8.1.1", + "typo3/cms-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1", + "typo3/cms-rte-ckeditor": ">=9.5,<9.5.42|>=10,<10.4.39|>=11,<11.5.30", + "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", + "typo3/html-sanitizer": ">=1,<1.5.1|>=2,<2.1.2", + "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.3.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<3.3.23|>=4,<4.0.17|>=4.1,<4.1.16|>=4.2,<4.2.12|>=4.3,<4.3.3", + "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1", + "typo3/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5", + "typo3fluid/fluid": ">=2,<2.0.8|>=2.1,<2.1.7|>=2.2,<2.2.4|>=2.3,<2.3.7|>=2.4,<2.4.4|>=2.5,<2.5.11|>=2.6,<2.6.10", + "ua-parser/uap-php": "<3.8", + "uasoft-indonesia/badaso": "<=2.9.7", + "unisharp/laravel-filemanager": "<=2.5.1", + "userfrosting/userfrosting": ">=0.3.1,<4.6.3", + "usmanhalalit/pixie": "<1.0.3|>=2,<2.0.2", + "uvdesk/community-skeleton": "<=1.1.1", + "vanilla/safecurl": "<0.9.2", + "verot/class.upload.php": "<=1.0.3|>=2,<=2.0.4", + "vova07/yii2-fileapi-widget": "<0.1.9", + "vrana/adminer": "<4.8.1", + "waldhacker/hcaptcha": "<2.1.2", + "wallabag/tcpdf": "<6.2.22", + "wallabag/wallabag": "<2.6.7", + "wanglelecc/laracms": "<=1.0.3", + "web-auth/webauthn-framework": ">=3.3,<3.3.4", + "webbuilders-group/silverstripe-kapost-bridge": "<0.4", + "webcoast/deferred-image-processing": "<1.0.2", + "webklex/laravel-imap": "<5.3", + "webklex/php-imap": "<5.3", + "webpa/webpa": "<3.1.2", + "wikibase/wikibase": "<=1.39.3", + "wikimedia/parsoid": "<0.12.2", + "willdurand/js-translation-bundle": "<2.1.1", + "wintercms/winter": "<1.2.3", + "woocommerce/woocommerce": "<6.6", + "wp-cli/wp-cli": "<2.5", + "wp-graphql/wp-graphql": "<=1.14.5", + "wpanel/wpanel4-cms": "<=4.3.1", + "wpcloud/wp-stateless": "<3.2", + "wwbn/avideo": "<=12.4", + "xataface/xataface": "<3", + "xpressengine/xpressengine": "<3.0.15", + "yeswiki/yeswiki": "<4.1", + "yetiforce/yetiforce-crm": "<=6.4", + "yidashi/yii2cmf": "<=2", + "yii2mod/yii2-cms": "<1.9.2", + "yiisoft/yii": "<1.1.27", + "yiisoft/yii2": "<2.0.38", + "yiisoft/yii2-bootstrap": "<2.0.4", + "yiisoft/yii2-dev": "<2.0.43", + "yiisoft/yii2-elasticsearch": "<2.0.5", + "yiisoft/yii2-gii": "<=2.2.4", + "yiisoft/yii2-jui": "<2.0.4", + "yiisoft/yii2-redis": "<2.0.8", + "yikesinc/yikes-inc-easy-mailchimp-extender": "<6.8.6", + "yoast-seo-for-typo3/yoast_seo": "<7.2.3", + "yourls/yourls": "<=1.8.2", + "zencart/zencart": "<=1.5.7.0-beta", + "zendesk/zendesk_api_client_php": "<2.2.11", + "zendframework/zend-cache": ">=2.4,<2.4.8|>=2.5,<2.5.3", + "zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2", + "zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2", + "zendframework/zend-db": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.10|>=2.3,<2.3.5", + "zendframework/zend-developer-tools": ">=1.2.2,<1.2.3", + "zendframework/zend-diactoros": "<1.8.4", + "zendframework/zend-feed": "<2.10.3", + "zendframework/zend-form": ">=2,<2.2.7|>=2.3,<2.3.1", + "zendframework/zend-http": "<2.8.1", + "zendframework/zend-json": ">=2.1,<2.1.6|>=2.2,<2.2.6", + "zendframework/zend-ldap": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.8|>=2.3,<2.3.3", + "zendframework/zend-mail": ">=2,<2.4.11|>=2.5,<2.7.2", + "zendframework/zend-navigation": ">=2,<2.2.7|>=2.3,<2.3.1", + "zendframework/zend-session": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.9|>=2.3,<2.3.4", + "zendframework/zend-validator": ">=2.3,<2.3.6", + "zendframework/zend-view": ">=2,<2.2.7|>=2.3,<2.3.1", + "zendframework/zend-xmlrpc": ">=2.1,<2.1.6|>=2.2,<2.2.6", + "zendframework/zendframework": "<=3", + "zendframework/zendframework1": "<1.12.20", + "zendframework/zendopenid": "<2.0.2", + "zendframework/zendrest": "<2.0.2", + "zendframework/zendservice-amazon": "<2.0.3", + "zendframework/zendservice-api": "<1", + "zendframework/zendservice-audioscrobbler": "<2.0.2", + "zendframework/zendservice-nirvanix": "<2.0.2", + "zendframework/zendservice-slideshare": "<2.0.2", + "zendframework/zendservice-technorati": "<2.0.2", + "zendframework/zendservice-windowsazure": "<2.0.2", + "zendframework/zendxml": "<1.0.1", + "zenstruck/collection": "<0.2.1", + "zetacomponents/mail": "<1.8.2", + "zf-commons/zfc-user": "<1.2.2", + "zfcampus/zf-apigility-doctrine": ">=1,<1.0.3", + "zfr/zfr-oauth2-server-module": "<0.1.2", + "zoujingli/thinkadmin": "<6.0.22" + }, + "time": "2023-10-06T19:04:00+00:00", + "default-branch": true, + "type": "metapackage", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "role": "maintainer" + }, + { + "name": "Ilya Tribusean", + "email": "slash3b@gmail.com", + "role": "maintainer" + } + ], + "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", + "keywords": [ + "dev" + ], + "support": { + "issues": "https://github.com/Roave/SecurityAdvisories/issues", + "source": "https://github.com/Roave/SecurityAdvisories/tree/latest" + }, + "funding": [ + { + "url": "https://github.com/Ocramius", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/roave/security-advisories", + "type": "tidelift" + } + ], + "install-path": null + } + ], + "dev": true, + "dev-package-names": [ + "roave/security-advisories" + ] +} diff --git a/apps/files_external/3rdparty/composer/installed.php b/apps/files_external/3rdparty/composer/installed.php new file mode 100644 index 000000000000..cbd8034b7dfd --- /dev/null +++ b/apps/files_external/3rdparty/composer/installed.php @@ -0,0 +1,145 @@ + array( + 'name' => 'files_external/3rdparty', + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'reference' => NULL, + 'type' => 'library', + 'install_path' => __DIR__ . '/../', + 'aliases' => array(), + 'dev' => true, + ), + 'versions' => array( + 'files_external/3rdparty' => array( + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'reference' => NULL, + 'type' => 'library', + 'install_path' => __DIR__ . '/../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'firebase/php-jwt' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '^6.8', + ), + ), + 'google/apiclient' => array( + 'pretty_version' => 'v2.15.1', + 'version' => '2.15.1.0', + 'reference' => '7a95ed29e4b6c6859d2d22300c5455a92e2622ad', + 'type' => 'library', + 'install_path' => __DIR__ . '/../google/apiclient', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'google/apiclient-services' => array( + 'pretty_version' => 'v0.319.0', + 'version' => '0.319.0.0', + 'reference' => '9206f4d8cfacbec3c494d68f1758b2c8ca5b179d', + 'type' => 'library', + 'install_path' => __DIR__ . '/../google/apiclient-services', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'google/auth' => array( + 'pretty_version' => 'v1.31.0', + 'version' => '1.31.0.0', + 'reference' => '22209fddd0c06f3f8e3cb4aade0b352aa00f9888', + 'type' => 'library', + 'install_path' => __DIR__ . '/../google/auth', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'guzzlehttp/guzzle' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '^7.7', + ), + ), + 'guzzlehttp/psr7' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '^2.5', + ), + ), + 'icewind/smb' => array( + 'pretty_version' => 'v3.6.0', + 'version' => '3.6.0.0', + 'reference' => 'e0e86b16640f5892dd00408ed50ad18357dac6c1', + 'type' => 'library', + 'install_path' => __DIR__ . '/../icewind/smb', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'icewind/streams' => array( + 'pretty_version' => 'v0.7.7', + 'version' => '0.7.7.0', + 'reference' => '64200fd7cfcc7f550c3c695c48d8fd8bba97fecb', + 'type' => 'library', + 'install_path' => __DIR__ . '/../icewind/streams', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'monolog/monolog' => array( + 'pretty_version' => '3.4.0', + 'version' => '3.4.0.0', + 'reference' => 'e2392369686d420ca32df3803de28b5d6f76867d', + 'type' => 'library', + 'install_path' => __DIR__ . '/../monolog/monolog', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'phpseclib/phpseclib' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '>=3.0.20', + ), + ), + 'psr/cache' => array( + 'pretty_version' => '3.0.0', + 'version' => '3.0.0.0', + 'reference' => 'aa5030cfa5405eccfdcb1083ce040c2cb8d253bf', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/cache', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/http-message' => array( + 'pretty_version' => '2.0', + 'version' => '2.0.0.0', + 'reference' => '402d35bcb92c70c026d1a6a9883f06b2ead23d71', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-message', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/log' => array( + 'pretty_version' => '3.0.0', + 'version' => '3.0.0.0', + 'reference' => 'fe5ea303b0887d5caefd3d431c3e61ad47037001', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/log', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/log-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '3.0.0', + ), + ), + 'roave/security-advisories' => array( + 'pretty_version' => 'dev-latest', + 'version' => 'dev-latest', + 'reference' => '85afa852eeecec97cec35d987dcfbc602c025620', + 'type' => 'metapackage', + 'install_path' => NULL, + 'aliases' => array( + 0 => '9999999-dev', + ), + 'dev_requirement' => true, + ), + ), +); diff --git a/apps/files_external/3rdparty/composer/platform_check.php b/apps/files_external/3rdparty/composer/platform_check.php new file mode 100644 index 000000000000..d32d90c6a98e --- /dev/null +++ b/apps/files_external/3rdparty/composer/platform_check.php @@ -0,0 +1,26 @@ += 80200)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 8.2.0". You are running ' . PHP_VERSION . '.'; +} + +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/apps/files_external/3rdparty/google/auth/src/CredentialSource/AwsNativeSource.php b/apps/files_external/3rdparty/google/auth/src/CredentialSource/AwsNativeSource.php new file mode 100644 index 000000000000..3a8c20eaa634 --- /dev/null +++ b/apps/files_external/3rdparty/google/auth/src/CredentialSource/AwsNativeSource.php @@ -0,0 +1,360 @@ +audience = $audience; + $this->regionalCredVerificationUrl = $regionalCredVerificationUrl; + $this->regionUrl = $regionUrl; + $this->securityCredentialsUrl = $securityCredentialsUrl; + $this->imdsv2SessionTokenUrl = $imdsv2SessionTokenUrl; + } + + public function fetchSubjectToken(callable $httpHandler = null): string + { + if (is_null($httpHandler)) { + $httpHandler = HttpHandlerFactory::build(HttpClientCache::getHttpClient()); + } + + $headers = []; + if ($this->imdsv2SessionTokenUrl) { + $headers = [ + 'X-aws-ec2-metadata-token' => self::getImdsV2SessionToken($this->imdsv2SessionTokenUrl, $httpHandler) + ]; + } + + if (!$signingVars = self::getSigningVarsFromEnv()) { + if (!$this->securityCredentialsUrl) { + throw new \LogicException('Unable to get credentials from ENV, and no security credentials URL provided'); + } + $signingVars = self::getSigningVarsFromUrl( + $httpHandler, + $this->securityCredentialsUrl, + self::getRoleName($httpHandler, $this->securityCredentialsUrl, $headers), + $headers + ); + } + + if (!$region = self::getRegionFromEnv()) { + if (!$this->regionUrl) { + throw new \LogicException('Unable to get region from ENV, and no region URL provided'); + } + $region = self::getRegionFromUrl($httpHandler, $this->regionUrl, $headers); + } + $url = str_replace('{region}', $region, $this->regionalCredVerificationUrl); + $host = parse_url($url)['host'] ?? ''; + + // From here we use the signing vars to create the signed request to receive a token + [$accessKeyId, $secretAccessKey, $securityToken] = $signingVars; + $headers = self::getSignedRequestHeaders($region, $host, $accessKeyId, $secretAccessKey, $securityToken); + + // Inject x-goog-cloud-target-resource into header + $headers['x-goog-cloud-target-resource'] = $this->audience; + + // Format headers as they're expected in the subject token + $formattedHeaders= array_map( + fn ($k, $v) => ['key' => $k, 'value' => $v], + array_keys($headers), + $headers, + ); + + $request = [ + 'headers' => $formattedHeaders, + 'method' => 'POST', + 'url' => $url, + ]; + + return urlencode(json_encode($request) ?: ''); + } + + /** + * @internal + */ + public static function getImdsV2SessionToken(string $imdsV2Url, callable $httpHandler): string + { + $headers = [ + 'X-aws-ec2-metadata-token-ttl-seconds' => '21600' + ]; + $request = new Request( + 'PUT', + $imdsV2Url, + $headers + ); + + $response = $httpHandler($request); + return (string) $response->getBody(); + } + + /** + * @see http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html + * + * @internal + * + * @return array + */ + public static function getSignedRequestHeaders( + string $region, + string $host, + string $accessKeyId, + string $secretAccessKey, + ?string $securityToken + ): array { + $service = 'sts'; + + # Create a date for headers and the credential string in ISO-8601 format + $amzdate = date('Ymd\THis\Z'); + $datestamp = date('Ymd'); # Date w/o time, used in credential scope + + # Create the canonical headers and signed headers. Header names + # must be trimmed and lowercase, and sorted in code point order from + # low to high. Note that there is a trailing \n. + $canonicalHeaders = sprintf("host:%s\nx-amz-date:%s\n", $host, $amzdate); + if ($securityToken) { + $canonicalHeaders .= sprintf("x-amz-security-token:%s\n", $securityToken); + } + + # Step 5: Create the list of signed headers. This lists the headers + # in the canonicalHeaders list, delimited with ";" and in alpha order. + # Note: The request can include any headers; $canonicalHeaders and + # $signedHeaders lists those that you want to be included in the + # hash of the request. "Host" and "x-amz-date" are always required. + $signedHeaders = 'host;x-amz-date'; + if ($securityToken) { + $signedHeaders .= ';x-amz-security-token'; + } + + # Step 6: Create payload hash (hash of the request body content). For GET + # requests, the payload is an empty string (""). + $payloadHash = hash('sha256', ''); + + # Step 7: Combine elements to create canonical request + $canonicalRequest = implode("\n", [ + 'POST', // method + '/', // canonical URL + self::CRED_VERIFICATION_QUERY, // query string + $canonicalHeaders, + $signedHeaders, + $payloadHash + ]); + + # ************* TASK 2: CREATE THE STRING TO SIGN************* + # Match the algorithm to the hashing algorithm you use, either SHA-1 or + # SHA-256 (recommended) + $algorithm = 'AWS4-HMAC-SHA256'; + $scope = implode('/', [$datestamp, $region, $service, 'aws4_request']); + $stringToSign = implode("\n", [$algorithm, $amzdate, $scope, hash('sha256', $canonicalRequest)]); + + # ************* TASK 3: CALCULATE THE SIGNATURE ************* + # Create the signing key using the function defined above. + // (done above) + $signingKey = self::getSignatureKey($secretAccessKey, $datestamp, $region, $service); + + # Sign the string_to_sign using the signing_key + $signature = bin2hex(self::hmacSign($signingKey, $stringToSign)); + + # ************* TASK 4: ADD SIGNING INFORMATION TO THE REQUEST ************* + # The signing information can be either in a query string value or in + # a header named Authorization. This code shows how to use a header. + # Create authorization header and add to request headers + $authorizationHeader = sprintf( + '%s Credential=%s/%s, SignedHeaders=%s, Signature=%s', + $algorithm, + $accessKeyId, + $scope, + $signedHeaders, + $signature + ); + + # The request can include any headers, but MUST include "host", "x-amz-date", + # and (for this scenario) "Authorization". "host" and "x-amz-date" must + # be included in the canonical_headers and signed_headers, as noted + # earlier. Order here is not significant. + $headers = [ + 'host' => $host, + 'x-amz-date' => $amzdate, + 'Authorization' => $authorizationHeader, + ]; + if ($securityToken) { + $headers['x-amz-security-token'] = $securityToken; + } + + return $headers; + } + + /** + * @internal + */ + public static function getRegionFromEnv(): ?string + { + $region = getenv('AWS_REGION'); + if (empty($region)) { + $region = getenv('AWS_DEFAULT_REGION'); + } + return $region ?: null; + } + + /** + * @internal + * + * @param callable $httpHandler + * @param string $regionUrl + * @param array $headers Request headers to send in with the request. + */ + public static function getRegionFromUrl(callable $httpHandler, string $regionUrl, array $headers): string + { + // get the region/zone from the region URL + $regionRequest = new Request('GET', $regionUrl, $headers); + $regionResponse = $httpHandler($regionRequest); + + // Remove last character. For example, if us-east-2b is returned, + // the region would be us-east-2. + return substr((string) $regionResponse->getBody(), 0, -1); + } + + /** + * @internal + * + * @param callable $httpHandler + * @param string $securityCredentialsUrl + * @param array $headers Request headers to send in with the request. + */ + public static function getRoleName(callable $httpHandler, string $securityCredentialsUrl, array $headers): string + { + // Get the AWS role name + $roleRequest = new Request('GET', $securityCredentialsUrl, $headers); + $roleResponse = $httpHandler($roleRequest); + $roleName = (string) $roleResponse->getBody(); + + return $roleName; + } + + /** + * @internal + * + * @param callable $httpHandler + * @param string $securityCredentialsUrl + * @param array $headers Request headers to send in with the request. + * @return array{string, string, ?string} + */ + public static function getSigningVarsFromUrl( + callable $httpHandler, + string $securityCredentialsUrl, + string $roleName, + array $headers + ): array { + // Get the AWS credentials + $credsRequest = new Request( + 'GET', + $securityCredentialsUrl . '/' . $roleName, + $headers + ); + $credsResponse = $httpHandler($credsRequest); + $awsCreds = json_decode((string) $credsResponse->getBody(), true); + return [ + $awsCreds['AccessKeyId'], // accessKeyId + $awsCreds['SecretAccessKey'], // secretAccessKey + $awsCreds['Token'], // token + ]; + } + + /** + * @internal + * + * @return array{string, string, ?string} + */ + public static function getSigningVarsFromEnv(): ?array + { + $accessKeyId = getenv('AWS_ACCESS_KEY_ID'); + $secretAccessKey = getenv('AWS_SECRET_ACCESS_KEY'); + if ($accessKeyId && $secretAccessKey) { + return [ + $accessKeyId, + $secretAccessKey, + getenv('AWS_SESSION_TOKEN') ?: null, // session token (can be null) + ]; + } + + return null; + } + + /** + * Return HMAC hash in binary string + */ + private static function hmacSign(string $key, string $msg): string + { + return hash_hmac('sha256', self::utf8Encode($msg), $key, true); + } + + /** + * @TODO add a fallback when mbstring is not available + */ + private static function utf8Encode(string $string): string + { + return mb_convert_encoding($string, 'UTF-8', 'ISO-8859-1'); + } + + private static function getSignatureKey( + string $key, + string $dateStamp, + string $regionName, + string $serviceName + ): string { + $kDate = self::hmacSign(self::utf8Encode('AWS4' . $key), $dateStamp); + $kRegion = self::hmacSign($kDate, $regionName); + $kService = self::hmacSign($kRegion, $serviceName); + $kSigning = self::hmacSign($kService, 'aws4_request'); + + return $kSigning; + } +} diff --git a/apps/files_external/3rdparty/google/auth/src/Credentials/ExternalAccountCredentials.php b/apps/files_external/3rdparty/google/auth/src/Credentials/ExternalAccountCredentials.php new file mode 100644 index 000000000000..b2716bfaa1d8 --- /dev/null +++ b/apps/files_external/3rdparty/google/auth/src/Credentials/ExternalAccountCredentials.php @@ -0,0 +1,241 @@ + $jsonKey JSON credentials as an associative array. + */ + public function __construct( + $scope, + array $jsonKey + ) { + if (!array_key_exists('type', $jsonKey)) { + throw new InvalidArgumentException('json key is missing the type field'); + } + if ($jsonKey['type'] !== self::EXTERNAL_ACCOUNT_TYPE) { + throw new InvalidArgumentException(sprintf( + 'expected "%s" type but received "%s"', + self::EXTERNAL_ACCOUNT_TYPE, + $jsonKey['type'] + )); + } + + if (!array_key_exists('token_url', $jsonKey)) { + throw new InvalidArgumentException( + 'json key is missing the token_url field' + ); + } + + if (!array_key_exists('audience', $jsonKey)) { + throw new InvalidArgumentException( + 'json key is missing the audience field' + ); + } + + if (!array_key_exists('subject_token_type', $jsonKey)) { + throw new InvalidArgumentException( + 'json key is missing the subject_token_type field' + ); + } + + if (!array_key_exists('credential_source', $jsonKey)) { + throw new InvalidArgumentException( + 'json key is missing the credential_source field' + ); + } + + if (array_key_exists('service_account_impersonation_url', $jsonKey)) { + $this->serviceAccountImpersonationUrl = $jsonKey['service_account_impersonation_url']; + } + + $this->quotaProject = $jsonKey['quota_project_id'] ?? null; + + $this->auth = new OAuth2([ + 'tokenCredentialUri' => $jsonKey['token_url'], + 'audience' => $jsonKey['audience'], + 'scope' => $scope, + 'subjectTokenType' => $jsonKey['subject_token_type'], + 'subjectTokenFetcher' => self::buildCredentialSource($jsonKey), + ]); + } + + /** + * @param array $jsonKey + */ + private static function buildCredentialSource(array $jsonKey): ExternalAccountCredentialSourceInterface + { + $credentialSource = $jsonKey['credential_source']; + if (isset($credentialSource['file'])) { + return new FileSource( + $credentialSource['file'], + $credentialSource['format']['type'] ?? null, + $credentialSource['format']['subject_token_field_name'] ?? null + ); + } + + if ( + isset($credentialSource['environment_id']) + && 1 === preg_match('/^aws(\d+)$/', $credentialSource['environment_id'], $matches) + ) { + if ($matches[1] !== '1') { + throw new InvalidArgumentException( + "aws version \"$matches[1]\" is not supported in the current build." + ); + } + if (!array_key_exists('regional_cred_verification_url', $credentialSource)) { + throw new InvalidArgumentException( + 'The regional_cred_verification_url field is required for aws1 credential source.' + ); + } + if (!array_key_exists('audience', $jsonKey)) { + throw new InvalidArgumentException( + 'aws1 credential source requires an audience to be set in the JSON file.' + ); + } + + return new AwsNativeSource( + $jsonKey['audience'], + $credentialSource['regional_cred_verification_url'], // $regionalCredVerificationUrl + $credentialSource['region_url'] ?? null, // $regionUrl + $credentialSource['url'] ?? null, // $securityCredentialsUrl + $credentialSource['imdsv2_session_token_url'] ?? null, // $imdsV2TokenUrl + ); + } + + if (isset($credentialSource['url'])) { + return new UrlSource( + $credentialSource['url'], + $credentialSource['format']['type'] ?? null, + $credentialSource['format']['subject_token_field_name'] ?? null, + $credentialSource['headers'] ?? null, + ); + } + + throw new InvalidArgumentException('Unable to determine credential source from json key.'); + } + /** + * @param string $stsToken + * @param callable $httpHandler + * + * @return array { + * A set of auth related metadata, containing the following + * + * @type string $access_token + * @type int $expires_at + * } + */ + private function getImpersonatedAccessToken(string $stsToken, callable $httpHandler = null): array + { + if (!isset($this->serviceAccountImpersonationUrl)) { + throw new InvalidArgumentException( + 'service_account_impersonation_url must be set in JSON credentials.' + ); + } + $request = new Request( + 'POST', + $this->serviceAccountImpersonationUrl, + [ + 'Content-Type' => 'application/json', + 'Authorization' => 'Bearer ' . $stsToken, + ], + (string) json_encode([ + 'lifetime' => sprintf('%ss', OAuth2::DEFAULT_EXPIRY_SECONDS), + 'scope' => $this->auth->getScope(), + ]), + ); + if (is_null($httpHandler)) { + $httpHandler = HttpHandlerFactory::build(HttpClientCache::getHttpClient()); + } + $response = $httpHandler($request); + $body = json_decode((string) $response->getBody(), true); + return [ + 'access_token' => $body['accessToken'], + 'expires_at' => strtotime($body['expireTime']), + ]; + } + + /** + * @param callable $httpHandler + * + * @return array { + * A set of auth related metadata, containing the following + * + * @type string $access_token + * @type int $expires_at (impersonated service accounts only) + * @type int $expires_in (identity pool only) + * @type string $issued_token_type (identity pool only) + * @type string $token_type (identity pool only) + * } + */ + public function fetchAuthToken(callable $httpHandler = null) + { + $stsToken = $this->auth->fetchAuthToken($httpHandler); + + if (isset($this->serviceAccountImpersonationUrl)) { + return $this->getImpersonatedAccessToken($stsToken['access_token'], $httpHandler); + } + + return $stsToken; + } + + public function getCacheKey() + { + return $this->auth->getCacheKey(); + } + + public function getLastReceivedToken() + { + return $this->auth->getLastReceivedToken(); + } + + /** + * Get the quota project used for this API request + * + * @return string|null + */ + public function getQuotaProject() + { + return $this->quotaProject; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/CHANGELOG.md b/apps/files_external/3rdparty/monolog/monolog/CHANGELOG.md new file mode 100644 index 000000000000..c08558a808f4 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/CHANGELOG.md @@ -0,0 +1,709 @@ +### 3.3.1 (2023-02-06) + + * Fixed Logger not being serializable anymore (#1792) + +### 3.3.0 (2023-02-06) + + * Deprecated FlowdockHandler & Formatter as the flowdock service was shutdown (#1748) + * Added `ClosureContextProcessor` to allow delaying the creation of context data by setting a Closure in context which is called when the log record is used (#1745) + * Added an ElasticsearchHandler option to set the `op_type` to `create` instead of the default `index` (#1766) + * Added support for enum context values in PsrLogMessageProcessor (#1773) + * Added graylog2/gelf-php 2.x support (#1747) + * Improved `BrowserConsoleHandler` logging to use more appropriate methods than just console.log in the browser (#1739) + * Fixed GitProcessor not filtering correctly based on Level (#1749) + * Fixed `WhatFailureGroupHandler` not catching errors happening inside `close()` (#1791) + * Fixed datetime field in `GoogleCloudLoggingFormatter` (#1758) + * Fixed infinite loop detection within Fibers (#1753) + * Fixed `AmqpHandler->setExtraAttributes` not working with buffering handler wrappers (#1781) + +### 3.2.0 (2022-07-24) + + * Deprecated `CubeHandler` and `PHPConsoleHandler` as both projects are abandoned and those should not be used anymore (#1734) + * Marked `Logger` `@final` as it should not be extended, prefer composition or talk to us if you are missing something + * Added RFC 5424 level (`7` to `0`) support to `Logger::log` and `Logger::addRecord` to increase interoperability (#1723) + * Added `SyslogFormatter` to output syslog-like files which can be consumed by tools like [lnav](https://lnav.org/) (#1689) + * Added support for `__toString` for objects which are not json serializable in `JsonFormatter` (#1733) + * Added `GoogleCloudLoggingFormatter` (#1719) + * Added support for Predis 2.x (#1732) + * Added `AmqpHandler->setExtraAttributes` to allow configuring attributes when using an AMQPExchange (#1724) + * Fixed serialization/unserialization of handlers to make sure private properties are included (#1727) + * Fixed allowInlineLineBreaks in LineFormatter causing issues with windows paths containing `\n` or `\r` sequences (#1720) + * Fixed max normalization depth not being taken into account when formatting exceptions with a deep chain of previous exceptions (#1726) + * Fixed PHP 8.2 deprecation warnings (#1722) + * Fixed rare race condition or filesystem issue where StreamHandler is unable to create the directory the log should go into yet it exists already (#1678) + +### 3.1.0 (2022-06-09) + + * Added `$datetime` parameter to `Logger::addRecord` as low level API to allow logging into the past or future (#1682) + * Added `Logger::useLoggingLoopDetection` to allow disabling cyclic logging detection in concurrent frameworks (#1681) + * Fixed handling of fatal errors if callPrevious is disabled in ErrorHandler (#1670) + * Fixed interop issue by removing the need for a return type in ProcessorInterface (#1680) + * Marked the reusable `Monolog\Test\TestCase` class as `@internal` to make sure PHPStorm does not show it above PHPUnit, you may still use it to test your own handlers/etc though (#1677) + * Fixed RotatingFileHandler issue when the date format contained slashes (#1671) + +### 3.0.0 (2022-05-10) + +Changes from RC1 + +- The `Monolog\LevelName` enum does not exist anymore, use `Monolog\Level->getName()` instead. + +### 3.0.0-RC1 (2022-05-08) + +This is mostly a cleanup release offering stronger type guarantees for integrators with the +array->object/enum changes, but there is no big new feature for end users. + +See [UPGRADE notes](UPGRADE.md#300) for details on all breaking changes especially if you are extending/implementing Monolog classes/interfaces. + +Noteworthy BC Breaks: + +- The minimum supported PHP version is now `8.1.0`. +- Log records have been converted from an array to a [`Monolog\LogRecord` object](src/Monolog/LogRecord.php) + with public (and mostly readonly) properties. e.g. instead of doing + `$record['context']` use `$record->context`. + In formatters or handlers if you rather need an array to work with you can use `$record->toArray()` + to get back a Monolog 1/2 style record array. This will contain the enum values instead of enum cases + in the `level` and `level_name` keys to be more backwards compatible and use simpler data types. +- `FormatterInterface`, `HandlerInterface`, `ProcessorInterface`, etc. changed to contain `LogRecord $record` + instead of `array $record` parameter types. If you want to support multiple Monolog versions this should + be possible by type-hinting nothing, or `array|LogRecord` if you support PHP 8.0+. You can then code + against the $record using Monolog 2 style as LogRecord implements ArrayAccess for BC. + The interfaces do not require a `LogRecord` return type even where it would be applicable, but if you only + support Monolog 3 in integration code I would recommend you use `LogRecord` return types wherever fitting + to ensure forward compatibility as it may be added in Monolog 4. +- Log levels are now enums [`Monolog\Level`](src/Monolog/Level.php) and [`Monolog\LevelName`](src/Monolog/LevelName.php) +- Removed deprecated SwiftMailerHandler, migrate to SymfonyMailerHandler instead. +- `ResettableInterface::reset()` now requires a void return type. +- All properties have had types added, which may require you to do so as well if you extended + a Monolog class and declared the same property. + +New deprecations: + +- `Logger::DEBUG`, `Logger::ERROR`, etc. are now deprecated in favor of the `Monolog\Level` enum. + e.g. instead of `Logger::WARNING` use `Level::Warning` if you need to pass the enum case + to Monolog or one of its handlers, or `Level::Warning->value` if you need the integer + value equal to what `Logger::WARNING` was giving you. +- `Logger::getLevelName()` is now deprecated. + +### 2.9.1 (2023-02-06) + + * Fixed Logger not being serializable anymore (#1792) + +### 2.9.0 (2023-02-05) + + * Deprecated FlowdockHandler & Formatter as the flowdock service was shutdown (#1748) + * Added support for enum context values in PsrLogMessageProcessor (#1773) + * Added graylog2/gelf-php 2.x support (#1747) + * Improved `BrowserConsoleHandler` logging to use more appropriate methods than just console.log in the browser (#1739) + * Fixed `WhatFailureGroupHandler` not catching errors happening inside `close()` (#1791) + * Fixed datetime field in `GoogleCloudLoggingFormatter` (#1758) + * Fixed infinite loop detection within Fibers (#1753) + * Fixed `AmqpHandler->setExtraAttributes` not working with buffering handler wrappers (#1781) + +### 2.8.0 (2022-07-24) + + * Deprecated `CubeHandler` and `PHPConsoleHandler` as both projects are abandoned and those should not be used anymore (#1734) + * Added RFC 5424 level (`7` to `0`) support to `Logger::log` and `Logger::addRecord` to increase interoperability (#1723) + * Added support for `__toString` for objects which are not json serializable in `JsonFormatter` (#1733) + * Added `GoogleCloudLoggingFormatter` (#1719) + * Added support for Predis 2.x (#1732) + * Added `AmqpHandler->setExtraAttributes` to allow configuring attributes when using an AMQPExchange (#1724) + * Fixed serialization/unserialization of handlers to make sure private properties are included (#1727) + * Fixed allowInlineLineBreaks in LineFormatter causing issues with windows paths containing `\n` or `\r` sequences (#1720) + * Fixed max normalization depth not being taken into account when formatting exceptions with a deep chain of previous exceptions (#1726) + * Fixed PHP 8.2 deprecation warnings (#1722) + * Fixed rare race condition or filesystem issue where StreamHandler is unable to create the directory the log should go into yet it exists already (#1678) + +### 2.7.0 (2022-06-09) + + * Added `$datetime` parameter to `Logger::addRecord` as low level API to allow logging into the past or future (#1682) + * Added `Logger::useLoggingLoopDetection` to allow disabling cyclic logging detection in concurrent frameworks (#1681) + * Fixed handling of fatal errors if callPrevious is disabled in ErrorHandler (#1670) + * Marked the reusable `Monolog\Test\TestCase` class as `@internal` to make sure PHPStorm does not show it above PHPUnit, you may still use it to test your own handlers/etc though (#1677) + * Fixed RotatingFileHandler issue when the date format contained slashes (#1671) + +### 2.6.0 (2022-05-10) + + * Deprecated `SwiftMailerHandler`, use `SymfonyMailerHandler` instead + * Added `SymfonyMailerHandler` (#1663) + * Added ElasticSearch 8.x support to the ElasticsearchHandler (#1662) + * Added a way to filter/modify stack traces in LineFormatter (#1665) + * Fixed UdpSocket not being able to reopen/reconnect after close() + * Fixed infinite loops if a Handler is triggering logging while handling log records + +### 2.5.0 (2022-04-08) + + * Added `callType` to IntrospectionProcessor (#1612) + * Fixed AsMonologProcessor syntax to be compatible with PHP 7.2 (#1651) + +### 2.4.0 (2022-03-14) + + * Added [`Monolog\LogRecord`](src/Monolog/LogRecord.php) interface that can be used to type-hint records like `array|\Monolog\LogRecord $record` to be forward compatible with the upcoming Monolog 3 changes + * Added `includeStacktraces` constructor params to LineFormatter & JsonFormatter (#1603) + * Added `persistent`, `timeout`, `writingTimeout`, `connectionTimeout`, `chunkSize` constructor params to SocketHandler and derivatives (#1600) + * Added `AsMonologProcessor` PHP attribute which can help autowiring / autoconfiguration of processors if frameworks / integrations decide to make use of it. This is useless when used purely with Monolog (#1637) + * Added support for keeping native BSON types as is in MongoDBFormatter (#1620) + * Added support for a `user_agent` key in WebProcessor, disabled by default but you can use it by configuring the $extraFields you want (#1613) + * Added support for username/userIcon in SlackWebhookHandler (#1617) + * Added extension points to BrowserConsoleHandler (#1593) + * Added record message/context/extra info to exceptions thrown when a StreamHandler cannot open its stream to avoid completely losing the data logged (#1630) + * Fixed error handler signature to accept a null $context which happens with internal PHP errors (#1614) + * Fixed a few setter methods not returning `self` (#1609) + * Fixed handling of records going over the max Telegram message length (#1616) + +### 2.3.5 (2021-10-01) + + * Fixed regression in StreamHandler since 2.3.3 on systems with the memory_limit set to >=20GB (#1592) + +### 2.3.4 (2021-09-15) + + * Fixed support for psr/log 3.x (#1589) + +### 2.3.3 (2021-09-14) + + * Fixed memory usage when using StreamHandler and calling stream_get_contents on the resource you passed to it (#1578, #1577) + * Fixed support for psr/log 2.x (#1587) + * Fixed some type annotations + +### 2.3.2 (2021-07-23) + + * Fixed compatibility with PHP 7.2 - 7.4 when experiencing PCRE errors (#1568) + +### 2.3.1 (2021-07-14) + + * Fixed Utils::getClass handling of anonymous classes not being fully compatible with PHP 8 (#1563) + * Fixed some `@inheritDoc` annotations having the wrong case + +### 2.3.0 (2021-07-05) + + * Added a ton of PHPStan type annotations as well as type aliases on Monolog\Logger for Record, Level and LevelName that you can import (#1557) + * Added ability to customize date format when using JsonFormatter (#1561) + * Fixed FilterHandler not calling reset on its internal handler when reset() is called on it (#1531) + * Fixed SyslogUdpHandler not setting the timezone correctly on DateTimeImmutable instances (#1540) + * Fixed StreamHandler thread safety - chunk size set to 2GB now to avoid interlacing when doing concurrent writes (#1553) + +### 2.2.0 (2020-12-14) + + * Added JSON_PARTIAL_OUTPUT_ON_ERROR to default json encoding flags, to avoid dropping entire context data or even records due to an invalid subset of it somewhere + * Added setDateFormat to NormalizerFormatter (and Line/Json formatters by extension) to allow changing this after object creation + * Added RedisPubSubHandler to log records to a Redis channel using PUBLISH + * Added support for Elastica 7, and deprecated the $type argument of ElasticaFormatter which is not in use anymore as of Elastica 7 + * Added support for millisecond write timeouts in SocketHandler, you can now pass floats to setWritingTimeout, e.g. 0.2 is 200ms + * Added support for unix sockets in SyslogUdpHandler (set $port to 0 to make the $host a unix socket) + * Added handleBatch support for TelegramBotHandler + * Added RFC5424e extended date format including milliseconds to SyslogUdpHandler + * Added support for configuring handlers with numeric level values in strings (coming from e.g. env vars) + * Fixed Wildfire/FirePHP/ChromePHP handling of unicode characters + * Fixed PHP 8 issues in SyslogUdpHandler + * Fixed internal type error when mbstring is missing + +### 2.1.1 (2020-07-23) + + * Fixed removing of json encoding options + * Fixed type hint of $level not accepting strings in SendGridHandler and OverflowHandler + * Fixed SwiftMailerHandler not accepting email templates with an empty subject + * Fixed array access on null in RavenHandler + * Fixed unique_id in WebProcessor not being disableable + +### 2.1.0 (2020-05-22) + + * Added `JSON_INVALID_UTF8_SUBSTITUTE` to default json flags, so that invalid UTF8 characters now get converted to [�](https://en.wikipedia.org/wiki/Specials_(Unicode_block)#Replacement_character) instead of being converted from ISO-8859-15 to UTF8 as it was before, which was hardly a comprehensive solution + * Added `$ignoreEmptyContextAndExtra` option to JsonFormatter to skip empty context/extra entirely from the output + * Added `$parseMode`, `$disableWebPagePreview` and `$disableNotification` options to TelegramBotHandler + * Added tentative support for PHP 8 + * NormalizerFormatter::addJsonEncodeOption and removeJsonEncodeOption are now public to allow modifying default json flags + * Fixed GitProcessor type error when there is no git repo present + * Fixed normalization of SoapFault objects containing deeply nested objects as "detail" + * Fixed support for relative paths in RotatingFileHandler + +### 2.0.2 (2019-12-20) + + * Fixed ElasticsearchHandler swallowing exceptions details when failing to index log records + * Fixed normalization of SoapFault objects containing non-strings as "detail" in LineFormatter + * Fixed formatting of resources in JsonFormatter + * Fixed RedisHandler failing to use MULTI properly when passed a proxied Redis instance (e.g. in Symfony with lazy services) + * Fixed FilterHandler triggering a notice when handleBatch was filtering all records passed to it + * Fixed Turkish locale messing up the conversion of level names to their constant values + +### 2.0.1 (2019-11-13) + + * Fixed normalization of Traversables to avoid traversing them as not all of them are rewindable + * Fixed setFormatter/getFormatter to forward to the nested handler in FilterHandler, FingersCrossedHandler, BufferHandler, OverflowHandler and SamplingHandler + * Fixed BrowserConsoleHandler formatting when using multiple styles + * Fixed normalization of exception codes to be always integers even for PDOException which have them as numeric strings + * Fixed normalization of SoapFault objects containing non-strings as "detail" + * Fixed json encoding across all handlers to always attempt recovery of non-UTF-8 strings instead of failing the whole encoding + * Fixed ChromePHPHandler to avoid sending more data than latest Chrome versions allow in headers (4KB down from 256KB). + * Fixed type error in BrowserConsoleHandler when the context array of log records was not associative. + +### 2.0.0 (2019-08-30) + + * BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release + * BC Break: Logger methods log/debug/info/notice/warning/error/critical/alert/emergency now have explicit void return types + * Added FallbackGroupHandler which works like the WhatFailureGroupHandler but stops dispatching log records as soon as one handler accepted it + * Fixed support for UTF-8 when cutting strings to avoid cutting a multibyte-character in half + * Fixed normalizers handling of exception backtraces to avoid serializing arguments in some cases + * Fixed date timezone handling in SyslogUdpHandler + +### 2.0.0-beta2 (2019-07-06) + + * BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release + * BC Break: PHP 7.2 is now the minimum required PHP version. + * BC Break: Removed SlackbotHandler, RavenHandler and HipChatHandler, see [UPGRADE.md](UPGRADE.md) for details + * Added OverflowHandler which will only flush log records to its nested handler when reaching a certain amount of logs (i.e. only pass through when things go really bad) + * Added TelegramBotHandler to log records to a [Telegram](https://core.telegram.org/bots/api) bot account + * Added support for JsonSerializable when normalizing exceptions + * Added support for RFC3164 (outdated BSD syslog protocol) to SyslogUdpHandler + * Added SoapFault details to formatted exceptions + * Fixed DeduplicationHandler silently failing to start when file could not be opened + * Fixed issue in GroupHandler and WhatFailureGroupHandler where setting multiple processors would duplicate records + * Fixed GelfFormatter losing some data when one attachment was too long + * Fixed issue in SignalHandler restarting syscalls functionality + * Improved performance of LogglyHandler when sending multiple logs in a single request + +### 2.0.0-beta1 (2018-12-08) + + * BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release + * BC Break: PHP 7.1 is now the minimum required PHP version. + * BC Break: Quite a few interface changes, only relevant if you implemented your own handlers/processors/formatters + * BC Break: Removed non-PSR-3 methods to add records, all the `add*` (e.g. `addWarning`) methods as well as `emerg`, `crit`, `err` and `warn` + * BC Break: The record timezone is now set per Logger instance and not statically anymore + * BC Break: There is no more default handler configured on empty Logger instances + * BC Break: ElasticSearchHandler renamed to ElasticaHandler + * BC Break: Various handler-specific breaks, see [UPGRADE.md](UPGRADE.md) for details + * Added scalar type hints and return hints in all the places it was possible. Switched strict_types on for more reliability. + * Added DateTimeImmutable support, all record datetime are now immutable, and will toString/json serialize with the correct date format, including microseconds (unless disabled) + * Added timezone and microseconds to the default date format + * Added SendGridHandler to use the SendGrid API to send emails + * Added LogmaticHandler to use the Logmatic.io API to store log records + * Added SqsHandler to send log records to an AWS SQS queue + * Added ElasticsearchHandler to send records via the official ES library. Elastica users should now use ElasticaHandler instead of ElasticSearchHandler + * Added NoopHandler which is similar to the NullHandle but does not prevent the bubbling of log records to handlers further down the configuration, useful for temporarily disabling a handler in configuration files + * Added ProcessHandler to write log output to the STDIN of a given process + * Added HostnameProcessor that adds the machine's hostname to log records + * Added a `$dateFormat` option to the PsrLogMessageProcessor which lets you format DateTime instances nicely + * Added support for the PHP 7.x `mongodb` extension in the MongoDBHandler + * Fixed many minor issues in various handlers, and probably added a few regressions too + +### 1.26.1 (2021-05-28) + + * Fixed PHP 8.1 deprecation warning + +### 1.26.0 (2020-12-14) + + * Added $dateFormat and $removeUsedContextFields arguments to PsrLogMessageProcessor (backport from 2.x) + +### 1.25.5 (2020-07-23) + + * Fixed array access on null in RavenHandler + * Fixed unique_id in WebProcessor not being disableable + +### 1.25.4 (2020-05-22) + + * Fixed GitProcessor type error when there is no git repo present + * Fixed normalization of SoapFault objects containing deeply nested objects as "detail" + * Fixed support for relative paths in RotatingFileHandler + +### 1.25.3 (2019-12-20) + + * Fixed formatting of resources in JsonFormatter + * Fixed RedisHandler failing to use MULTI properly when passed a proxied Redis instance (e.g. in Symfony with lazy services) + * Fixed FilterHandler triggering a notice when handleBatch was filtering all records passed to it + * Fixed Turkish locale messing up the conversion of level names to their constant values + +### 1.25.2 (2019-11-13) + + * Fixed normalization of Traversables to avoid traversing them as not all of them are rewindable + * Fixed setFormatter/getFormatter to forward to the nested handler in FilterHandler, FingersCrossedHandler, BufferHandler and SamplingHandler + * Fixed BrowserConsoleHandler formatting when using multiple styles + * Fixed normalization of exception codes to be always integers even for PDOException which have them as numeric strings + * Fixed normalization of SoapFault objects containing non-strings as "detail" + * Fixed json encoding across all handlers to always attempt recovery of non-UTF-8 strings instead of failing the whole encoding + +### 1.25.1 (2019-09-06) + + * Fixed forward-compatible interfaces to be compatible with Monolog 1.x too. + +### 1.25.0 (2019-09-06) + + * Deprecated SlackbotHandler, use SlackWebhookHandler or SlackHandler instead + * Deprecated RavenHandler, use sentry/sentry 2.x and their Sentry\Monolog\Handler instead + * Deprecated HipChatHandler, migrate to Slack and use SlackWebhookHandler or SlackHandler instead + * Added forward-compatible interfaces and traits FormattableHandlerInterface, FormattableHandlerTrait, ProcessableHandlerInterface, ProcessableHandlerTrait. If you use modern PHP and want to make code compatible with Monolog 1 and 2 this can help. You will have to require at least Monolog 1.25 though. + * Added support for RFC3164 (outdated BSD syslog protocol) to SyslogUdpHandler + * Fixed issue in GroupHandler and WhatFailureGroupHandler where setting multiple processors would duplicate records + * Fixed issue in SignalHandler restarting syscalls functionality + * Fixed normalizers handling of exception backtraces to avoid serializing arguments in some cases + * Fixed ZendMonitorHandler to work with the latest Zend Server versions + * Fixed ChromePHPHandler to avoid sending more data than latest Chrome versions allow in headers (4KB down from 256KB). + +### 1.24.0 (2018-11-05) + + * BC Notice: If you are extending any of the Monolog's Formatters' `normalize` method, make sure you add the new `$depth = 0` argument to your function signature to avoid strict PHP warnings. + * Added a `ResettableInterface` in order to reset/reset/clear/flush handlers and processors + * Added a `ProcessorInterface` as an optional way to label a class as being a processor (mostly useful for autowiring dependency containers) + * Added a way to log signals being received using Monolog\SignalHandler + * Added ability to customize error handling at the Logger level using Logger::setExceptionHandler + * Added InsightOpsHandler to migrate users of the LogEntriesHandler + * Added protection to NormalizerFormatter against circular and very deep structures, it now stops normalizing at a depth of 9 + * Added capture of stack traces to ErrorHandler when logging PHP errors + * Added RavenHandler support for a `contexts` context or extra key to forward that to Sentry's contexts + * Added forwarding of context info to FluentdFormatter + * Added SocketHandler::setChunkSize to override the default chunk size in case you must send large log lines to rsyslog for example + * Added ability to extend/override BrowserConsoleHandler + * Added SlackWebhookHandler::getWebhookUrl and SlackHandler::getToken to enable class extensibility + * Added SwiftMailerHandler::getSubjectFormatter to enable class extensibility + * Dropped official support for HHVM in test builds + * Fixed normalization of exception traces when call_user_func is used to avoid serializing objects and the data they contain + * Fixed naming of fields in Slack handler, all field names are now capitalized in all cases + * Fixed HipChatHandler bug where slack dropped messages randomly + * Fixed normalization of objects in Slack handlers + * Fixed support for PHP7's Throwable in NewRelicHandler + * Fixed race bug when StreamHandler sometimes incorrectly reported it failed to create a directory + * Fixed table row styling issues in HtmlFormatter + * Fixed RavenHandler dropping the message when logging exception + * Fixed WhatFailureGroupHandler skipping processors when using handleBatch + and implement it where possible + * Fixed display of anonymous class names + +### 1.23.0 (2017-06-19) + + * Improved SyslogUdpHandler's support for RFC5424 and added optional `$ident` argument + * Fixed GelfHandler truncation to be per field and not per message + * Fixed compatibility issue with PHP <5.3.6 + * Fixed support for headless Chrome in ChromePHPHandler + * Fixed support for latest Aws SDK in DynamoDbHandler + * Fixed support for SwiftMailer 6.0+ in SwiftMailerHandler + +### 1.22.1 (2017-03-13) + + * Fixed lots of minor issues in the new Slack integrations + * Fixed support for allowInlineLineBreaks in LineFormatter when formatting exception backtraces + +### 1.22.0 (2016-11-26) + + * Added SlackbotHandler and SlackWebhookHandler to set up Slack integration more easily + * Added MercurialProcessor to add mercurial revision and branch names to log records + * Added support for AWS SDK v3 in DynamoDbHandler + * Fixed fatal errors occurring when normalizing generators that have been fully consumed + * Fixed RollbarHandler to include a level (rollbar level), monolog_level (original name), channel and datetime (unix) + * Fixed RollbarHandler not flushing records automatically, calling close() explicitly is not necessary anymore + * Fixed SyslogUdpHandler to avoid sending empty frames + * Fixed a few PHP 7.0 and 7.1 compatibility issues + +### 1.21.0 (2016-07-29) + + * Break: Reverted the addition of $context when the ErrorHandler handles regular php errors from 1.20.0 as it was causing issues + * Added support for more formats in RotatingFileHandler::setFilenameFormat as long as they have Y, m and d in order + * Added ability to format the main line of text the SlackHandler sends by explicitly setting a formatter on the handler + * Added information about SoapFault instances in NormalizerFormatter + * Added $handleOnlyReportedErrors option on ErrorHandler::registerErrorHandler (default true) to allow logging of all errors no matter the error_reporting level + +### 1.20.0 (2016-07-02) + + * Added FingersCrossedHandler::activate() to manually trigger the handler regardless of the activation policy + * Added StreamHandler::getUrl to retrieve the stream's URL + * Added ability to override addRow/addTitle in HtmlFormatter + * Added the $context to context information when the ErrorHandler handles a regular php error + * Deprecated RotatingFileHandler::setFilenameFormat to only support 3 formats: Y, Y-m and Y-m-d + * Fixed WhatFailureGroupHandler to work with PHP7 throwables + * Fixed a few minor bugs + +### 1.19.0 (2016-04-12) + + * Break: StreamHandler will not close streams automatically that it does not own. If you pass in a stream (not a path/url), then it will not close it for you. You can retrieve those using getStream() if needed + * Added DeduplicationHandler to remove duplicate records from notifications across multiple requests, useful for email or other notifications on errors + * Added ability to use `%message%` and other LineFormatter replacements in the subject line of emails sent with NativeMailHandler and SwiftMailerHandler + * Fixed HipChatHandler handling of long messages + +### 1.18.2 (2016-04-02) + + * Fixed ElasticaFormatter to use more precise dates + * Fixed GelfMessageFormatter sending too long messages + +### 1.18.1 (2016-03-13) + + * Fixed SlackHandler bug where slack dropped messages randomly + * Fixed RedisHandler issue when using with the PHPRedis extension + * Fixed AmqpHandler content-type being incorrectly set when using with the AMQP extension + * Fixed BrowserConsoleHandler regression + +### 1.18.0 (2016-03-01) + + * Added optional reduction of timestamp precision via `Logger->useMicrosecondTimestamps(false)`, disabling it gets you a bit of performance boost but reduces the precision to the second instead of microsecond + * Added possibility to skip some extra stack frames in IntrospectionProcessor if you have some library wrapping Monolog that is always adding frames + * Added `Logger->withName` to clone a logger (keeping all handlers) with a new name + * Added FluentdFormatter for the Fluentd unix socket protocol + * Added HandlerWrapper base class to ease the creation of handler wrappers, just extend it and override as needed + * Added support for replacing context sub-keys using `%context.*%` in LineFormatter + * Added support for `payload` context value in RollbarHandler + * Added setRelease to RavenHandler to describe the application version, sent with every log + * Added support for `fingerprint` context value in RavenHandler + * Fixed JSON encoding errors that would gobble up the whole log record, we now handle those more gracefully by dropping chars as needed + * Fixed write timeouts in SocketHandler and derivatives, set to 10sec by default, lower it with `setWritingTimeout()` + * Fixed PHP7 compatibility with regard to Exception/Throwable handling in a few places + +### 1.17.2 (2015-10-14) + + * Fixed ErrorHandler compatibility with non-Monolog PSR-3 loggers + * Fixed SlackHandler handling to use slack functionalities better + * Fixed SwiftMailerHandler bug when sending multiple emails they all had the same id + * Fixed 5.3 compatibility regression + +### 1.17.1 (2015-08-31) + + * Fixed RollbarHandler triggering PHP notices + +### 1.17.0 (2015-08-30) + + * Added support for `checksum` and `release` context/extra values in RavenHandler + * Added better support for exceptions in RollbarHandler + * Added UidProcessor::getUid + * Added support for showing the resource type in NormalizedFormatter + * Fixed IntrospectionProcessor triggering PHP notices + +### 1.16.0 (2015-08-09) + + * Added IFTTTHandler to notify ifttt.com triggers + * Added Logger::setHandlers() to allow setting/replacing all handlers + * Added $capSize in RedisHandler to cap the log size + * Fixed StreamHandler creation of directory to only trigger when the first log write happens + * Fixed bug in the handling of curl failures + * Fixed duplicate logging of fatal errors when both error and fatal error handlers are registered in monolog's ErrorHandler + * Fixed missing fatal errors records with handlers that need to be closed to flush log records + * Fixed TagProcessor::addTags support for associative arrays + +### 1.15.0 (2015-07-12) + + * Added addTags and setTags methods to change a TagProcessor + * Added automatic creation of directories if they are missing for a StreamHandler to open a log file + * Added retry functionality to Loggly, Cube and Mandrill handlers so they retry up to 5 times in case of network failure + * Fixed process exit code being incorrectly reset to 0 if ErrorHandler::registerExceptionHandler was used + * Fixed HTML/JS escaping in BrowserConsoleHandler + * Fixed JSON encoding errors being silently suppressed (PHP 5.5+ only) + +### 1.14.0 (2015-06-19) + + * Added PHPConsoleHandler to send record to Chrome's PHP Console extension and library + * Added support for objects implementing __toString in the NormalizerFormatter + * Added support for HipChat's v2 API in HipChatHandler + * Added Logger::setTimezone() to initialize the timezone monolog should use in case date.timezone isn't correct for your app + * Added an option to send formatted message instead of the raw record on PushoverHandler via ->useFormattedMessage(true) + * Fixed curl errors being silently suppressed + +### 1.13.1 (2015-03-09) + + * Fixed regression in HipChat requiring a new token to be created + +### 1.13.0 (2015-03-05) + + * Added Registry::hasLogger to check for the presence of a logger instance + * Added context.user support to RavenHandler + * Added HipChat API v2 support in the HipChatHandler + * Added NativeMailerHandler::addParameter to pass params to the mail() process + * Added context data to SlackHandler when $includeContextAndExtra is true + * Added ability to customize the Swift_Message per-email in SwiftMailerHandler + * Fixed SwiftMailerHandler to lazily create message instances if a callback is provided + * Fixed serialization of INF and NaN values in Normalizer and LineFormatter + +### 1.12.0 (2014-12-29) + + * Break: HandlerInterface::isHandling now receives a partial record containing only a level key. This was always the intent and does not break any Monolog handler but is strictly speaking a BC break and you should check if you relied on any other field in your own handlers. + * Added PsrHandler to forward records to another PSR-3 logger + * Added SamplingHandler to wrap around a handler and include only every Nth record + * Added MongoDBFormatter to support better storage with MongoDBHandler (it must be enabled manually for now) + * Added exception codes in the output of most formatters + * Added LineFormatter::includeStacktraces to enable exception stack traces in logs (uses more than one line) + * Added $useShortAttachment to SlackHandler to minify attachment size and $includeExtra to append extra data + * Added $host to HipChatHandler for users of private instances + * Added $transactionName to NewRelicHandler and support for a transaction_name context value + * Fixed MandrillHandler to avoid outputting API call responses + * Fixed some non-standard behaviors in SyslogUdpHandler + +### 1.11.0 (2014-09-30) + + * Break: The NewRelicHandler extra and context data are now prefixed with extra_ and context_ to avoid clashes. Watch out if you have scripts reading those from the API and rely on names + * Added WhatFailureGroupHandler to suppress any exception coming from the wrapped handlers and avoid chain failures if a logging service fails + * Added MandrillHandler to send emails via the Mandrillapp.com API + * Added SlackHandler to log records to a Slack.com account + * Added FleepHookHandler to log records to a Fleep.io account + * Added LogglyHandler::addTag to allow adding tags to an existing handler + * Added $ignoreEmptyContextAndExtra to LineFormatter to avoid empty [] at the end + * Added $useLocking to StreamHandler and RotatingFileHandler to enable flock() while writing + * Added support for PhpAmqpLib in the AmqpHandler + * Added FingersCrossedHandler::clear and BufferHandler::clear to reset them between batches in long running jobs + * Added support for adding extra fields from $_SERVER in the WebProcessor + * Fixed support for non-string values in PrsLogMessageProcessor + * Fixed SwiftMailer messages being sent with the wrong date in long running scripts + * Fixed minor PHP 5.6 compatibility issues + * Fixed BufferHandler::close being called twice + +### 1.10.0 (2014-06-04) + + * Added Logger::getHandlers() and Logger::getProcessors() methods + * Added $passthruLevel argument to FingersCrossedHandler to let it always pass some records through even if the trigger level is not reached + * Added support for extra data in NewRelicHandler + * Added $expandNewlines flag to the ErrorLogHandler to create multiple log entries when a message has multiple lines + +### 1.9.1 (2014-04-24) + + * Fixed regression in RotatingFileHandler file permissions + * Fixed initialization of the BufferHandler to make sure it gets flushed after receiving records + * Fixed ChromePHPHandler and FirePHPHandler's activation strategies to be more conservative + +### 1.9.0 (2014-04-20) + + * Added LogEntriesHandler to send logs to a LogEntries account + * Added $filePermissions to tweak file mode on StreamHandler and RotatingFileHandler + * Added $useFormatting flag to MemoryProcessor to make it send raw data in bytes + * Added support for table formatting in FirePHPHandler via the table context key + * Added a TagProcessor to add tags to records, and support for tags in RavenHandler + * Added $appendNewline flag to the JsonFormatter to enable using it when logging to files + * Added sound support to the PushoverHandler + * Fixed multi-threading support in StreamHandler + * Fixed empty headers issue when ChromePHPHandler received no records + * Fixed default format of the ErrorLogHandler + +### 1.8.0 (2014-03-23) + + * Break: the LineFormatter now strips newlines by default because this was a bug, set $allowInlineLineBreaks to true if you need them + * Added BrowserConsoleHandler to send logs to any browser's console via console.log() injection in the output + * Added FilterHandler to filter records and only allow those of a given list of levels through to the wrapped handler + * Added FlowdockHandler to send logs to a Flowdock account + * Added RollbarHandler to send logs to a Rollbar account + * Added HtmlFormatter to send prettier log emails with colors for each log level + * Added GitProcessor to add the current branch/commit to extra record data + * Added a Monolog\Registry class to allow easier global access to pre-configured loggers + * Added support for the new official graylog2/gelf-php lib for GelfHandler, upgrade if you can by replacing the mlehner/gelf-php requirement + * Added support for HHVM + * Added support for Loggly batch uploads + * Added support for tweaking the content type and encoding in NativeMailerHandler + * Added $skipClassesPartials to tweak the ignored classes in the IntrospectionProcessor + * Fixed batch request support in GelfHandler + +### 1.7.0 (2013-11-14) + + * Added ElasticSearchHandler to send logs to an Elastic Search server + * Added DynamoDbHandler and ScalarFormatter to send logs to Amazon's Dynamo DB + * Added SyslogUdpHandler to send logs to a remote syslogd server + * Added LogglyHandler to send logs to a Loggly account + * Added $level to IntrospectionProcessor so it only adds backtraces when needed + * Added $version to LogstashFormatter to allow using the new v1 Logstash format + * Added $appName to NewRelicHandler + * Added configuration of Pushover notification retries/expiry + * Added $maxColumnWidth to NativeMailerHandler to change the 70 chars default + * Added chainability to most setters for all handlers + * Fixed RavenHandler batch processing so it takes the message from the record with highest priority + * Fixed HipChatHandler batch processing so it sends all messages at once + * Fixed issues with eAccelerator + * Fixed and improved many small things + +### 1.6.0 (2013-07-29) + + * Added HipChatHandler to send logs to a HipChat chat room + * Added ErrorLogHandler to send logs to PHP's error_log function + * Added NewRelicHandler to send logs to NewRelic's service + * Added Monolog\ErrorHandler helper class to register a Logger as exception/error/fatal handler + * Added ChannelLevelActivationStrategy for the FingersCrossedHandler to customize levels by channel + * Added stack traces output when normalizing exceptions (json output & co) + * Added Monolog\Logger::API constant (currently 1) + * Added support for ChromePHP's v4.0 extension + * Added support for message priorities in PushoverHandler, see $highPriorityLevel and $emergencyLevel + * Added support for sending messages to multiple users at once with the PushoverHandler + * Fixed RavenHandler's support for batch sending of messages (when behind a Buffer or FingersCrossedHandler) + * Fixed normalization of Traversables with very large data sets, only the first 1000 items are shown now + * Fixed issue in RotatingFileHandler when an open_basedir restriction is active + * Fixed minor issues in RavenHandler and bumped the API to Raven 0.5.0 + * Fixed SyslogHandler issue when many were used concurrently with different facilities + +### 1.5.0 (2013-04-23) + + * Added ProcessIdProcessor to inject the PID in log records + * Added UidProcessor to inject a unique identifier to all log records of one request/run + * Added support for previous exceptions in the LineFormatter exception serialization + * Added Monolog\Logger::getLevels() to get all available levels + * Fixed ChromePHPHandler so it avoids sending headers larger than Chrome can handle + +### 1.4.1 (2013-04-01) + + * Fixed exception formatting in the LineFormatter to be more minimalistic + * Fixed RavenHandler's handling of context/extra data, requires Raven client >0.1.0 + * Fixed log rotation in RotatingFileHandler to work with long running scripts spanning multiple days + * Fixed WebProcessor array access so it checks for data presence + * Fixed Buffer, Group and FingersCrossed handlers to make use of their processors + +### 1.4.0 (2013-02-13) + + * Added RedisHandler to log to Redis via the Predis library or the phpredis extension + * Added ZendMonitorHandler to log to the Zend Server monitor + * Added the possibility to pass arrays of handlers and processors directly in the Logger constructor + * Added `$useSSL` option to the PushoverHandler which is enabled by default + * Fixed ChromePHPHandler and FirePHPHandler issue when multiple instances are used simultaneously + * Fixed header injection capability in the NativeMailHandler + +### 1.3.1 (2013-01-11) + + * Fixed LogstashFormatter to be usable with stream handlers + * Fixed GelfMessageFormatter levels on Windows + +### 1.3.0 (2013-01-08) + + * Added PSR-3 compliance, the `Monolog\Logger` class is now an instance of `Psr\Log\LoggerInterface` + * Added PsrLogMessageProcessor that you can selectively enable for full PSR-3 compliance + * Added LogstashFormatter (combine with SocketHandler or StreamHandler to send logs to Logstash) + * Added PushoverHandler to send mobile notifications + * Added CouchDBHandler and DoctrineCouchDBHandler + * Added RavenHandler to send data to Sentry servers + * Added support for the new MongoClient class in MongoDBHandler + * Added microsecond precision to log records' timestamps + * Added `$flushOnOverflow` param to BufferHandler to flush by batches instead of losing + the oldest entries + * Fixed normalization of objects with cyclic references + +### 1.2.1 (2012-08-29) + + * Added new $logopts arg to SyslogHandler to provide custom openlog options + * Fixed fatal error in SyslogHandler + +### 1.2.0 (2012-08-18) + + * Added AmqpHandler (for use with AMQP servers) + * Added CubeHandler + * Added NativeMailerHandler::addHeader() to send custom headers in mails + * Added the possibility to specify more than one recipient in NativeMailerHandler + * Added the possibility to specify float timeouts in SocketHandler + * Added NOTICE and EMERGENCY levels to conform with RFC 5424 + * Fixed the log records to use the php default timezone instead of UTC + * Fixed BufferHandler not being flushed properly on PHP fatal errors + * Fixed normalization of exotic resource types + * Fixed the default format of the SyslogHandler to avoid duplicating datetimes in syslog + +### 1.1.0 (2012-04-23) + + * Added Monolog\Logger::isHandling() to check if a handler will + handle the given log level + * Added ChromePHPHandler + * Added MongoDBHandler + * Added GelfHandler (for use with Graylog2 servers) + * Added SocketHandler (for use with syslog-ng for example) + * Added NormalizerFormatter + * Added the possibility to change the activation strategy of the FingersCrossedHandler + * Added possibility to show microseconds in logs + * Added `server` and `referer` to WebProcessor output + +### 1.0.2 (2011-10-24) + + * Fixed bug in IE with large response headers and FirePHPHandler + +### 1.0.1 (2011-08-25) + + * Added MemoryPeakUsageProcessor and MemoryUsageProcessor + * Added Monolog\Logger::getName() to get a logger's channel name + +### 1.0.0 (2011-07-06) + + * Added IntrospectionProcessor to get info from where the logger was called + * Fixed WebProcessor in CLI + +### 1.0.0-RC1 (2011-07-01) + + * Initial release diff --git a/apps/files_external/3rdparty/monolog/monolog/README.md b/apps/files_external/3rdparty/monolog/monolog/README.md new file mode 100644 index 000000000000..4630f1f6916d --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/README.md @@ -0,0 +1,118 @@ +# Monolog - Logging for PHP [![Continuous Integration](https://github.com/Seldaek/monolog/workflows/Continuous%20Integration/badge.svg?branch=main)](https://github.com/Seldaek/monolog/actions) + +[![Total Downloads](https://img.shields.io/packagist/dt/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog) +[![Latest Stable Version](https://img.shields.io/packagist/v/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog) + +>**Note** This is the **documentation for Monolog 3.x**, if you are using older releases +>see the documentation for [Monolog 2.x](https://github.com/Seldaek/monolog/blob/2.x/README.md) or [Monolog 1.x](https://github.com/Seldaek/monolog/blob/1.x/README.md) + +Monolog sends your logs to files, sockets, inboxes, databases and various +web services. See the complete list of handlers below. Special handlers +allow you to build advanced logging strategies. + +This library implements the [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +interface that you can type-hint against in your own libraries to keep +a maximum of interoperability. You can also use it in your applications to +make sure you can always use another compatible logger at a later time. +As of 1.11.0 Monolog public APIs will also accept PSR-3 log levels. +Internally Monolog still uses its own level scheme since it predates PSR-3. + +## Installation + +Install the latest version with + +```bash +$ composer require monolog/monolog +``` + +## Basic Usage + +```php +pushHandler(new StreamHandler('path/to/your.log', Level::Warning)); + +// add records to the log +$log->warning('Foo'); +$log->error('Bar'); +``` + +## Documentation + +- [Usage Instructions](doc/01-usage.md) +- [Handlers, Formatters and Processors](doc/02-handlers-formatters-processors.md) +- [Utility Classes](doc/03-utilities.md) +- [Extending Monolog](doc/04-extending.md) +- [Log Record Structure](doc/message-structure.md) + +## Support Monolog Financially + +Get supported Monolog and help fund the project with the [Tidelift Subscription](https://tidelift.com/subscription/pkg/packagist-monolog-monolog?utm_source=packagist-monolog-monolog&utm_medium=referral&utm_campaign=enterprise) or via [GitHub sponsorship](https://github.com/sponsors/Seldaek). + +Tidelift delivers commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. + +## Third Party Packages + +Third party handlers, formatters and processors are +[listed in the wiki](https://github.com/Seldaek/monolog/wiki/Third-Party-Packages). You +can also add your own there if you publish one. + +## About + +### Requirements + +- Monolog `^3.0` works with PHP 8.1 or above. +- Monolog `^2.5` works with PHP 7.2 or above. +- Monolog `^1.25` works with PHP 5.3 up to 8.1, but is not very maintained anymore and will not receive PHP support fixes anymore. + +### Support + +Monolog 1.x support is somewhat limited at this point and only important fixes will be done. You should migrate to Monolog 2 or 3 where possible to benefit from all the latest features and fixes. + +### Submitting bugs and feature requests + +Bugs and feature request are tracked on [GitHub](https://github.com/Seldaek/monolog/issues) + +### Framework Integrations + +- Frameworks and libraries using [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) + can be used very easily with Monolog since it implements the interface. +- [Symfony](http://symfony.com) comes out of the box with Monolog. +- [Laravel](http://laravel.com/) comes out of the box with Monolog. +- [Lumen](http://lumen.laravel.com/) comes out of the box with Monolog. +- [PPI](https://github.com/ppi/framework) comes out of the box with Monolog. +- [CakePHP](http://cakephp.org/) is usable with Monolog via the [cakephp-monolog](https://github.com/jadb/cakephp-monolog) plugin. +- [Slim](http://www.slimframework.com/) is usable with Monolog via the [Slim-Monolog](https://github.com/Flynsarmy/Slim-Monolog) log writer. +- [XOOPS 2.6](http://xoops.org/) comes out of the box with Monolog. +- [Aura.Web_Project](https://github.com/auraphp/Aura.Web_Project) comes out of the box with Monolog. +- [Nette Framework](http://nette.org/en/) is usable with Monolog via the [contributte/monolog](https://github.com/contributte/monolog) or [orisai/nette-monolog](https://github.com/orisai/nette-monolog) extensions. +- [Proton Micro Framework](https://github.com/alexbilbie/Proton) comes out of the box with Monolog. +- [FuelPHP](http://fuelphp.com/) comes out of the box with Monolog. +- [Equip Framework](https://github.com/equip/framework) comes out of the box with Monolog. +- [Yii 2](http://www.yiiframework.com/) is usable with Monolog via the [yii2-monolog](https://github.com/merorafael/yii2-monolog) or [yii2-psr-log-target](https://github.com/samdark/yii2-psr-log-target) plugins. +- [Hawkbit Micro Framework](https://github.com/HawkBitPhp/hawkbit) comes out of the box with Monolog. +- [SilverStripe 4](https://www.silverstripe.org/) comes out of the box with Monolog. +- [Drupal](https://www.drupal.org/) is usable with Monolog via the [monolog](https://www.drupal.org/project/monolog) module. +- [Aimeos ecommerce framework](https://aimeos.org/) is usable with Monolog via the [ai-monolog](https://github.com/aimeos/ai-monolog) extension. +- [Magento](https://magento.com/) comes out of the box with Monolog. +- [Spiral Framework](https://spiral.dev) comes out of the box with Monolog bridge. + +### Author + +Jordi Boggiano - -
+See also the list of [contributors](https://github.com/Seldaek/monolog/contributors) who participated in this project. + +### License + +Monolog is licensed under the MIT License - see the [LICENSE](LICENSE) file for details + +### Acknowledgements + +This library is heavily inspired by Python's [Logbook](https://logbook.readthedocs.io/en/stable/) +library, although most concepts have been adjusted to fit to the PHP world. diff --git a/apps/files_external/3rdparty/monolog/monolog/composer.json b/apps/files_external/3rdparty/monolog/monolog/composer.json new file mode 100644 index 000000000000..4ccd07211e04 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/composer.json @@ -0,0 +1,77 @@ +{ + "name": "monolog/monolog", + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "keywords": ["log", "logging", "psr-3"], + "homepage": "https://github.com/Seldaek/monolog", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "require-dev": { + "ext-json": "*", + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-strict-rules": "^1.4", + "phpunit/phpunit": "^10.1", + "predis/predis": "^1.1 || ^2", + "ruflin/elastica": "^7", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-openssl": "Required to send log messages using SSL" + }, + "autoload": { + "psr-4": {"Monolog\\": "src/Monolog"} + }, + "autoload-dev": { + "psr-4": {"Monolog\\": "tests/Monolog"} + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "scripts": { + "test": "@php vendor/bin/phpunit", + "phpstan": "@php vendor/bin/phpstan analyse" + }, + "config": { + "lock": false, + "sort-packages": true, + "platform-check": false + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Attribute/AsMonologProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Attribute/AsMonologProcessor.php new file mode 100644 index 000000000000..c519e0537e39 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Attribute/AsMonologProcessor.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Attribute; + +/** + * A reusable attribute to help configure a class or a method as a processor. + * + * Using it offers no guarantee: it needs to be leveraged by a Monolog third-party consumer. + * + * Using it with the Monolog library only has no effect at all: processors should still be turned into a callable if + * needed and manually pushed to the loggers and to the processable handlers. + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class AsMonologProcessor +{ + /** + * @param string|null $channel The logging channel the processor should be pushed to. + * @param string|null $handler The handler the processor should be pushed to. + * @param string|null $method The method that processes the records (if the attribute is used at the class level). + * @param int|null $priority The priority of the processor so the order can be determined. + */ + public function __construct( + public readonly ?string $channel = null, + public readonly ?string $handler = null, + public readonly ?string $method = null, + public readonly ?int $priority = null + ) { + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/DateTimeImmutable.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/DateTimeImmutable.php new file mode 100644 index 000000000000..274b73ea1df0 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/DateTimeImmutable.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use DateTimeZone; + +/** + * Overrides default json encoding of date time objects + * + * @author Menno Holtkamp + * @author Jordi Boggiano + */ +class DateTimeImmutable extends \DateTimeImmutable implements \JsonSerializable +{ + private bool $useMicroseconds; + + public function __construct(bool $useMicroseconds, ?DateTimeZone $timezone = null) + { + $this->useMicroseconds = $useMicroseconds; + + parent::__construct('now', $timezone); + } + + public function jsonSerialize(): string + { + if ($this->useMicroseconds) { + return $this->format('Y-m-d\TH:i:s.uP'); + } + + return $this->format('Y-m-d\TH:i:sP'); + } + + public function __toString(): string + { + return $this->jsonSerialize(); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/ErrorHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/ErrorHandler.php new file mode 100644 index 000000000000..594e64b24436 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/ErrorHandler.php @@ -0,0 +1,278 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Closure; +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; + +/** + * Monolog error handler + * + * A facility to enable logging of runtime errors, exceptions and fatal errors. + * + * Quick setup: ErrorHandler::register($logger); + * + * @author Jordi Boggiano + */ +class ErrorHandler +{ + private Closure|null $previousExceptionHandler = null; + + /** @var array an array of class name to LogLevel::* constant mapping */ + private array $uncaughtExceptionLevelMap = []; + + /** @var Closure|true|null */ + private Closure|bool|null $previousErrorHandler = null; + + /** @var array an array of E_* constant to LogLevel::* constant mapping */ + private array $errorLevelMap = []; + + private bool $handleOnlyReportedErrors = true; + + private bool $hasFatalErrorHandler = false; + + private string $fatalLevel = LogLevel::ALERT; + + private string|null $reservedMemory = null; + + /** @var ?array{type: int, message: string, file: string, line: int, trace: mixed} */ + private array|null $lastFatalData = null; + + private const FATAL_ERRORS = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR]; + + public function __construct( + private LoggerInterface $logger + ) { + } + + /** + * Registers a new ErrorHandler for a given Logger + * + * By default it will handle errors, exceptions and fatal errors + * + * @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling + * @param array|false $exceptionLevelMap an array of class name to LogLevel::* constant mapping, or false to disable exception handling + * @param LogLevel::*|null|false $fatalLevel a LogLevel::* constant, null to use the default LogLevel::ALERT or false to disable fatal error handling + * @return ErrorHandler + */ + public static function register(LoggerInterface $logger, $errorLevelMap = [], $exceptionLevelMap = [], $fatalLevel = null): self + { + /** @phpstan-ignore-next-line */ + $handler = new static($logger); + if ($errorLevelMap !== false) { + $handler->registerErrorHandler($errorLevelMap); + } + if ($exceptionLevelMap !== false) { + $handler->registerExceptionHandler($exceptionLevelMap); + } + if ($fatalLevel !== false) { + $handler->registerFatalHandler($fatalLevel); + } + + return $handler; + } + + /** + * @param array $levelMap an array of class name to LogLevel::* constant mapping + * @return $this + */ + public function registerExceptionHandler(array $levelMap = [], bool $callPrevious = true): self + { + $prev = set_exception_handler(function (\Throwable $e): void { + $this->handleException($e); + }); + $this->uncaughtExceptionLevelMap = $levelMap; + foreach ($this->defaultExceptionLevelMap() as $class => $level) { + if (!isset($this->uncaughtExceptionLevelMap[$class])) { + $this->uncaughtExceptionLevelMap[$class] = $level; + } + } + if ($callPrevious && null !== $prev) { + $this->previousExceptionHandler = $prev(...); + } + + return $this; + } + + /** + * @param array $levelMap an array of E_* constant to LogLevel::* constant mapping + * @return $this + */ + public function registerErrorHandler(array $levelMap = [], bool $callPrevious = true, int $errorTypes = -1, bool $handleOnlyReportedErrors = true): self + { + $prev = set_error_handler($this->handleError(...), $errorTypes); + $this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap); + if ($callPrevious) { + $this->previousErrorHandler = $prev !== null ? $prev(...) : true; + } else { + $this->previousErrorHandler = null; + } + + $this->handleOnlyReportedErrors = $handleOnlyReportedErrors; + + return $this; + } + + /** + * @param LogLevel::*|null $level a LogLevel::* constant, null to use the default LogLevel::ALERT + * @param int $reservedMemorySize Amount of KBs to reserve in memory so that it can be freed when handling fatal errors giving Monolog some room in memory to get its job done + */ + public function registerFatalHandler($level = null, int $reservedMemorySize = 20): self + { + register_shutdown_function($this->handleFatalError(...)); + + $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize); + $this->fatalLevel = null === $level ? LogLevel::ALERT : $level; + $this->hasFatalErrorHandler = true; + + return $this; + } + + /** + * @return array + */ + protected function defaultExceptionLevelMap(): array + { + return [ + 'ParseError' => LogLevel::CRITICAL, + 'Throwable' => LogLevel::ERROR, + ]; + } + + /** + * @return array + */ + protected function defaultErrorLevelMap(): array + { + return [ + E_ERROR => LogLevel::CRITICAL, + E_WARNING => LogLevel::WARNING, + E_PARSE => LogLevel::ALERT, + E_NOTICE => LogLevel::NOTICE, + E_CORE_ERROR => LogLevel::CRITICAL, + E_CORE_WARNING => LogLevel::WARNING, + E_COMPILE_ERROR => LogLevel::ALERT, + E_COMPILE_WARNING => LogLevel::WARNING, + E_USER_ERROR => LogLevel::ERROR, + E_USER_WARNING => LogLevel::WARNING, + E_USER_NOTICE => LogLevel::NOTICE, + E_STRICT => LogLevel::NOTICE, + E_RECOVERABLE_ERROR => LogLevel::ERROR, + E_DEPRECATED => LogLevel::NOTICE, + E_USER_DEPRECATED => LogLevel::NOTICE, + ]; + } + + private function handleException(\Throwable $e): never + { + $level = LogLevel::ERROR; + foreach ($this->uncaughtExceptionLevelMap as $class => $candidate) { + if ($e instanceof $class) { + $level = $candidate; + break; + } + } + + $this->logger->log( + $level, + sprintf('Uncaught Exception %s: "%s" at %s line %s', Utils::getClass($e), $e->getMessage(), $e->getFile(), $e->getLine()), + ['exception' => $e] + ); + + if (null !== $this->previousExceptionHandler) { + ($this->previousExceptionHandler)($e); + } + + if (!headers_sent() && in_array(strtolower((string) ini_get('display_errors')), ['0', '', 'false', 'off', 'none', 'no'], true)) { + http_response_code(500); + } + + exit(255); + } + + private function handleError(int $code, string $message, string $file = '', int $line = 0): bool + { + if ($this->handleOnlyReportedErrors && 0 === (error_reporting() & $code)) { + return false; + } + + // fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries + if (!$this->hasFatalErrorHandler || !in_array($code, self::FATAL_ERRORS, true)) { + $level = $this->errorLevelMap[$code] ?? LogLevel::CRITICAL; + $this->logger->log($level, self::codeToString($code).': '.$message, ['code' => $code, 'message' => $message, 'file' => $file, 'line' => $line]); + } else { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + array_shift($trace); // Exclude handleError from trace + $this->lastFatalData = ['type' => $code, 'message' => $message, 'file' => $file, 'line' => $line, 'trace' => $trace]; + } + + if ($this->previousErrorHandler === true) { + return false; + } + if ($this->previousErrorHandler instanceof Closure) { + return (bool) ($this->previousErrorHandler)($code, $message, $file, $line); + } + + return true; + } + + /** + * @private + */ + public function handleFatalError(): void + { + $this->reservedMemory = ''; + + if (is_array($this->lastFatalData)) { + $lastError = $this->lastFatalData; + } else { + $lastError = error_get_last(); + } + if (is_array($lastError) && in_array($lastError['type'], self::FATAL_ERRORS, true)) { + $trace = $lastError['trace'] ?? null; + $this->logger->log( + $this->fatalLevel, + 'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'], + ['code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'], 'trace' => $trace] + ); + + if ($this->logger instanceof Logger) { + foreach ($this->logger->getHandlers() as $handler) { + $handler->close(); + } + } + } + } + + private static function codeToString(int $code): string + { + return match ($code) { + E_ERROR => 'E_ERROR', + E_WARNING => 'E_WARNING', + E_PARSE => 'E_PARSE', + E_NOTICE => 'E_NOTICE', + E_CORE_ERROR => 'E_CORE_ERROR', + E_CORE_WARNING => 'E_CORE_WARNING', + E_COMPILE_ERROR => 'E_COMPILE_ERROR', + E_COMPILE_WARNING => 'E_COMPILE_WARNING', + E_USER_ERROR => 'E_USER_ERROR', + E_USER_WARNING => 'E_USER_WARNING', + E_USER_NOTICE => 'E_USER_NOTICE', + E_STRICT => 'E_STRICT', + E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', + E_DEPRECATED => 'E_DEPRECATED', + E_USER_DEPRECATED => 'E_USER_DEPRECATED', + default => 'Unknown PHP error', + }; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php new file mode 100644 index 000000000000..3f1d4582975f --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Level; +use Monolog\LogRecord; + +/** + * Formats a log message according to the ChromePHP array format + * + * @author Christophe Coevoet + */ +class ChromePHPFormatter implements FormatterInterface +{ + /** + * Translates Monolog log levels to Wildfire levels. + * + * @return 'log'|'info'|'warn'|'error' + */ + private function toWildfireLevel(Level $level): string + { + return match ($level) { + Level::Debug => 'log', + Level::Info => 'info', + Level::Notice => 'info', + Level::Warning => 'warn', + Level::Error => 'error', + Level::Critical => 'error', + Level::Alert => 'error', + Level::Emergency => 'error', + }; + } + + /** + * @inheritDoc + */ + public function format(LogRecord $record) + { + // Retrieve the line and file if set and remove them from the formatted extra + $backtrace = 'unknown'; + if (isset($record->extra['file'], $record->extra['line'])) { + $backtrace = $record->extra['file'].' : '.$record->extra['line']; + unset($record->extra['file'], $record->extra['line']); + } + + $message = ['message' => $record->message]; + if (\count($record->context) > 0) { + $message['context'] = $record->context; + } + if (\count($record->extra) > 0) { + $message['extra'] = $record->extra; + } + if (count($message) === 1) { + $message = reset($message); + } + + return [ + $record->channel, + $message, + $backtrace, + $this->toWildfireLevel($record->level), + ]; + } + + /** + * @inheritDoc + */ + public function formatBatch(array $records) + { + $formatted = []; + + foreach ($records as $record) { + $formatted[] = $this->format($record); + } + + return $formatted; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php new file mode 100644 index 000000000000..8c92eff22449 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Elastica\Document; +use Monolog\LogRecord; + +/** + * Format a log message into an Elastica Document + * + * @author Jelle Vink + */ +class ElasticaFormatter extends NormalizerFormatter +{ + /** + * @var string Elastic search index name + */ + protected string $index; + + /** + * @var string|null Elastic search document type + */ + protected string|null $type; + + /** + * @param string $index Elastic Search index name + * @param ?string $type Elastic Search document type, deprecated as of Elastica 7 + * + * @throws \RuntimeException If the function json_encode does not exist + */ + public function __construct(string $index, ?string $type) + { + // elasticsearch requires a ISO 8601 format date with optional millisecond precision. + parent::__construct('Y-m-d\TH:i:s.uP'); + + $this->index = $index; + $this->type = $type; + } + + /** + * @inheritDoc + */ + public function format(LogRecord $record) + { + $record = parent::format($record); + + return $this->getDocument($record); + } + + public function getIndex(): string + { + return $this->index; + } + + /** + * @deprecated since Elastica 7 type has no effect + */ + public function getType(): string + { + /** @phpstan-ignore-next-line */ + return $this->type; + } + + /** + * Convert a log message into an Elastica Document + * + * @param mixed[] $record + */ + protected function getDocument(array $record): Document + { + $document = new Document(); + $document->setData($record); + if (method_exists($document, 'setType')) { + $document->setType($this->type); + } + $document->setIndex($this->index); + + return $document; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php new file mode 100644 index 000000000000..b38aca0793a0 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use DateTimeInterface; +use Monolog\LogRecord; + +/** + * Format a log message into an Elasticsearch record + * + * @author Avtandil Kikabidze + */ +class ElasticsearchFormatter extends NormalizerFormatter +{ + /** + * @var string Elasticsearch index name + */ + protected string $index; + + /** + * @var string Elasticsearch record type + */ + protected string $type; + + /** + * @param string $index Elasticsearch index name + * @param string $type Elasticsearch record type + * + * @throws \RuntimeException If the function json_encode does not exist + */ + public function __construct(string $index, string $type) + { + // Elasticsearch requires an ISO 8601 format date with optional millisecond precision. + parent::__construct(DateTimeInterface::ISO8601); + + $this->index = $index; + $this->type = $type; + } + + /** + * @inheritDoc + */ + public function format(LogRecord $record) + { + $record = parent::format($record); + + return $this->getDocument($record); + } + + /** + * Getter index + */ + public function getIndex(): string + { + return $this->index; + } + + /** + * Getter type + */ + public function getType(): string + { + return $this->type; + } + + /** + * Convert a log message into an Elasticsearch record + * + * @param mixed[] $record Log message + * @return mixed[] + */ + protected function getDocument(array $record): array + { + $record['_index'] = $this->index; + $record['_type'] = $this->type; + + return $record; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php new file mode 100644 index 000000000000..c64da7c06f08 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\LogRecord; + +/** + * formats the record to be used in the FlowdockHandler + * + * @author Dominik Liebler + * @deprecated Since 2.9.0 and 3.3.0, Flowdock was shutdown we will thus drop this handler in Monolog 4 + */ +class FlowdockFormatter implements FormatterInterface +{ + private string $source; + + private string $sourceEmail; + + public function __construct(string $source, string $sourceEmail) + { + $this->source = $source; + $this->sourceEmail = $sourceEmail; + } + + /** + * @inheritDoc + * + * @return mixed[] + */ + public function format(LogRecord $record): array + { + $tags = [ + '#logs', + '#' . $record->level->toPsrLogLevel(), + '#' . $record->channel, + ]; + + foreach ($record->extra as $value) { + $tags[] = '#' . $value; + } + + $subject = sprintf( + 'in %s: %s - %s', + $this->source, + $record->level->getName(), + $this->getShortMessage($record->message) + ); + + return [ + 'source' => $this->source, + 'from_address' => $this->sourceEmail, + 'subject' => $subject, + 'content' => $record->message, + 'tags' => $tags, + 'project' => $this->source, + ]; + } + + /** + * @inheritDoc + * + * @return mixed[][] + */ + public function formatBatch(array $records): array + { + $formatted = []; + + foreach ($records as $record) { + $formatted[] = $this->format($record); + } + + return $formatted; + } + + public function getShortMessage(string $message): string + { + static $hasMbString; + + if (null === $hasMbString) { + $hasMbString = function_exists('mb_strlen'); + } + + $maxLength = 45; + + if ($hasMbString) { + if (mb_strlen($message, 'UTF-8') > $maxLength) { + $message = mb_substr($message, 0, $maxLength - 4, 'UTF-8') . ' ...'; + } + } else { + if (strlen($message) > $maxLength) { + $message = substr($message, 0, $maxLength - 4) . ' ...'; + } + } + + return $message; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php new file mode 100644 index 000000000000..04495a614d4b --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * Class FluentdFormatter + * + * Serializes a log message to Fluentd unix socket protocol + * + * Fluentd config: + * + * + * type unix + * path /var/run/td-agent/td-agent.sock + * + * + * Monolog setup: + * + * $logger = new Monolog\Logger('fluent.tag'); + * $fluentHandler = new Monolog\Handler\SocketHandler('unix:///var/run/td-agent/td-agent.sock'); + * $fluentHandler->setFormatter(new Monolog\Formatter\FluentdFormatter()); + * $logger->pushHandler($fluentHandler); + * + * @author Andrius Putna + */ +class FluentdFormatter implements FormatterInterface +{ + /** + * @var bool $levelTag should message level be a part of the fluentd tag + */ + protected bool $levelTag = false; + + /** + * @throws \RuntimeException If the function json_encode does not exist + */ + public function __construct(bool $levelTag = false) + { + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s FluentdUnixFormatter'); + } + + $this->levelTag = $levelTag; + } + + public function isUsingLevelsInTag(): bool + { + return $this->levelTag; + } + + public function format(LogRecord $record): string + { + $tag = $record->channel; + if ($this->levelTag) { + $tag .= '.' . $record->level->toPsrLogLevel(); + } + + $message = [ + 'message' => $record->message, + 'context' => $record->context, + 'extra' => $record->extra, + ]; + + if (!$this->levelTag) { + $message['level'] = $record->level->value; + $message['level_name'] = $record->level->getName(); + } + + return Utils::jsonEncode([$tag, $record->datetime->getTimestamp(), $message]); + } + + public function formatBatch(array $records): string + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php new file mode 100644 index 000000000000..3413a4b05a0b --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\LogRecord; + +/** + * Interface for formatters + * + * @author Jordi Boggiano + */ +interface FormatterInterface +{ + /** + * Formats a log record. + * + * @param LogRecord $record A record to format + * @return mixed The formatted record + */ + public function format(LogRecord $record); + + /** + * Formats a set of log records. + * + * @param array $records A set of records to format + * @return mixed The formatted set of records + */ + public function formatBatch(array $records); +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php new file mode 100644 index 000000000000..96cb60ffef89 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Level; +use Gelf\Message; +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * Serializes a log message to GELF + * @see http://docs.graylog.org/en/latest/pages/gelf.html + * + * @author Matt Lehner + */ +class GelfMessageFormatter extends NormalizerFormatter +{ + protected const DEFAULT_MAX_LENGTH = 32766; + + /** + * @var string the name of the system for the Gelf log message + */ + protected string $systemName; + + /** + * @var string a prefix for 'extra' fields from the Monolog record (optional) + */ + protected string $extraPrefix; + + /** + * @var string a prefix for 'context' fields from the Monolog record (optional) + */ + protected string $contextPrefix; + + /** + * @var int max length per field + */ + protected int $maxLength; + + /** + * Translates Monolog log levels to Graylog2 log priorities. + */ + private function getGraylog2Priority(Level $level): int + { + return match ($level) { + Level::Debug => 7, + Level::Info => 6, + Level::Notice => 5, + Level::Warning => 4, + Level::Error => 3, + Level::Critical => 2, + Level::Alert => 1, + Level::Emergency => 0, + }; + } + + /** + * @throws \RuntimeException + */ + public function __construct(?string $systemName = null, ?string $extraPrefix = null, string $contextPrefix = 'ctxt_', ?int $maxLength = null) + { + if (!class_exists(Message::class)) { + throw new \RuntimeException('Composer package graylog2/gelf-php is required to use Monolog\'s GelfMessageFormatter'); + } + + parent::__construct('U.u'); + + $this->systemName = (null === $systemName || $systemName === '') ? (string) gethostname() : $systemName; + + $this->extraPrefix = null === $extraPrefix ? '' : $extraPrefix; + $this->contextPrefix = $contextPrefix; + $this->maxLength = null === $maxLength ? self::DEFAULT_MAX_LENGTH : $maxLength; + } + + /** + * @inheritDoc + */ + public function format(LogRecord $record): Message + { + $context = $extra = []; + if (isset($record->context)) { + /** @var mixed[] $context */ + $context = parent::normalize($record->context); + } + if (isset($record->extra)) { + /** @var mixed[] $extra */ + $extra = parent::normalize($record->extra); + } + + $message = new Message(); + $message + ->setTimestamp($record->datetime) + ->setShortMessage($record->message) + ->setHost($this->systemName) + ->setLevel($this->getGraylog2Priority($record->level)); + + // message length + system name length + 200 for padding / metadata + $len = 200 + strlen($record->message) + strlen($this->systemName); + + if ($len > $this->maxLength) { + $message->setShortMessage(Utils::substr($record->message, 0, $this->maxLength)); + } + + if (isset($record->channel)) { + $message->setAdditional('facility', $record->channel); + } + + foreach ($extra as $key => $val) { + $val = is_scalar($val) || null === $val ? $val : $this->toJson($val); + $len = strlen($this->extraPrefix . $key . $val); + if ($len > $this->maxLength) { + $message->setAdditional($this->extraPrefix . $key, Utils::substr((string) $val, 0, $this->maxLength)); + + continue; + } + $message->setAdditional($this->extraPrefix . $key, $val); + } + + foreach ($context as $key => $val) { + $val = is_scalar($val) || null === $val ? $val : $this->toJson($val); + $len = strlen($this->contextPrefix . $key . $val); + if ($len > $this->maxLength) { + $message->setAdditional($this->contextPrefix . $key, Utils::substr((string) $val, 0, $this->maxLength)); + + continue; + } + $message->setAdditional($this->contextPrefix . $key, $val); + } + + if (!$message->hasAdditional('file') && isset($context['exception']['file'])) { + if (1 === preg_match("/^(.+):([0-9]+)$/", $context['exception']['file'], $matches)) { + $message->setAdditional('file', $matches[1]); + $message->setAdditional('line', $matches[2]); + } + } + + return $message; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/GoogleCloudLoggingFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/GoogleCloudLoggingFormatter.php new file mode 100644 index 000000000000..ea555d4de930 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/GoogleCloudLoggingFormatter.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use DateTimeInterface; +use Monolog\LogRecord; + +/** + * Encodes message information into JSON in a format compatible with Cloud logging. + * + * @see https://cloud.google.com/logging/docs/structured-logging + * @see https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry + * + * @author Luís Cobucci + */ +final class GoogleCloudLoggingFormatter extends JsonFormatter +{ + protected function normalizeRecord(LogRecord $record): array + { + $normalized = parent::normalizeRecord($record); + + // Re-key level for GCP logging + $normalized['severity'] = $normalized['level_name']; + $normalized['time'] = $record->datetime->format(DateTimeInterface::RFC3339_EXTENDED); + + // Remove keys that are not used by GCP + unset($normalized['level'], $normalized['level_name'], $normalized['datetime']); + + return $normalized; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php new file mode 100644 index 000000000000..c316b65eea87 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Level; +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * Formats incoming records into an HTML table + * + * This is especially useful for html email logging + * + * @author Tiago Brito + */ +class HtmlFormatter extends NormalizerFormatter +{ + /** + * Translates Monolog log levels to html color priorities. + */ + protected function getLevelColor(Level $level): string + { + return match ($level) { + Level::Debug => '#CCCCCC', + Level::Info => '#28A745', + Level::Notice => '#17A2B8', + Level::Warning => '#FFC107', + Level::Error => '#FD7E14', + Level::Critical => '#DC3545', + Level::Alert => '#821722', + Level::Emergency => '#000000', + }; + } + + /** + * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format + * @throws \RuntimeException If the function json_encode does not exist + */ + public function __construct(?string $dateFormat = null) + { + parent::__construct($dateFormat); + } + + /** + * Creates an HTML table row + * + * @param string $th Row header content + * @param string $td Row standard cell content + * @param bool $escapeTd false if td content must not be html escaped + */ + protected function addRow(string $th, string $td = ' ', bool $escapeTd = true): string + { + $th = htmlspecialchars($th, ENT_NOQUOTES, 'UTF-8'); + if ($escapeTd) { + $td = '
'.htmlspecialchars($td, ENT_NOQUOTES, 'UTF-8').'
'; + } + + return "\n$th:\n".$td."\n"; + } + + /** + * Create a HTML h1 tag + * + * @param string $title Text to be in the h1 + */ + protected function addTitle(string $title, Level $level): string + { + $title = htmlspecialchars($title, ENT_NOQUOTES, 'UTF-8'); + + return '

'.$title.'

'; + } + + /** + * Formats a log record. + * + * @return string The formatted record + */ + public function format(LogRecord $record): string + { + $output = $this->addTitle($record->level->getName(), $record->level); + $output .= ''; + + $output .= $this->addRow('Message', $record->message); + $output .= $this->addRow('Time', $this->formatDate($record->datetime)); + $output .= $this->addRow('Channel', $record->channel); + if (\count($record->context) > 0) { + $embeddedTable = '
'; + foreach ($record->context as $key => $value) { + $embeddedTable .= $this->addRow((string) $key, $this->convertToString($value)); + } + $embeddedTable .= '
'; + $output .= $this->addRow('Context', $embeddedTable, false); + } + if (\count($record->extra) > 0) { + $embeddedTable = ''; + foreach ($record->extra as $key => $value) { + $embeddedTable .= $this->addRow((string) $key, $this->convertToString($value)); + } + $embeddedTable .= '
'; + $output .= $this->addRow('Extra', $embeddedTable, false); + } + + return $output.''; + } + + /** + * Formats a set of log records. + * + * @return string The formatted set of records + */ + public function formatBatch(array $records): string + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } + + /** + * @param mixed $data + */ + protected function convertToString($data): string + { + if (null === $data || is_scalar($data)) { + return (string) $data; + } + + $data = $this->normalize($data); + + return Utils::jsonEncode($data, JSON_PRETTY_PRINT | Utils::DEFAULT_JSON_FLAGS, true); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php new file mode 100644 index 000000000000..039c38dded8c --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php @@ -0,0 +1,213 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Stringable; +use Throwable; +use Monolog\LogRecord; + +/** + * Encodes whatever record data is passed to it as json + * + * This can be useful to log to databases or remote APIs + * + * @author Jordi Boggiano + */ +class JsonFormatter extends NormalizerFormatter +{ + public const BATCH_MODE_JSON = 1; + public const BATCH_MODE_NEWLINES = 2; + + /** @var self::BATCH_MODE_* */ + protected int $batchMode; + + protected bool $appendNewline; + + protected bool $ignoreEmptyContextAndExtra; + + protected bool $includeStacktraces = false; + + /** + * @param self::BATCH_MODE_* $batchMode + * + * @throws \RuntimeException If the function json_encode does not exist + */ + public function __construct(int $batchMode = self::BATCH_MODE_JSON, bool $appendNewline = true, bool $ignoreEmptyContextAndExtra = false, bool $includeStacktraces = false) + { + $this->batchMode = $batchMode; + $this->appendNewline = $appendNewline; + $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra; + $this->includeStacktraces = $includeStacktraces; + + parent::__construct(); + } + + /** + * The batch mode option configures the formatting style for + * multiple records. By default, multiple records will be + * formatted as a JSON-encoded array. However, for + * compatibility with some API endpoints, alternative styles + * are available. + */ + public function getBatchMode(): int + { + return $this->batchMode; + } + + /** + * True if newlines are appended to every formatted record + */ + public function isAppendingNewlines(): bool + { + return $this->appendNewline; + } + + /** + * @inheritDoc + */ + public function format(LogRecord $record): string + { + $normalized = parent::format($record); + + if (isset($normalized['context']) && $normalized['context'] === []) { + if ($this->ignoreEmptyContextAndExtra) { + unset($normalized['context']); + } else { + $normalized['context'] = new \stdClass; + } + } + if (isset($normalized['extra']) && $normalized['extra'] === []) { + if ($this->ignoreEmptyContextAndExtra) { + unset($normalized['extra']); + } else { + $normalized['extra'] = new \stdClass; + } + } + + return $this->toJson($normalized, true) . ($this->appendNewline ? "\n" : ''); + } + + /** + * @inheritDoc + */ + public function formatBatch(array $records): string + { + return match ($this->batchMode) { + static::BATCH_MODE_NEWLINES => $this->formatBatchNewlines($records), + default => $this->formatBatchJson($records), + }; + } + + public function includeStacktraces(bool $include = true): self + { + $this->includeStacktraces = $include; + + return $this; + } + + /** + * Return a JSON-encoded array of records. + * + * @phpstan-param LogRecord[] $records + */ + protected function formatBatchJson(array $records): string + { + return $this->toJson($this->normalize($records), true); + } + + /** + * Use new lines to separate records instead of a + * JSON-encoded array. + * + * @phpstan-param LogRecord[] $records + */ + protected function formatBatchNewlines(array $records): string + { + $oldNewline = $this->appendNewline; + $this->appendNewline = false; + $formatted = array_map(fn (LogRecord $record) => $this->format($record), $records); + $this->appendNewline = $oldNewline; + + return implode("\n", $formatted); + } + + /** + * Normalizes given $data. + * + * @return null|scalar|array|object + */ + protected function normalize(mixed $data, int $depth = 0): mixed + { + if ($depth > $this->maxNormalizeDepth) { + return 'Over '.$this->maxNormalizeDepth.' levels deep, aborting normalization'; + } + + if (is_array($data)) { + $normalized = []; + + $count = 1; + foreach ($data as $key => $value) { + if ($count++ > $this->maxNormalizeItemCount) { + $normalized['...'] = 'Over '.$this->maxNormalizeItemCount.' items ('.count($data).' total), aborting normalization'; + break; + } + + $normalized[$key] = $this->normalize($value, $depth + 1); + } + + return $normalized; + } + + if (is_object($data)) { + if ($data instanceof \DateTimeInterface) { + return $this->formatDate($data); + } + + if ($data instanceof Throwable) { + return $this->normalizeException($data, $depth); + } + + // if the object has specific json serializability we want to make sure we skip the __toString treatment below + if ($data instanceof \JsonSerializable) { + return $data; + } + + if ($data instanceof Stringable) { + return $data->__toString(); + } + + return $data; + } + + if (is_resource($data)) { + return parent::normalize($data); + } + + return $data; + } + + /** + * Normalizes given exception with or without its own stack trace based on + * `includeStacktraces` property. + * + * @inheritDoc + */ + protected function normalizeException(Throwable $e, int $depth = 0): array + { + $data = parent::normalizeException($e, $depth); + if (!$this->includeStacktraces) { + unset($data['trace']); + } + + return $data; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LineFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LineFormatter.php new file mode 100644 index 000000000000..76ac7700b5c9 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LineFormatter.php @@ -0,0 +1,244 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Closure; +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * Formats incoming records into a one-line string + * + * This is especially useful for logging to files + * + * @author Jordi Boggiano + * @author Christophe Coevoet + */ +class LineFormatter extends NormalizerFormatter +{ + public const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"; + + protected string $format; + protected bool $allowInlineLineBreaks; + protected bool $ignoreEmptyContextAndExtra; + protected bool $includeStacktraces; + protected Closure|null $stacktracesParser = null; + + /** + * @param string|null $format The format of the message + * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format + * @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries + * + * @throws \RuntimeException If the function json_encode does not exist + */ + public function __construct(?string $format = null, ?string $dateFormat = null, bool $allowInlineLineBreaks = false, bool $ignoreEmptyContextAndExtra = false, bool $includeStacktraces = false) + { + $this->format = $format === null ? static::SIMPLE_FORMAT : $format; + $this->allowInlineLineBreaks = $allowInlineLineBreaks; + $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra; + $this->includeStacktraces($includeStacktraces); + parent::__construct($dateFormat); + } + + public function includeStacktraces(bool $include = true, ?Closure $parser = null): self + { + $this->includeStacktraces = $include; + if ($this->includeStacktraces) { + $this->allowInlineLineBreaks = true; + $this->stacktracesParser = $parser; + } + + return $this; + } + + public function allowInlineLineBreaks(bool $allow = true): self + { + $this->allowInlineLineBreaks = $allow; + + return $this; + } + + public function ignoreEmptyContextAndExtra(bool $ignore = true): self + { + $this->ignoreEmptyContextAndExtra = $ignore; + + return $this; + } + + /** + * @inheritDoc + */ + public function format(LogRecord $record): string + { + $vars = parent::format($record); + + $output = $this->format; + foreach ($vars['extra'] as $var => $val) { + if (false !== strpos($output, '%extra.'.$var.'%')) { + $output = str_replace('%extra.'.$var.'%', $this->stringify($val), $output); + unset($vars['extra'][$var]); + } + } + + foreach ($vars['context'] as $var => $val) { + if (false !== strpos($output, '%context.'.$var.'%')) { + $output = str_replace('%context.'.$var.'%', $this->stringify($val), $output); + unset($vars['context'][$var]); + } + } + + if ($this->ignoreEmptyContextAndExtra) { + if (\count($vars['context']) === 0) { + unset($vars['context']); + $output = str_replace('%context%', '', $output); + } + + if (\count($vars['extra']) === 0) { + unset($vars['extra']); + $output = str_replace('%extra%', '', $output); + } + } + + foreach ($vars as $var => $val) { + if (false !== strpos($output, '%'.$var.'%')) { + $output = str_replace('%'.$var.'%', $this->stringify($val), $output); + } + } + + // remove leftover %extra.xxx% and %context.xxx% if any + if (false !== strpos($output, '%')) { + $output = preg_replace('/%(?:extra|context)\..+?%/', '', $output); + if (null === $output) { + $pcreErrorCode = preg_last_error(); + + throw new \RuntimeException('Failed to run preg_replace: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode)); + } + } + + return $output; + } + + public function formatBatch(array $records): string + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } + + /** + * @param mixed $value + */ + public function stringify($value): string + { + return $this->replaceNewlines($this->convertToString($value)); + } + + protected function normalizeException(\Throwable $e, int $depth = 0): string + { + $str = $this->formatException($e); + + if (($previous = $e->getPrevious()) instanceof \Throwable) { + do { + $depth++; + if ($depth > $this->maxNormalizeDepth) { + $str .= '\n[previous exception] Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization'; + break; + } + + $str .= "\n[previous exception] " . $this->formatException($previous); + } while ($previous = $previous->getPrevious()); + } + + return $str; + } + + /** + * @param mixed $data + */ + protected function convertToString($data): string + { + if (null === $data || is_bool($data)) { + return var_export($data, true); + } + + if (is_scalar($data)) { + return (string) $data; + } + + return $this->toJson($data, true); + } + + protected function replaceNewlines(string $str): string + { + if ($this->allowInlineLineBreaks) { + if (0 === strpos($str, '{')) { + $str = preg_replace('/(?getCode(); + if ($e instanceof \SoapFault) { + if (isset($e->faultcode)) { + $str .= ' faultcode: ' . $e->faultcode; + } + + if (isset($e->faultactor)) { + $str .= ' faultactor: ' . $e->faultactor; + } + + if (isset($e->detail)) { + if (is_string($e->detail)) { + $str .= ' detail: ' . $e->detail; + } elseif (is_object($e->detail) || is_array($e->detail)) { + $str .= ' detail: ' . $this->toJson($e->detail, true); + } + } + } + $str .= '): ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine() . ')'; + + if ($this->includeStacktraces) { + $str .= $this->stacktracesParser($e); + } + + return $str; + } + + private function stacktracesParser(\Throwable $e): string + { + $trace = $e->getTraceAsString(); + + if ($this->stacktracesParser !== null) { + $trace = $this->stacktracesParserCustom($trace); + } + + return "\n[stacktrace]\n" . $trace . "\n"; + } + + private function stacktracesParserCustom(string $trace): string + { + return implode("\n", array_filter(array_map($this->stacktracesParser, explode("\n", $trace)))); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php new file mode 100644 index 000000000000..5f0b6a453fc2 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\LogRecord; + +/** + * Encodes message information into JSON in a format compatible with Loggly. + * + * @author Adam Pancutt + */ +class LogglyFormatter extends JsonFormatter +{ + /** + * Overrides the default batch mode to new lines for compatibility with the + * Loggly bulk API. + */ + public function __construct(int $batchMode = self::BATCH_MODE_NEWLINES, bool $appendNewline = false) + { + parent::__construct($batchMode, $appendNewline); + } + + /** + * Appends the 'timestamp' parameter for indexing by Loggly. + * + * @see https://www.loggly.com/docs/automated-parsing/#json + * @see \Monolog\Formatter\JsonFormatter::format() + */ + protected function normalizeRecord(LogRecord $record): array + { + $recordData = parent::normalizeRecord($record); + + $recordData["timestamp"] = $record->datetime->format("Y-m-d\TH:i:s.uO"); + unset($recordData["datetime"]); + + return $recordData; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php new file mode 100644 index 000000000000..10ad0d9c0a68 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\LogRecord; + +/** + * Encodes message information into JSON in a format compatible with Logmatic. + * + * @author Julien Breux + */ +class LogmaticFormatter extends JsonFormatter +{ + protected const MARKERS = ["sourcecode", "php"]; + + protected string $hostname = ''; + + protected string $appName = ''; + + public function setHostname(string $hostname): self + { + $this->hostname = $hostname; + + return $this; + } + + public function setAppName(string $appName): self + { + $this->appName = $appName; + + return $this; + } + + /** + * Appends the 'hostname' and 'appname' parameter for indexing by Logmatic. + * + * @see http://doc.logmatic.io/docs/basics-to-send-data + * @see \Monolog\Formatter\JsonFormatter::format() + */ + public function normalizeRecord(LogRecord $record): array + { + $record = parent::normalizeRecord($record); + + if ($this->hostname !== '') { + $record["hostname"] = $this->hostname; + } + if ($this->appName !== '') { + $record["appname"] = $this->appName; + } + + $record["@marker"] = static::MARKERS; + + return $record; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php new file mode 100644 index 000000000000..abee3cd13770 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\LogRecord; + +/** + * Serializes a log message to Logstash Event Format + * + * @see https://www.elastic.co/products/logstash + * @see https://github.com/elastic/logstash/blob/master/logstash-core/src/main/java/org/logstash/Event.java + * + * @author Tim Mower + */ +class LogstashFormatter extends NormalizerFormatter +{ + /** + * @var string the name of the system for the Logstash log message, used to fill the @source field + */ + protected string $systemName; + + /** + * @var string an application name for the Logstash log message, used to fill the @type field + */ + protected string $applicationName; + + /** + * @var string the key for 'extra' fields from the Monolog record + */ + protected string $extraKey; + + /** + * @var string the key for 'context' fields from the Monolog record + */ + protected string $contextKey; + + /** + * @param string $applicationName The application that sends the data, used as the "type" field of logstash + * @param string|null $systemName The system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine + * @param string $extraKey The key for extra keys inside logstash "fields", defaults to extra + * @param string $contextKey The key for context keys inside logstash "fields", defaults to context + * + * @throws \RuntimeException If the function json_encode does not exist + */ + public function __construct(string $applicationName, ?string $systemName = null, string $extraKey = 'extra', string $contextKey = 'context') + { + // logstash requires a ISO 8601 format date with optional millisecond precision. + parent::__construct('Y-m-d\TH:i:s.uP'); + + $this->systemName = $systemName === null ? (string) gethostname() : $systemName; + $this->applicationName = $applicationName; + $this->extraKey = $extraKey; + $this->contextKey = $contextKey; + } + + /** + * @inheritDoc + */ + public function format(LogRecord $record): string + { + $recordData = parent::format($record); + + $message = [ + '@timestamp' => $recordData['datetime'], + '@version' => 1, + 'host' => $this->systemName, + ]; + if (isset($recordData['message'])) { + $message['message'] = $recordData['message']; + } + if (isset($recordData['channel'])) { + $message['type'] = $recordData['channel']; + $message['channel'] = $recordData['channel']; + } + if (isset($recordData['level_name'])) { + $message['level'] = $recordData['level_name']; + } + if (isset($recordData['level'])) { + $message['monolog_level'] = $recordData['level']; + } + if ('' !== $this->applicationName) { + $message['type'] = $this->applicationName; + } + if (\count($recordData['extra']) > 0) { + $message[$this->extraKey] = $recordData['extra']; + } + if (\count($recordData['context']) > 0) { + $message[$this->contextKey] = $recordData['context']; + } + + return $this->toJson($message) . "\n"; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php new file mode 100644 index 000000000000..a3bdd4f87234 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use MongoDB\BSON\Type; +use MongoDB\BSON\UTCDateTime; +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * Formats a record for use with the MongoDBHandler. + * + * @author Florian Plattner + */ +class MongoDBFormatter implements FormatterInterface +{ + private bool $exceptionTraceAsString; + private int $maxNestingLevel; + private bool $isLegacyMongoExt; + + /** + * @param int $maxNestingLevel 0 means infinite nesting, the $record itself is level 1, $record->context is 2 + * @param bool $exceptionTraceAsString set to false to log exception traces as a sub documents instead of strings + */ + public function __construct(int $maxNestingLevel = 3, bool $exceptionTraceAsString = true) + { + $this->maxNestingLevel = max($maxNestingLevel, 0); + $this->exceptionTraceAsString = $exceptionTraceAsString; + + $this->isLegacyMongoExt = extension_loaded('mongodb') && version_compare((string) phpversion('mongodb'), '1.1.9', '<='); + } + + /** + * @inheritDoc + * + * @return mixed[] + */ + public function format(LogRecord $record): array + { + /** @var mixed[] $res */ + $res = $this->formatArray($record->toArray()); + + return $res; + } + + /** + * @inheritDoc + * + * @return array + */ + public function formatBatch(array $records): array + { + $formatted = []; + foreach ($records as $key => $record) { + $formatted[$key] = $this->format($record); + } + + return $formatted; + } + + /** + * @param mixed[] $array + * @return mixed[]|string Array except when max nesting level is reached then a string "[...]" + */ + protected function formatArray(array $array, int $nestingLevel = 0) + { + if ($this->maxNestingLevel > 0 && $nestingLevel > $this->maxNestingLevel) { + return '[...]'; + } + + foreach ($array as $name => $value) { + if ($value instanceof \DateTimeInterface) { + $array[$name] = $this->formatDate($value, $nestingLevel + 1); + } elseif ($value instanceof \Throwable) { + $array[$name] = $this->formatException($value, $nestingLevel + 1); + } elseif (is_array($value)) { + $array[$name] = $this->formatArray($value, $nestingLevel + 1); + } elseif (is_object($value) && !$value instanceof Type) { + $array[$name] = $this->formatObject($value, $nestingLevel + 1); + } + } + + return $array; + } + + /** + * @param mixed $value + * @return mixed[]|string + */ + protected function formatObject($value, int $nestingLevel) + { + $objectVars = get_object_vars($value); + $objectVars['class'] = Utils::getClass($value); + + return $this->formatArray($objectVars, $nestingLevel); + } + + /** + * @return mixed[]|string + */ + protected function formatException(\Throwable $exception, int $nestingLevel) + { + $formattedException = [ + 'class' => Utils::getClass($exception), + 'message' => $exception->getMessage(), + 'code' => (int) $exception->getCode(), + 'file' => $exception->getFile() . ':' . $exception->getLine(), + ]; + + if ($this->exceptionTraceAsString === true) { + $formattedException['trace'] = $exception->getTraceAsString(); + } else { + $formattedException['trace'] = $exception->getTrace(); + } + + return $this->formatArray($formattedException, $nestingLevel); + } + + protected function formatDate(\DateTimeInterface $value, int $nestingLevel): UTCDateTime + { + if ($this->isLegacyMongoExt) { + return $this->legacyGetMongoDbDateTime($value); + } + + return $this->getMongoDbDateTime($value); + } + + private function getMongoDbDateTime(\DateTimeInterface $value): UTCDateTime + { + return new UTCDateTime((int) floor(((float) $value->format('U.u')) * 1000)); + } + + /** + * This is needed to support MongoDB Driver v1.19 and below + * + * See https://github.com/mongodb/mongo-php-driver/issues/426 + * + * It can probably be removed in 2.1 or later once MongoDB's 1.2 is released and widely adopted + */ + private function legacyGetMongoDbDateTime(\DateTimeInterface $value): UTCDateTime + { + $milliseconds = floor(((float) $value->format('U.u')) * 1000); + + $milliseconds = (PHP_INT_SIZE == 8) //64-bit OS? + ? (int) $milliseconds + : (string) $milliseconds; + + // @phpstan-ignore-next-line + return new UTCDateTime($milliseconds); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php new file mode 100644 index 000000000000..b1214f073e0f --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php @@ -0,0 +1,305 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\DateTimeImmutable; +use Monolog\Utils; +use Throwable; +use Monolog\LogRecord; + +/** + * Normalizes incoming records to remove objects/resources so it's easier to dump to various targets + * + * @author Jordi Boggiano + */ +class NormalizerFormatter implements FormatterInterface +{ + public const SIMPLE_DATE = "Y-m-d\TH:i:sP"; + + protected string $dateFormat; + protected int $maxNormalizeDepth = 9; + protected int $maxNormalizeItemCount = 1000; + + private int $jsonEncodeOptions = Utils::DEFAULT_JSON_FLAGS; + + /** + * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format + * @throws \RuntimeException If the function json_encode does not exist + */ + public function __construct(?string $dateFormat = null) + { + $this->dateFormat = null === $dateFormat ? static::SIMPLE_DATE : $dateFormat; + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s NormalizerFormatter'); + } + } + + /** + * @inheritDoc + */ + public function format(LogRecord $record) + { + return $this->normalizeRecord($record); + } + + /** + * Normalize an arbitrary value to a scalar|array|null + * + * @return null|scalar|array + */ + public function normalizeValue(mixed $data): mixed + { + return $this->normalize($data); + } + + /** + * @inheritDoc + */ + public function formatBatch(array $records) + { + foreach ($records as $key => $record) { + $records[$key] = $this->format($record); + } + + return $records; + } + + public function getDateFormat(): string + { + return $this->dateFormat; + } + + public function setDateFormat(string $dateFormat): self + { + $this->dateFormat = $dateFormat; + + return $this; + } + + /** + * The maximum number of normalization levels to go through + */ + public function getMaxNormalizeDepth(): int + { + return $this->maxNormalizeDepth; + } + + public function setMaxNormalizeDepth(int $maxNormalizeDepth): self + { + $this->maxNormalizeDepth = $maxNormalizeDepth; + + return $this; + } + + /** + * The maximum number of items to normalize per level + */ + public function getMaxNormalizeItemCount(): int + { + return $this->maxNormalizeItemCount; + } + + public function setMaxNormalizeItemCount(int $maxNormalizeItemCount): self + { + $this->maxNormalizeItemCount = $maxNormalizeItemCount; + + return $this; + } + + /** + * Enables `json_encode` pretty print. + */ + public function setJsonPrettyPrint(bool $enable): self + { + if ($enable) { + $this->jsonEncodeOptions |= JSON_PRETTY_PRINT; + } else { + $this->jsonEncodeOptions &= ~JSON_PRETTY_PRINT; + } + + return $this; + } + + /** + * Provided as extension point + * + * Because normalize is called with sub-values of context data etc, normalizeRecord can be + * extended when data needs to be appended on the record array but not to other normalized data. + * + * @return array + */ + protected function normalizeRecord(LogRecord $record): array + { + /** @var array $normalized */ + $normalized = $this->normalize($record->toArray()); + + return $normalized; + } + + /** + * @return null|scalar|array + */ + protected function normalize(mixed $data, int $depth = 0): mixed + { + if ($depth > $this->maxNormalizeDepth) { + return 'Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization'; + } + + if (null === $data || is_scalar($data)) { + if (is_float($data)) { + if (is_infinite($data)) { + return ($data > 0 ? '' : '-') . 'INF'; + } + if (is_nan($data)) { + return 'NaN'; + } + } + + return $data; + } + + if (is_array($data)) { + $normalized = []; + + $count = 1; + foreach ($data as $key => $value) { + if ($count++ > $this->maxNormalizeItemCount) { + $normalized['...'] = 'Over ' . $this->maxNormalizeItemCount . ' items ('.count($data).' total), aborting normalization'; + break; + } + + $normalized[$key] = $this->normalize($value, $depth + 1); + } + + return $normalized; + } + + if ($data instanceof \DateTimeInterface) { + return $this->formatDate($data); + } + + if (is_object($data)) { + if ($data instanceof Throwable) { + return $this->normalizeException($data, $depth); + } + + if ($data instanceof \JsonSerializable) { + /** @var null|scalar|array $value */ + $value = $data->jsonSerialize(); + } elseif (method_exists($data, '__toString')) { + /** @var string $value */ + $value = $data->__toString(); + } else { + // the rest is normalized by json encoding and decoding it + /** @var null|scalar|array $value */ + $value = json_decode($this->toJson($data, true), true); + } + + return [Utils::getClass($data) => $value]; + } + + if (is_resource($data)) { + return sprintf('[resource(%s)]', get_resource_type($data)); + } + + return '[unknown('.gettype($data).')]'; + } + + /** + * @return mixed[] + */ + protected function normalizeException(Throwable $e, int $depth = 0) + { + if ($depth > $this->maxNormalizeDepth) { + return ['Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization']; + } + + if ($e instanceof \JsonSerializable) { + return (array) $e->jsonSerialize(); + } + + $data = [ + 'class' => Utils::getClass($e), + 'message' => $e->getMessage(), + 'code' => (int) $e->getCode(), + 'file' => $e->getFile().':'.$e->getLine(), + ]; + + if ($e instanceof \SoapFault) { + if (isset($e->faultcode)) { + $data['faultcode'] = $e->faultcode; + } + + if (isset($e->faultactor)) { + $data['faultactor'] = $e->faultactor; + } + + if (isset($e->detail)) { + if (is_string($e->detail)) { + $data['detail'] = $e->detail; + } elseif (is_object($e->detail) || is_array($e->detail)) { + $data['detail'] = $this->toJson($e->detail, true); + } + } + } + + $trace = $e->getTrace(); + foreach ($trace as $frame) { + if (isset($frame['file'], $frame['line'])) { + $data['trace'][] = $frame['file'].':'.$frame['line']; + } + } + + if (($previous = $e->getPrevious()) instanceof \Throwable) { + $data['previous'] = $this->normalizeException($previous, $depth + 1); + } + + return $data; + } + + /** + * Return the JSON representation of a value + * + * @param mixed $data + * @throws \RuntimeException if encoding fails and errors are not ignored + * @return string if encoding fails and ignoreErrors is true 'null' is returned + */ + protected function toJson($data, bool $ignoreErrors = false): string + { + return Utils::jsonEncode($data, $this->jsonEncodeOptions, $ignoreErrors); + } + + protected function formatDate(\DateTimeInterface $date): string + { + // in case the date format isn't custom then we defer to the custom DateTimeImmutable + // formatting logic, which will pick the right format based on whether useMicroseconds is on + if ($this->dateFormat === self::SIMPLE_DATE && $date instanceof DateTimeImmutable) { + return (string) $date; + } + + return $date->format($this->dateFormat); + } + + public function addJsonEncodeOption(int $option): self + { + $this->jsonEncodeOptions |= $option; + + return $this; + } + + public function removeJsonEncodeOption(int $option): self + { + $this->jsonEncodeOptions &= ~$option; + + return $this; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php new file mode 100644 index 000000000000..4bc20a08cde7 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\LogRecord; + +/** + * Formats data into an associative array of scalar (+ null) values. + * Objects and arrays will be JSON encoded. + * + * @author Andrew Lawson + */ +class ScalarFormatter extends NormalizerFormatter +{ + /** + * @inheritDoc + * + * @phpstan-return array $record + */ + public function format(LogRecord $record): array + { + $result = []; + foreach ($record->toArray() as $key => $value) { + $result[$key] = $this->toScalar($value); + } + + return $result; + } + + protected function toScalar(mixed $value): string|int|float|bool|null + { + $normalized = $this->normalize($value); + + if (is_array($normalized)) { + return $this->toJson($normalized, true); + } + + return $normalized; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/SyslogFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/SyslogFormatter.php new file mode 100644 index 000000000000..6ed7e92efa59 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/SyslogFormatter.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Level; +use Monolog\LogRecord; + +/** + * Serializes a log message according to RFC 5424 + * + * @author Dalibor Karlović + * @author Renat Gabdullin + */ +class SyslogFormatter extends LineFormatter +{ + private const SYSLOG_FACILITY_USER = 1; + private const FORMAT = "<%extra.priority%>1 %datetime% %extra.hostname% %extra.app-name% %extra.procid% %channel% %extra.structured-data% %level_name%: %message% %context% %extra%\n"; + private const NILVALUE = '-'; + + private string $hostname; + private int $procid; + + public function __construct(private string $applicationName = self::NILVALUE) + { + parent::__construct(self::FORMAT, 'Y-m-d\TH:i:s.uP', true, true); + $this->hostname = (string) gethostname(); + $this->procid = (int) getmypid(); + } + + public function format(LogRecord $record): string + { + $record->extra = $this->formatExtra($record); + + return parent::format($record); + } + + /** + * @param LogRecord $record + * @return array + */ + private function formatExtra(LogRecord $record): array + { + $extra = $record->extra; + $extra['app-name'] = $this->applicationName; + $extra['hostname'] = $this->hostname; + $extra['procid'] = $this->procid; + $extra['priority'] = self::calculatePriority($record->level); + $extra['structured-data'] = self::NILVALUE; + + return $extra; + } + + private static function calculatePriority(Level $level): int + { + return (self::SYSLOG_FACILITY_USER * 8) + $level->toRFC5424Level(); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php new file mode 100644 index 000000000000..2e28b3ab4890 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Level; +use Monolog\LogRecord; + +/** + * Serializes a log message according to Wildfire's header requirements + * + * @author Eric Clemmons (@ericclemmons) + * @author Christophe Coevoet + * @author Kirill chEbba Chebunin + */ +class WildfireFormatter extends NormalizerFormatter +{ + /** + * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format + * + * @throws \RuntimeException If the function json_encode does not exist + */ + public function __construct(?string $dateFormat = null) + { + parent::__construct($dateFormat); + + // http headers do not like non-ISO-8559-1 characters + $this->removeJsonEncodeOption(JSON_UNESCAPED_UNICODE); + } + + /** + * Translates Monolog log levels to Wildfire levels. + * + * @return 'LOG'|'INFO'|'WARN'|'ERROR' + */ + private function toWildfireLevel(Level $level): string + { + return match ($level) { + Level::Debug => 'LOG', + Level::Info => 'INFO', + Level::Notice => 'INFO', + Level::Warning => 'WARN', + Level::Error => 'ERROR', + Level::Critical => 'ERROR', + Level::Alert => 'ERROR', + Level::Emergency => 'ERROR', + }; + } + + /** + * @inheritDoc + */ + public function format(LogRecord $record): string + { + // Retrieve the line and file if set and remove them from the formatted extra + $file = $line = ''; + if (isset($record->extra['file'])) { + $file = $record->extra['file']; + unset($record->extra['file']); + } + if (isset($record->extra['line'])) { + $line = $record->extra['line']; + unset($record->extra['line']); + } + + $message = ['message' => $record->message]; + $handleError = false; + if (count($record->context) > 0) { + $message['context'] = $this->normalize($record->context); + $handleError = true; + } + if (count($record->extra) > 0) { + $message['extra'] = $this->normalize($record->extra); + $handleError = true; + } + if (count($message) === 1) { + $message = reset($message); + } + + if (is_array($message) && isset($message['context']['table'])) { + $type = 'TABLE'; + $label = $record->channel .': '. $record->message; + $message = $message['context']['table']; + } else { + $type = $this->toWildfireLevel($record->level); + $label = $record->channel; + } + + // Create JSON object describing the appearance of the message in the console + $json = $this->toJson([ + [ + 'Type' => $type, + 'File' => $file, + 'Line' => $line, + 'Label' => $label, + ], + $message, + ], $handleError); + + // The message itself is a serialization of the above JSON object + it's length + return sprintf( + '%d|%s|', + strlen($json), + $json + ); + } + + /** + * @inheritDoc + * + * @phpstan-return never + */ + public function formatBatch(array $records) + { + throw new \BadMethodCallException('Batch formatting does not make sense for the WildfireFormatter'); + } + + /** + * @inheritDoc + * + * @return null|scalar|array|object + */ + protected function normalize(mixed $data, int $depth = 0): mixed + { + if (is_object($data) && !$data instanceof \DateTimeInterface) { + return $data; + } + + return parent::normalize($data, $depth); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AbstractHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AbstractHandler.php new file mode 100644 index 000000000000..3399a54e237f --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AbstractHandler.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Logger; +use Monolog\ResettableInterface; +use Psr\Log\LogLevel; +use Monolog\LogRecord; + +/** + * Base Handler class providing basic level/bubble support + * + * @author Jordi Boggiano + */ +abstract class AbstractHandler extends Handler implements ResettableInterface +{ + protected Level $level = Level::Debug; + protected bool $bubble = true; + + /** + * @param int|string|Level|LogLevel::* $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + public function __construct(int|string|Level $level = Level::Debug, bool $bubble = true) + { + $this->setLevel($level); + $this->bubble = $bubble; + } + + /** + * @inheritDoc + */ + public function isHandling(LogRecord $record): bool + { + return $record->level->value >= $this->level->value; + } + + /** + * Sets minimum logging level at which this handler will be triggered. + * + * @param Level|LogLevel::* $level Level or level name + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + public function setLevel(int|string|Level $level): self + { + $this->level = Logger::toMonologLevel($level); + + return $this; + } + + /** + * Gets minimum logging level at which this handler will be triggered. + */ + public function getLevel(): Level + { + return $this->level; + } + + /** + * Sets the bubbling behavior. + * + * @param bool $bubble true means that this handler allows bubbling. + * false means that bubbling is not permitted. + */ + public function setBubble(bool $bubble): self + { + $this->bubble = $bubble; + + return $this; + } + + /** + * Gets the bubbling behavior. + * + * @return bool true means that this handler allows bubbling. + * false means that bubbling is not permitted. + */ + public function getBubble(): bool + { + return $this->bubble; + } + + /** + * @inheritDoc + */ + public function reset(): void + { + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php new file mode 100644 index 000000000000..de13a76bec87 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\LogRecord; + +/** + * Base Handler class providing the Handler structure, including processors and formatters + * + * Classes extending it should (in most cases) only implement write($record) + * + * @author Jordi Boggiano + * @author Christophe Coevoet + */ +abstract class AbstractProcessingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface +{ + use ProcessableHandlerTrait; + use FormattableHandlerTrait; + + /** + * @inheritDoc + */ + public function handle(LogRecord $record): bool + { + if (!$this->isHandling($record)) { + return false; + } + + if (\count($this->processors) > 0) { + $record = $this->processRecord($record); + } + + $record->formatted = $this->getFormatter()->format($record); + + $this->write($record); + + return false === $this->bubble; + } + + /** + * Writes the (already formatted) record down to the log of the implementing handler + */ + abstract protected function write(LogRecord $record): void; + + public function reset(): void + { + parent::reset(); + + $this->resetProcessors(); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php new file mode 100644 index 000000000000..695a1c07f4f2 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; + +/** + * Common syslog functionality + */ +abstract class AbstractSyslogHandler extends AbstractProcessingHandler +{ + protected int $facility; + + /** + * List of valid log facility names. + * @var array + */ + protected array $facilities = [ + 'auth' => \LOG_AUTH, + 'authpriv' => \LOG_AUTHPRIV, + 'cron' => \LOG_CRON, + 'daemon' => \LOG_DAEMON, + 'kern' => \LOG_KERN, + 'lpr' => \LOG_LPR, + 'mail' => \LOG_MAIL, + 'news' => \LOG_NEWS, + 'syslog' => \LOG_SYSLOG, + 'user' => \LOG_USER, + 'uucp' => \LOG_UUCP, + ]; + + /** + * Translates Monolog log levels to syslog log priorities. + */ + protected function toSyslogPriority(Level $level): int + { + return $level->toRFC5424Level(); + } + + /** + * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant + */ + public function __construct(string|int $facility = \LOG_USER, int|string|Level $level = Level::Debug, bool $bubble = true) + { + parent::__construct($level, $bubble); + + if (!defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->facilities['local0'] = \LOG_LOCAL0; + $this->facilities['local1'] = \LOG_LOCAL1; + $this->facilities['local2'] = \LOG_LOCAL2; + $this->facilities['local3'] = \LOG_LOCAL3; + $this->facilities['local4'] = \LOG_LOCAL4; + $this->facilities['local5'] = \LOG_LOCAL5; + $this->facilities['local6'] = \LOG_LOCAL6; + $this->facilities['local7'] = \LOG_LOCAL7; + } else { + $this->facilities['local0'] = 128; // LOG_LOCAL0 + $this->facilities['local1'] = 136; // LOG_LOCAL1 + $this->facilities['local2'] = 144; // LOG_LOCAL2 + $this->facilities['local3'] = 152; // LOG_LOCAL3 + $this->facilities['local4'] = 160; // LOG_LOCAL4 + $this->facilities['local5'] = 168; // LOG_LOCAL5 + $this->facilities['local6'] = 176; // LOG_LOCAL6 + $this->facilities['local7'] = 184; // LOG_LOCAL7 + } + + // convert textual description of facility to syslog constant + if (is_string($facility) && array_key_exists(strtolower($facility), $this->facilities)) { + $facility = $this->facilities[strtolower($facility)]; + } elseif (!in_array($facility, array_values($this->facilities), true)) { + throw new \UnexpectedValueException('Unknown facility value "'.$facility.'" given'); + } + + $this->facility = $facility; + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter('%channel%.%level_name%: %message% %context% %extra%'); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AmqpHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AmqpHandler.php new file mode 100644 index 000000000000..72265d4baca0 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AmqpHandler.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\JsonFormatter; +use PhpAmqpLib\Message\AMQPMessage; +use PhpAmqpLib\Channel\AMQPChannel; +use AMQPExchange; +use Monolog\LogRecord; + +class AmqpHandler extends AbstractProcessingHandler +{ + protected AMQPExchange|AMQPChannel $exchange; + + /** @var array */ + private array $extraAttributes = []; + + protected string $exchangeName; + + /** + * @param AMQPExchange|AMQPChannel $exchange AMQPExchange (php AMQP ext) or PHP AMQP lib channel, ready for use + * @param string|null $exchangeName Optional exchange name, for AMQPChannel (PhpAmqpLib) only + */ + public function __construct(AMQPExchange|AMQPChannel $exchange, ?string $exchangeName = null, int|string|Level $level = Level::Debug, bool $bubble = true) + { + if ($exchange instanceof AMQPChannel) { + $this->exchangeName = (string) $exchangeName; + } elseif ($exchangeName !== null) { + @trigger_error('The $exchangeName parameter can only be passed when using PhpAmqpLib, if using an AMQPExchange instance configure it beforehand', E_USER_DEPRECATED); + } + $this->exchange = $exchange; + + parent::__construct($level, $bubble); + } + + /** + * @return array + */ + public function getExtraAttributes(): array + { + return $this->extraAttributes; + } + + /** + * Configure extra attributes to pass to the AMQPExchange (if you are using the amqp extension) + * + * @param array $extraAttributes One of content_type, content_encoding, + * message_id, user_id, app_id, delivery_mode, + * priority, timestamp, expiration, type + * or reply_to, headers. + * @return $this + */ + public function setExtraAttributes(array $extraAttributes): self + { + $this->extraAttributes = $extraAttributes; + return $this; + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $data = $record->formatted; + $routingKey = $this->getRoutingKey($record); + + if ($this->exchange instanceof AMQPExchange) { + $attributes = [ + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ]; + if (\count($this->extraAttributes) > 0) { + $attributes = array_merge($attributes, $this->extraAttributes); + } + $this->exchange->publish( + $data, + $routingKey, + 0, + $attributes + ); + } else { + $this->exchange->basic_publish( + $this->createAmqpMessage($data), + $this->exchangeName, + $routingKey + ); + } + } + + /** + * @inheritDoc + */ + public function handleBatch(array $records): void + { + if ($this->exchange instanceof AMQPExchange) { + parent::handleBatch($records); + + return; + } + + foreach ($records as $record) { + if (!$this->isHandling($record)) { + continue; + } + + $record = $this->processRecord($record); + $data = $this->getFormatter()->format($record); + + $this->exchange->batch_basic_publish( + $this->createAmqpMessage($data), + $this->exchangeName, + $this->getRoutingKey($record) + ); + } + + $this->exchange->publish_batch(); + } + + /** + * Gets the routing key for the AMQP exchange + */ + protected function getRoutingKey(LogRecord $record): string + { + $routingKey = sprintf('%s.%s', $record->level->name, $record->channel); + + return strtolower($routingKey); + } + + private function createAmqpMessage(string $data): AMQPMessage + { + $attributes = [ + 'delivery_mode' => 2, + 'content_type' => 'application/json', + ]; + if (\count($this->extraAttributes) > 0) { + $attributes = array_merge($attributes, $this->extraAttributes); + } + return new AMQPMessage($data, $attributes); + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php new file mode 100644 index 000000000000..5930ca488e69 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php @@ -0,0 +1,301 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; +use Monolog\Utils; +use Monolog\LogRecord; +use Monolog\Level; + +use function count; +use function headers_list; +use function stripos; + +/** + * Handler sending logs to browser's javascript console with no browser extension required + * + * @author Olivier Poitrey + */ +class BrowserConsoleHandler extends AbstractProcessingHandler +{ + protected static bool $initialized = false; + + /** @var LogRecord[] */ + protected static array $records = []; + + protected const FORMAT_HTML = 'html'; + protected const FORMAT_JS = 'js'; + protected const FORMAT_UNKNOWN = 'unknown'; + + /** + * @inheritDoc + * + * Formatted output may contain some formatting markers to be transferred to `console.log` using the %c format. + * + * Example of formatted string: + * + * You can do [[blue text]]{color: blue} or [[green background]]{background-color: green; color: white} + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter('[[%channel%]]{macro: autolabel} [[%level_name%]]{font-weight: bold} %message%'); + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + // Accumulate records + static::$records[] = $record; + + // Register shutdown handler if not already done + if (!static::$initialized) { + static::$initialized = true; + $this->registerShutdownFunction(); + } + } + + /** + * Convert records to javascript console commands and send it to the browser. + * This method is automatically called on PHP shutdown if output is HTML or Javascript. + */ + public static function send(): void + { + $format = static::getResponseFormat(); + if ($format === self::FORMAT_UNKNOWN) { + return; + } + + if (count(static::$records) > 0) { + if ($format === self::FORMAT_HTML) { + static::writeOutput(''); + } else { // js format + static::writeOutput(self::generateScript()); + } + static::resetStatic(); + } + } + + public function close(): void + { + self::resetStatic(); + } + + public function reset(): void + { + parent::reset(); + + self::resetStatic(); + } + + /** + * Forget all logged records + */ + public static function resetStatic(): void + { + static::$records = []; + } + + /** + * Wrapper for register_shutdown_function to allow overriding + */ + protected function registerShutdownFunction(): void + { + if (PHP_SAPI !== 'cli') { + register_shutdown_function(['Monolog\Handler\BrowserConsoleHandler', 'send']); + } + } + + /** + * Wrapper for echo to allow overriding + */ + protected static function writeOutput(string $str): void + { + echo $str; + } + + /** + * Checks the format of the response + * + * If Content-Type is set to application/javascript or text/javascript -> js + * If Content-Type is set to text/html, or is unset -> html + * If Content-Type is anything else -> unknown + * + * @return string One of 'js', 'html' or 'unknown' + * @phpstan-return self::FORMAT_* + */ + protected static function getResponseFormat(): string + { + // Check content type + foreach (headers_list() as $header) { + if (stripos($header, 'content-type:') === 0) { + return static::getResponseFormatFromContentType($header); + } + } + + return self::FORMAT_HTML; + } + + /** + * @return string One of 'js', 'html' or 'unknown' + * @phpstan-return self::FORMAT_* + */ + protected static function getResponseFormatFromContentType(string $contentType): string + { + // This handler only works with HTML and javascript outputs + // text/javascript is obsolete in favour of application/javascript, but still used + if (stripos($contentType, 'application/javascript') !== false || stripos($contentType, 'text/javascript') !== false) { + return self::FORMAT_JS; + } + + if (stripos($contentType, 'text/html') !== false) { + return self::FORMAT_HTML; + } + + return self::FORMAT_UNKNOWN; + } + + private static function generateScript(): string + { + $script = []; + foreach (static::$records as $record) { + $context = self::dump('Context', $record->context); + $extra = self::dump('Extra', $record->extra); + + if (\count($context) === 0 && \count($extra) === 0) { + $script[] = self::call_array(self::getConsoleMethodForLevel($record->level), self::handleStyles($record->formatted)); + } else { + $script = array_merge( + $script, + [self::call_array('groupCollapsed', self::handleStyles($record->formatted))], + $context, + $extra, + [self::call('groupEnd')] + ); + } + } + + return "(function (c) {if (c && c.groupCollapsed) {\n" . implode("\n", $script) . "\n}})(console);"; + } + + private static function getConsoleMethodForLevel(Level $level): string + { + return match ($level) { + Level::Debug => 'debug', + Level::Info, Level::Notice => 'info', + Level::Warning => 'warn', + Level::Error, Level::Critical, Level::Alert, Level::Emergency => 'error', + }; + } + + /** + * @return string[] + */ + private static function handleStyles(string $formatted): array + { + $args = []; + $format = '%c' . $formatted; + preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); + + foreach (array_reverse($matches) as $match) { + $args[] = '"font-weight: normal"'; + $args[] = self::quote(self::handleCustomStyles($match[2][0], $match[1][0])); + + $pos = $match[0][1]; + $format = Utils::substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . Utils::substr($format, $pos + strlen($match[0][0])); + } + + $args[] = self::quote('font-weight: normal'); + $args[] = self::quote($format); + + return array_reverse($args); + } + + private static function handleCustomStyles(string $style, string $string): string + { + static $colors = ['blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey']; + static $labels = []; + + $style = preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function (array $m) use ($string, &$colors, &$labels) { + if (trim($m[1]) === 'autolabel') { + // Format the string as a label with consistent auto assigned background color + if (!isset($labels[$string])) { + $labels[$string] = $colors[count($labels) % count($colors)]; + } + $color = $labels[$string]; + + return "background-color: $color; color: white; border-radius: 3px; padding: 0 2px 0 2px"; + } + + return $m[1]; + }, $style); + + if (null === $style) { + $pcreErrorCode = preg_last_error(); + + throw new \RuntimeException('Failed to run preg_replace_callback: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode)); + } + + return $style; + } + + /** + * @param mixed[] $dict + * @return mixed[] + */ + private static function dump(string $title, array $dict): array + { + $script = []; + $dict = array_filter($dict); + if (\count($dict) === 0) { + return $script; + } + $script[] = self::call('log', self::quote('%c%s'), self::quote('font-weight: bold'), self::quote($title)); + foreach ($dict as $key => $value) { + $value = json_encode($value); + if (false === $value) { + $value = self::quote(''); + } + $script[] = self::call('log', self::quote('%s: %o'), self::quote((string) $key), $value); + } + + return $script; + } + + private static function quote(string $arg): string + { + return '"' . addcslashes($arg, "\"\n\\") . '"'; + } + + /** + * @param mixed $args + */ + private static function call(...$args): string + { + $method = array_shift($args); + if (!is_string($method)) { + throw new \UnexpectedValueException('Expected the first arg to be a string, got: '.var_export($method, true)); + } + + return self::call_array($method, $args); + } + + /** + * @param mixed[] $args + */ + private static function call_array(string $method, array $args): string + { + return 'c.' . $method . '(' . implode(', ', $args) . ');'; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/BufferHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/BufferHandler.php new file mode 100644 index 000000000000..ff89faa8a31f --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/BufferHandler.php @@ -0,0 +1,165 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\ResettableInterface; +use Monolog\Formatter\FormatterInterface; +use Monolog\LogRecord; + +/** + * Buffers all records until closing the handler and then pass them as batch. + * + * This is useful for a MailHandler to send only one mail per request instead of + * sending one per log message. + * + * @author Christophe Coevoet + */ +class BufferHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface +{ + use ProcessableHandlerTrait; + + protected HandlerInterface $handler; + + protected int $bufferSize = 0; + + protected int $bufferLimit; + + protected bool $flushOnOverflow; + + /** @var LogRecord[] */ + protected array $buffer = []; + + protected bool $initialized = false; + + /** + * @param HandlerInterface $handler Handler. + * @param int $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param bool $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded + */ + public function __construct(HandlerInterface $handler, int $bufferLimit = 0, int|string|Level $level = Level::Debug, bool $bubble = true, bool $flushOnOverflow = false) + { + parent::__construct($level, $bubble); + $this->handler = $handler; + $this->bufferLimit = $bufferLimit; + $this->flushOnOverflow = $flushOnOverflow; + } + + /** + * @inheritDoc + */ + public function handle(LogRecord $record): bool + { + if ($record->level->isLowerThan($this->level)) { + return false; + } + + if (!$this->initialized) { + // __destructor() doesn't get called on Fatal errors + register_shutdown_function([$this, 'close']); + $this->initialized = true; + } + + if ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) { + if ($this->flushOnOverflow) { + $this->flush(); + } else { + array_shift($this->buffer); + $this->bufferSize--; + } + } + + if (\count($this->processors) > 0) { + $record = $this->processRecord($record); + } + + $this->buffer[] = $record; + $this->bufferSize++; + + return false === $this->bubble; + } + + public function flush(): void + { + if ($this->bufferSize === 0) { + return; + } + + $this->handler->handleBatch($this->buffer); + $this->clear(); + } + + public function __destruct() + { + // suppress the parent behavior since we already have register_shutdown_function() + // to call close(), and the reference contained there will prevent this from being + // GC'd until the end of the request + } + + /** + * @inheritDoc + */ + public function close(): void + { + $this->flush(); + + $this->handler->close(); + } + + /** + * Clears the buffer without flushing any messages down to the wrapped handler. + */ + public function clear(): void + { + $this->bufferSize = 0; + $this->buffer = []; + } + + public function reset(): void + { + $this->flush(); + + parent::reset(); + + $this->resetProcessors(); + + if ($this->handler instanceof ResettableInterface) { + $this->handler->reset(); + } + } + + /** + * @inheritDoc + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + $this->handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); + } + + /** + * @inheritDoc + */ + public function getFormatter(): FormatterInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + return $this->handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php new file mode 100644 index 000000000000..2f7f21d5f427 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php @@ -0,0 +1,192 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\ChromePHPFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Level; +use Monolog\Utils; +use Monolog\LogRecord; +use Monolog\DateTimeImmutable; + +/** + * Handler sending logs to the ChromePHP extension (http://www.chromephp.com/) + * + * This also works out of the box with Firefox 43+ + * + * @author Christophe Coevoet + */ +class ChromePHPHandler extends AbstractProcessingHandler +{ + use WebRequestRecognizerTrait; + + /** + * Version of the extension + */ + protected const VERSION = '4.0'; + + /** + * Header name + */ + protected const HEADER_NAME = 'X-ChromeLogger-Data'; + + /** + * Regular expression to detect supported browsers (matches any Chrome, or Firefox 43+) + */ + protected const USER_AGENT_REGEX = '{\b(?:Chrome/\d+(?:\.\d+)*|HeadlessChrome|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}'; + + protected static bool $initialized = false; + + /** + * Tracks whether we sent too much data + * + * Chrome limits the headers to 4KB, so when we sent 3KB we stop sending + */ + protected static bool $overflowed = false; + + /** @var mixed[] */ + protected static array $json = [ + 'version' => self::VERSION, + 'columns' => ['label', 'log', 'backtrace', 'type'], + 'rows' => [], + ]; + + protected static bool $sendHeaders = true; + + /** + * @throws \RuntimeException If the function json_encode does not exist + */ + public function __construct(int|string|Level $level = Level::Debug, bool $bubble = true) + { + parent::__construct($level, $bubble); + if (!function_exists('json_encode')) { + throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s ChromePHPHandler'); + } + } + + /** + * @inheritDoc + */ + public function handleBatch(array $records): void + { + if (!$this->isWebRequest()) { + return; + } + + $messages = []; + + foreach ($records as $record) { + if ($record->level < $this->level) { + continue; + } + + $message = $this->processRecord($record); + $messages[] = $message; + } + + if (\count($messages) > 0) { + $messages = $this->getFormatter()->formatBatch($messages); + self::$json['rows'] = array_merge(self::$json['rows'], $messages); + $this->send(); + } + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new ChromePHPFormatter(); + } + + /** + * Creates & sends header for a record + * + * @see sendHeader() + * @see send() + */ + protected function write(LogRecord $record): void + { + if (!$this->isWebRequest()) { + return; + } + + self::$json['rows'][] = $record->formatted; + + $this->send(); + } + + /** + * Sends the log header + * + * @see sendHeader() + */ + protected function send(): void + { + if (self::$overflowed || !self::$sendHeaders) { + return; + } + + if (!self::$initialized) { + self::$initialized = true; + + self::$sendHeaders = $this->headersAccepted(); + if (!self::$sendHeaders) { + return; + } + + self::$json['request_uri'] = $_SERVER['REQUEST_URI'] ?? ''; + } + + $json = Utils::jsonEncode(self::$json, Utils::DEFAULT_JSON_FLAGS & ~JSON_UNESCAPED_UNICODE, true); + $data = base64_encode($json); + if (strlen($data) > 3 * 1024) { + self::$overflowed = true; + + $record = new LogRecord( + message: 'Incomplete logs, chrome header size limit reached', + level: Level::Warning, + channel: 'monolog', + datetime: new DateTimeImmutable(true), + ); + self::$json['rows'][count(self::$json['rows']) - 1] = $this->getFormatter()->format($record); + $json = Utils::jsonEncode(self::$json, Utils::DEFAULT_JSON_FLAGS & ~JSON_UNESCAPED_UNICODE, true); + $data = base64_encode($json); + } + + if (trim($data) !== '') { + $this->sendHeader(static::HEADER_NAME, $data); + } + } + + /** + * Send header string to the client + */ + protected function sendHeader(string $header, string $content): void + { + if (!headers_sent() && self::$sendHeaders) { + header(sprintf('%s: %s', $header, $content)); + } + } + + /** + * Verifies if the headers are accepted by the current user agent + */ + protected function headersAccepted(): bool + { + if (!isset($_SERVER['HTTP_USER_AGENT'])) { + return false; + } + + return preg_match(static::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']) === 1; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php new file mode 100644 index 000000000000..8d9c10e76132 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\JsonFormatter; +use Monolog\Level; +use Monolog\LogRecord; + +/** + * CouchDB handler + * + * @author Markus Bachmann + * @phpstan-type Options array{ + * host: string, + * port: int, + * dbname: string, + * username: string|null, + * password: string|null + * } + * @phpstan-type InputOptions array{ + * host?: string, + * port?: int, + * dbname?: string, + * username?: string|null, + * password?: string|null + * } + */ +class CouchDBHandler extends AbstractProcessingHandler +{ + /** + * @var mixed[] + * @phpstan-var Options + */ + private array $options; + + /** + * @param mixed[] $options + * + * @phpstan-param InputOptions $options + */ + public function __construct(array $options = [], int|string|Level $level = Level::Debug, bool $bubble = true) + { + $this->options = array_merge([ + 'host' => 'localhost', + 'port' => 5984, + 'dbname' => 'logger', + 'username' => null, + 'password' => null, + ], $options); + + parent::__construct($level, $bubble); + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $basicAuth = null; + if (null !== $this->options['username'] && null !== $this->options['password']) { + $basicAuth = sprintf('%s:%s@', $this->options['username'], $this->options['password']); + } + + $url = 'http://'.$basicAuth.$this->options['host'].':'.$this->options['port'].'/'.$this->options['dbname']; + $context = stream_context_create([ + 'http' => [ + 'method' => 'POST', + 'content' => $record->formatted, + 'ignore_errors' => true, + 'max_redirects' => 0, + 'header' => 'Content-type: application/json', + ], + ]); + + if (false === @file_get_contents($url, false, $context)) { + throw new \RuntimeException(sprintf('Could not connect to %s', $url)); + } + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/CubeHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/CubeHandler.php new file mode 100644 index 000000000000..8388f5ade74d --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/CubeHandler.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * Logs to Cube. + * + * @link https://github.com/square/cube/wiki + * @author Wan Chen + * @deprecated Since 2.8.0 and 3.2.0, Cube appears abandoned and thus we will drop this handler in Monolog 4 + */ +class CubeHandler extends AbstractProcessingHandler +{ + private ?\Socket $udpConnection = null; + private ?\CurlHandle $httpConnection = null; + private string $scheme; + private string $host; + private int $port; + /** @var string[] */ + private array $acceptedSchemes = ['http', 'udp']; + + /** + * Create a Cube handler + * + * @throws \UnexpectedValueException when given url is not a valid url. + * A valid url must consist of three parts : protocol://host:port + * Only valid protocols used by Cube are http and udp + */ + public function __construct(string $url, int|string|Level $level = Level::Debug, bool $bubble = true) + { + $urlInfo = parse_url($url); + + if ($urlInfo === false || !isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) { + throw new \UnexpectedValueException('URL "'.$url.'" is not valid'); + } + + if (!in_array($urlInfo['scheme'], $this->acceptedSchemes, true)) { + throw new \UnexpectedValueException( + 'Invalid protocol (' . $urlInfo['scheme'] . ').' + . ' Valid options are ' . implode(', ', $this->acceptedSchemes) + ); + } + + $this->scheme = $urlInfo['scheme']; + $this->host = $urlInfo['host']; + $this->port = $urlInfo['port']; + + parent::__construct($level, $bubble); + } + + /** + * Establish a connection to an UDP socket + * + * @throws \LogicException when unable to connect to the socket + * @throws MissingExtensionException when there is no socket extension + */ + protected function connectUdp(): void + { + if (!extension_loaded('sockets')) { + throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler'); + } + + $udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0); + if (false === $udpConnection) { + throw new \LogicException('Unable to create a socket'); + } + + $this->udpConnection = $udpConnection; + if (!socket_connect($this->udpConnection, $this->host, $this->port)) { + throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port); + } + } + + /** + * Establish a connection to an http server + * + * @throws \LogicException when unable to connect to the socket + * @throws MissingExtensionException when no curl extension + */ + protected function connectHttp(): void + { + if (!extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is required to use http URLs with the CubeHandler'); + } + + $httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put'); + if (false === $httpConnection) { + throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port); + } + + $this->httpConnection = $httpConnection; + curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST"); + curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true); + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $date = $record->datetime; + + $data = ['time' => $date->format('Y-m-d\TH:i:s.uO')]; + $context = $record->context; + + if (isset($context['type'])) { + $data['type'] = $context['type']; + unset($context['type']); + } else { + $data['type'] = $record->channel; + } + + $data['data'] = $context; + $data['data']['level'] = $record->level; + + if ($this->scheme === 'http') { + $this->writeHttp(Utils::jsonEncode($data)); + } else { + $this->writeUdp(Utils::jsonEncode($data)); + } + } + + private function writeUdp(string $data): void + { + if (null === $this->udpConnection) { + $this->connectUdp(); + } + + if (null === $this->udpConnection) { + throw new \LogicException('No UDP socket could be opened'); + } + + socket_send($this->udpConnection, $data, strlen($data), 0); + } + + private function writeHttp(string $data): void + { + if (null === $this->httpConnection) { + $this->connectHttp(); + } + + if (null === $this->httpConnection) { + throw new \LogicException('No connection could be established'); + } + + curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']'); + curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json', + 'Content-Length: ' . strlen('['.$data.']'), + ]); + + Curl\Util::execute($this->httpConnection, 5, false); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/Curl/Util.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/Curl/Util.php new file mode 100644 index 000000000000..4decf0e62ed9 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/Curl/Util.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\Curl; + +use CurlHandle; + +/** + * This class is marked as internal and it is not under the BC promise of the package. + * + * @internal + */ +final class Util +{ + /** @var array */ + private static array $retriableErrorCodes = [ + CURLE_COULDNT_RESOLVE_HOST, + CURLE_COULDNT_CONNECT, + CURLE_HTTP_NOT_FOUND, + CURLE_READ_ERROR, + CURLE_OPERATION_TIMEOUTED, + CURLE_HTTP_POST_ERROR, + CURLE_SSL_CONNECT_ERROR, + ]; + + /** + * Executes a CURL request with optional retries and exception on failure + * + * @param CurlHandle $ch curl handler + * @return bool|string @see curl_exec + */ + public static function execute(CurlHandle $ch, int $retries = 5, bool $closeAfterDone = true) + { + while ($retries--) { + $curlResponse = curl_exec($ch); + if ($curlResponse === false) { + $curlErrno = curl_errno($ch); + + if (false === in_array($curlErrno, self::$retriableErrorCodes, true) || $retries === 0) { + $curlError = curl_error($ch); + + if ($closeAfterDone) { + curl_close($ch); + } + + throw new \RuntimeException(sprintf('Curl error (code %d): %s', $curlErrno, $curlError)); + } + + continue; + } + + if ($closeAfterDone) { + curl_close($ch); + } + + return $curlResponse; + } + + return false; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php new file mode 100644 index 000000000000..b8ec90099bb4 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Logger; +use Psr\Log\LogLevel; +use Monolog\LogRecord; + +/** + * Simple handler wrapper that deduplicates log records across multiple requests + * + * It also includes the BufferHandler functionality and will buffer + * all messages until the end of the request or flush() is called. + * + * This works by storing all log records' messages above $deduplicationLevel + * to the file specified by $deduplicationStore. When further logs come in at the end of the + * request (or when flush() is called), all those above $deduplicationLevel are checked + * against the existing stored logs. If they match and the timestamps in the stored log is + * not older than $time seconds, the new log record is discarded. If no log record is new, the + * whole data set is discarded. + * + * This is mainly useful in combination with Mail handlers or things like Slack or HipChat handlers + * that send messages to people, to avoid spamming with the same message over and over in case of + * a major component failure like a database server being down which makes all requests fail in the + * same way. + * + * @author Jordi Boggiano + */ +class DeduplicationHandler extends BufferHandler +{ + protected string $deduplicationStore; + + protected Level $deduplicationLevel; + + protected int $time; + + private bool $gc = false; + + /** + * @param HandlerInterface $handler Handler. + * @param string|null $deduplicationStore The file/path where the deduplication log should be kept + * @param int|string|Level|LogLevel::* $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes + * @param int $time The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $deduplicationLevel + */ + public function __construct(HandlerInterface $handler, ?string $deduplicationStore = null, int|string|Level $deduplicationLevel = Level::Error, int $time = 60, bool $bubble = true) + { + parent::__construct($handler, 0, Level::Debug, $bubble, false); + + $this->deduplicationStore = $deduplicationStore === null ? sys_get_temp_dir() . '/monolog-dedup-' . substr(md5(__FILE__), 0, 20) .'.log' : $deduplicationStore; + $this->deduplicationLevel = Logger::toMonologLevel($deduplicationLevel); + $this->time = $time; + } + + public function flush(): void + { + if ($this->bufferSize === 0) { + return; + } + + $passthru = null; + + foreach ($this->buffer as $record) { + if ($record->level->value >= $this->deduplicationLevel->value) { + $passthru = $passthru === true || !$this->isDuplicate($record); + if ($passthru) { + $this->appendRecord($record); + } + } + } + + // default of null is valid as well as if no record matches duplicationLevel we just pass through + if ($passthru === true || $passthru === null) { + $this->handler->handleBatch($this->buffer); + } + + $this->clear(); + + if ($this->gc) { + $this->collectLogs(); + } + } + + private function isDuplicate(LogRecord $record): bool + { + if (!file_exists($this->deduplicationStore)) { + return false; + } + + $store = file($this->deduplicationStore, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if (!is_array($store)) { + return false; + } + + $yesterday = time() - 86400; + $timestampValidity = $record->datetime->getTimestamp() - $this->time; + $expectedMessage = preg_replace('{[\r\n].*}', '', $record->message); + + for ($i = count($store) - 1; $i >= 0; $i--) { + list($timestamp, $level, $message) = explode(':', $store[$i], 3); + + if ($level === $record->level->getName() && $message === $expectedMessage && $timestamp > $timestampValidity) { + return true; + } + + if ($timestamp < $yesterday) { + $this->gc = true; + } + } + + return false; + } + + private function collectLogs(): void + { + if (!file_exists($this->deduplicationStore)) { + return; + } + + $handle = fopen($this->deduplicationStore, 'rw+'); + + if (false === $handle) { + throw new \RuntimeException('Failed to open file for reading and writing: ' . $this->deduplicationStore); + } + + flock($handle, LOCK_EX); + $validLogs = []; + + $timestampValidity = time() - $this->time; + + while (!feof($handle)) { + $log = fgets($handle); + if (is_string($log) && '' !== $log && substr($log, 0, 10) >= $timestampValidity) { + $validLogs[] = $log; + } + } + + ftruncate($handle, 0); + rewind($handle); + foreach ($validLogs as $log) { + fwrite($handle, $log); + } + + flock($handle, LOCK_UN); + fclose($handle); + + $this->gc = false; + } + + private function appendRecord(LogRecord $record): void + { + file_put_contents($this->deduplicationStore, $record->datetime->getTimestamp() . ':' . $record->level->getName() . ':' . preg_replace('{[\r\n].*}', '', $record->message) . "\n", FILE_APPEND); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php new file mode 100644 index 000000000000..eab9f1089d83 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Formatter\FormatterInterface; +use Doctrine\CouchDB\CouchDBClient; +use Monolog\LogRecord; + +/** + * CouchDB handler for Doctrine CouchDB ODM + * + * @author Markus Bachmann + */ +class DoctrineCouchDBHandler extends AbstractProcessingHandler +{ + private CouchDBClient $client; + + public function __construct(CouchDBClient $client, int|string|Level $level = Level::Debug, bool $bubble = true) + { + $this->client = $client; + parent::__construct($level, $bubble); + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $this->client->postDocument($record->formatted); + } + + protected function getDefaultFormatter(): FormatterInterface + { + return new NormalizerFormatter; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php new file mode 100644 index 000000000000..f1c5a9590dfd --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Aws\Sdk; +use Aws\DynamoDb\DynamoDbClient; +use Monolog\Formatter\FormatterInterface; +use Aws\DynamoDb\Marshaler; +use Monolog\Formatter\ScalarFormatter; +use Monolog\Level; +use Monolog\LogRecord; + +/** + * Amazon DynamoDB handler (http://aws.amazon.com/dynamodb/) + * + * @link https://github.com/aws/aws-sdk-php/ + * @author Andrew Lawson + */ +class DynamoDbHandler extends AbstractProcessingHandler +{ + public const DATE_FORMAT = 'Y-m-d\TH:i:s.uO'; + + protected DynamoDbClient $client; + + protected string $table; + + protected Marshaler $marshaler; + + public function __construct(DynamoDbClient $client, string $table, int|string|Level $level = Level::Debug, bool $bubble = true) + { + $this->marshaler = new Marshaler; + + $this->client = $client; + $this->table = $table; + + parent::__construct($level, $bubble); + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $filtered = $this->filterEmptyFields($record->formatted); + $formatted = $this->marshaler->marshalItem($filtered); + + $this->client->putItem([ + 'TableName' => $this->table, + 'Item' => $formatted, + ]); + } + + /** + * @param mixed[] $record + * @return mixed[] + */ + protected function filterEmptyFields(array $record): array + { + return array_filter($record, function ($value) { + return [] !== $value; + }); + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new ScalarFormatter(self::DATE_FORMAT); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php new file mode 100644 index 000000000000..d9b85b4d0ec8 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Elastica\Document; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\ElasticaFormatter; +use Monolog\Level; +use Elastica\Client; +use Elastica\Exception\ExceptionInterface; +use Monolog\LogRecord; + +/** + * Elastic Search handler + * + * Usage example: + * + * $client = new \Elastica\Client(); + * $options = array( + * 'index' => 'elastic_index_name', + * 'type' => 'elastic_doc_type', Types have been removed in Elastica 7 + * ); + * $handler = new ElasticaHandler($client, $options); + * $log = new Logger('application'); + * $log->pushHandler($handler); + * + * @author Jelle Vink + * @phpstan-type Options array{ + * index: string, + * type: string, + * ignore_error: bool + * } + * @phpstan-type InputOptions array{ + * index?: string, + * type?: string, + * ignore_error?: bool + * } + */ +class ElasticaHandler extends AbstractProcessingHandler +{ + protected Client $client; + + /** + * @var mixed[] Handler config options + * @phpstan-var Options + */ + protected array $options; + + /** + * @param Client $client Elastica Client object + * @param mixed[] $options Handler configuration + * + * @phpstan-param InputOptions $options + */ + public function __construct(Client $client, array $options = [], int|string|Level $level = Level::Debug, bool $bubble = true) + { + parent::__construct($level, $bubble); + $this->client = $client; + $this->options = array_merge( + [ + 'index' => 'monolog', // Elastic index name + 'type' => 'record', // Elastic document type + 'ignore_error' => false, // Suppress Elastica exceptions + ], + $options + ); + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $this->bulkSend([$record->formatted]); + } + + /** + * @inheritDoc + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($formatter instanceof ElasticaFormatter) { + return parent::setFormatter($formatter); + } + + throw new \InvalidArgumentException('ElasticaHandler is only compatible with ElasticaFormatter'); + } + + /** + * @return mixed[] + * + * @phpstan-return Options + */ + public function getOptions(): array + { + return $this->options; + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new ElasticaFormatter($this->options['index'], $this->options['type']); + } + + /** + * @inheritDoc + */ + public function handleBatch(array $records): void + { + $documents = $this->getFormatter()->formatBatch($records); + $this->bulkSend($documents); + } + + /** + * Use Elasticsearch bulk API to send list of documents + * + * @param Document[] $documents + * + * @throws \RuntimeException + */ + protected function bulkSend(array $documents): void + { + try { + $this->client->addDocuments($documents); + } catch (ExceptionInterface $e) { + if (!$this->options['ignore_error']) { + throw new \RuntimeException("Error sending messages to Elasticsearch", 0, $e); + } + } + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ElasticsearchHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ElasticsearchHandler.php new file mode 100644 index 000000000000..74cc7b6e6572 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ElasticsearchHandler.php @@ -0,0 +1,230 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Elastic\Elasticsearch\Response\Elasticsearch; +use Throwable; +use RuntimeException; +use Monolog\Level; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\ElasticsearchFormatter; +use InvalidArgumentException; +use Elasticsearch\Common\Exceptions\RuntimeException as ElasticsearchRuntimeException; +use Elasticsearch\Client; +use Monolog\LogRecord; +use Elastic\Elasticsearch\Exception\InvalidArgumentException as ElasticInvalidArgumentException; +use Elastic\Elasticsearch\Client as Client8; + +/** + * Elasticsearch handler + * + * @link https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/index.html + * + * Simple usage example: + * + * $client = \Elasticsearch\ClientBuilder::create() + * ->setHosts($hosts) + * ->build(); + * + * $options = array( + * 'index' => 'elastic_index_name', + * 'type' => 'elastic_doc_type', + * ); + * $handler = new ElasticsearchHandler($client, $options); + * $log = new Logger('application'); + * $log->pushHandler($handler); + * + * @author Avtandil Kikabidze + * @phpstan-type Options array{ + * index: string, + * type: string, + * ignore_error: bool, + * op_type: 'index'|'create' + * } + * @phpstan-type InputOptions array{ + * index?: string, + * type?: string, + * ignore_error?: bool, + * op_type?: 'index'|'create' + * } + */ +class ElasticsearchHandler extends AbstractProcessingHandler +{ + protected Client|Client8 $client; + + /** + * @var mixed[] Handler config options + * @phpstan-var Options + */ + protected array $options; + + /** + * @var bool + */ + private $needsType; + + /** + * @param Client|Client8 $client Elasticsearch Client object + * @param mixed[] $options Handler configuration + * + * @phpstan-param InputOptions $options + */ + public function __construct(Client|Client8 $client, array $options = [], int|string|Level $level = Level::Debug, bool $bubble = true) + { + parent::__construct($level, $bubble); + $this->client = $client; + $this->options = array_merge( + [ + 'index' => 'monolog', // Elastic index name + 'type' => '_doc', // Elastic document type + 'ignore_error' => false, // Suppress Elasticsearch exceptions + 'op_type' => 'index', // Elastic op_type (index or create) (https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#docs-index-api-op_type) + ], + $options + ); + + if ($client instanceof Client8 || $client::VERSION[0] === '7') { + $this->needsType = false; + // force the type to _doc for ES8/ES7 + $this->options['type'] = '_doc'; + } else { + $this->needsType = true; + } + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $this->bulkSend([$record->formatted]); + } + + /** + * @inheritDoc + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($formatter instanceof ElasticsearchFormatter) { + return parent::setFormatter($formatter); + } + + throw new InvalidArgumentException('ElasticsearchHandler is only compatible with ElasticsearchFormatter'); + } + + /** + * Getter options + * + * @return mixed[] + * + * @phpstan-return Options + */ + public function getOptions(): array + { + return $this->options; + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new ElasticsearchFormatter($this->options['index'], $this->options['type']); + } + + /** + * @inheritDoc + */ + public function handleBatch(array $records): void + { + $documents = $this->getFormatter()->formatBatch($records); + $this->bulkSend($documents); + } + + /** + * Use Elasticsearch bulk API to send list of documents + * + * @param array> $records Records + _index/_type keys + * @throws \RuntimeException + */ + protected function bulkSend(array $records): void + { + try { + $params = [ + 'body' => [], + ]; + + foreach ($records as $record) { + $params['body'][] = [ + $this->options['op_type'] => $this->needsType ? [ + '_index' => $record['_index'], + '_type' => $record['_type'], + ] : [ + '_index' => $record['_index'], + ], + ]; + unset($record['_index'], $record['_type']); + + $params['body'][] = $record; + } + + /** @var Elasticsearch */ + $responses = $this->client->bulk($params); + + if ($responses['errors'] === true) { + throw $this->createExceptionFromResponses($responses); + } + } catch (Throwable $e) { + if (! $this->options['ignore_error']) { + throw new RuntimeException('Error sending messages to Elasticsearch', 0, $e); + } + } + } + + /** + * Creates elasticsearch exception from responses array + * + * Only the first error is converted into an exception. + * + * @param mixed[]|Elasticsearch $responses returned by $this->client->bulk() + */ + protected function createExceptionFromResponses($responses): Throwable + { + foreach ($responses['items'] ?? [] as $item) { + if (isset($item['index']['error'])) { + return $this->createExceptionFromError($item['index']['error']); + } + } + + if (class_exists(ElasticInvalidArgumentException::class)) { + return new ElasticInvalidArgumentException('Elasticsearch failed to index one or more records.'); + } + + return new ElasticsearchRuntimeException('Elasticsearch failed to index one or more records.'); + } + + /** + * Creates elasticsearch exception from error array + * + * @param mixed[] $error + */ + protected function createExceptionFromError(array $error): Throwable + { + $previous = isset($error['caused_by']) ? $this->createExceptionFromError($error['caused_by']) : null; + + if (class_exists(ElasticInvalidArgumentException::class)) { + return new ElasticInvalidArgumentException($error['type'] . ': ' . $error['reason'], 0, $previous); + } + + return new ElasticsearchRuntimeException($error['type'] . ': ' . $error['reason'], 0, $previous); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php new file mode 100644 index 000000000000..571c439e1035 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Level; +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * Stores to PHP error_log() handler. + * + * @author Elan Ruusamäe + */ +class ErrorLogHandler extends AbstractProcessingHandler +{ + public const OPERATING_SYSTEM = 0; + public const SAPI = 4; + + protected int $messageType; + protected bool $expandNewlines; + + /** + * @param int $messageType Says where the error should go. + * @param bool $expandNewlines If set to true, newlines in the message will be expanded to be take multiple log entries + * + * @throws \InvalidArgumentException If an unsupported message type is set + */ + public function __construct(int $messageType = self::OPERATING_SYSTEM, int|string|Level $level = Level::Debug, bool $bubble = true, bool $expandNewlines = false) + { + parent::__construct($level, $bubble); + + if (false === in_array($messageType, self::getAvailableTypes(), true)) { + $message = sprintf('The given message type "%s" is not supported', print_r($messageType, true)); + + throw new \InvalidArgumentException($message); + } + + $this->messageType = $messageType; + $this->expandNewlines = $expandNewlines; + } + + /** + * @return int[] With all available types + */ + public static function getAvailableTypes(): array + { + return [ + self::OPERATING_SYSTEM, + self::SAPI, + ]; + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter('[%datetime%] %channel%.%level_name%: %message% %context% %extra%'); + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + if (!$this->expandNewlines) { + error_log((string) $record->formatted, $this->messageType); + + return; + } + + $lines = preg_split('{[\r\n]+}', (string) $record->formatted); + if ($lines === false) { + $pcreErrorCode = preg_last_error(); + + throw new \RuntimeException('Failed to preg_split formatted string: ' . $pcreErrorCode . ' / '. Utils::pcreLastErrorMessage($pcreErrorCode)); + } + foreach ($lines as $line) { + error_log($line, $this->messageType); + } + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php new file mode 100644 index 000000000000..1776eb517fc4 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Throwable; +use Monolog\LogRecord; + +/** + * Forwards records to at most one handler + * + * If a handler fails, the exception is suppressed and the record is forwarded to the next handler. + * + * As soon as one handler handles a record successfully, the handling stops there. + */ +class FallbackGroupHandler extends GroupHandler +{ + /** + * @inheritDoc + */ + public function handle(LogRecord $record): bool + { + if (\count($this->processors) > 0) { + $record = $this->processRecord($record); + } + foreach ($this->handlers as $handler) { + try { + $handler->handle($record); + break; + } catch (Throwable $e) { + // What throwable? + } + } + + return false === $this->bubble; + } + + /** + * @inheritDoc + */ + public function handleBatch(array $records): void + { + if (\count($this->processors) > 0) { + $processed = []; + foreach ($records as $record) { + $processed[] = $this->processRecord($record); + } + $records = $processed; + } + + foreach ($this->handlers as $handler) { + try { + $handler->handleBatch($records); + break; + } catch (Throwable $e) { + // What throwable? + } + } + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FilterHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FilterHandler.php new file mode 100644 index 000000000000..00381ab4d136 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FilterHandler.php @@ -0,0 +1,201 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Closure; +use Monolog\Level; +use Monolog\Logger; +use Monolog\ResettableInterface; +use Monolog\Formatter\FormatterInterface; +use Psr\Log\LogLevel; +use Monolog\LogRecord; + +/** + * Simple handler wrapper that filters records based on a list of levels + * + * It can be configured with an exact list of levels to allow, or a min/max level. + * + * @author Hennadiy Verkh + * @author Jordi Boggiano + */ +class FilterHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface, FormattableHandlerInterface +{ + use ProcessableHandlerTrait; + + /** + * Handler or factory Closure($record, $this) + * + * @phpstan-var (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface + */ + protected Closure|HandlerInterface $handler; + + /** + * Minimum level for logs that are passed to handler + * + * @var bool[] Map of Level value => true + * @phpstan-var array, true> + */ + protected array $acceptedLevels; + + /** + * Whether the messages that are handled can bubble up the stack or not + */ + protected bool $bubble; + + /** + * @phpstan-param (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface $handler + * + * @param Closure|HandlerInterface $handler Handler or factory Closure($record|null, $filterHandler). + * @param int|string|Level|array $minLevelOrList A list of levels to accept or a minimum level if maxLevel is provided + * @param int|string|Level|LogLevel::* $maxLevel Maximum level to accept, only used if $minLevelOrList is not an array + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * + * @phpstan-param value-of|value-of|Level|LogLevel::*|array|value-of|Level|LogLevel::*> $minLevelOrList + * @phpstan-param value-of|value-of|Level|LogLevel::* $maxLevel + */ + public function __construct(Closure|HandlerInterface $handler, int|string|Level|array $minLevelOrList = Level::Debug, int|string|Level $maxLevel = Level::Emergency, bool $bubble = true) + { + $this->handler = $handler; + $this->bubble = $bubble; + $this->setAcceptedLevels($minLevelOrList, $maxLevel); + } + + /** + * @phpstan-return list List of levels + */ + public function getAcceptedLevels(): array + { + return array_map(fn (int $level) => Level::from($level), array_keys($this->acceptedLevels)); + } + + /** + * @param int|string|Level|LogLevel::*|array $minLevelOrList A list of levels to accept or a minimum level or level name if maxLevel is provided + * @param int|string|Level|LogLevel::* $maxLevel Maximum level or level name to accept, only used if $minLevelOrList is not an array + * + * @phpstan-param value-of|value-of|Level|LogLevel::*|array|value-of|Level|LogLevel::*> $minLevelOrList + * @phpstan-param value-of|value-of|Level|LogLevel::* $maxLevel + */ + public function setAcceptedLevels(int|string|Level|array $minLevelOrList = Level::Debug, int|string|Level $maxLevel = Level::Emergency): self + { + if (is_array($minLevelOrList)) { + $acceptedLevels = array_map(Logger::toMonologLevel(...), $minLevelOrList); + } else { + $minLevelOrList = Logger::toMonologLevel($minLevelOrList); + $maxLevel = Logger::toMonologLevel($maxLevel); + $acceptedLevels = array_values(array_filter(Level::cases(), fn (Level $level) => $level->value >= $minLevelOrList->value && $level->value <= $maxLevel->value)); + } + $this->acceptedLevels = []; + foreach ($acceptedLevels as $level) { + $this->acceptedLevels[$level->value] = true; + } + + return $this; + } + + /** + * @inheritDoc + */ + public function isHandling(LogRecord $record): bool + { + return isset($this->acceptedLevels[$record->level->value]); + } + + /** + * @inheritDoc + */ + public function handle(LogRecord $record): bool + { + if (!$this->isHandling($record)) { + return false; + } + + if (\count($this->processors) > 0) { + $record = $this->processRecord($record); + } + + $this->getHandler($record)->handle($record); + + return false === $this->bubble; + } + + /** + * @inheritDoc + */ + public function handleBatch(array $records): void + { + $filtered = []; + foreach ($records as $record) { + if ($this->isHandling($record)) { + $filtered[] = $record; + } + } + + if (count($filtered) > 0) { + $this->getHandler($filtered[count($filtered) - 1])->handleBatch($filtered); + } + } + + /** + * Return the nested handler + * + * If the handler was provided as a factory, this will trigger the handler's instantiation. + */ + public function getHandler(LogRecord $record = null): HandlerInterface + { + if (!$this->handler instanceof HandlerInterface) { + $handler = ($this->handler)($record, $this); + if (!$handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory Closure should return a HandlerInterface"); + } + $this->handler = $handler; + } + + return $this->handler; + } + + /** + * @inheritDoc + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); + } + + /** + * @inheritDoc + */ + public function getFormatter(): FormatterInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + return $handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); + } + + public function reset(): void + { + $this->resetProcessors(); + + if ($this->getHandler() instanceof ResettableInterface) { + $this->getHandler()->reset(); + } + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php new file mode 100644 index 000000000000..e8a1b0b0dbb6 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +use Monolog\LogRecord; + +/** + * Interface for activation strategies for the FingersCrossedHandler. + * + * @author Johannes M. Schmitt + */ +interface ActivationStrategyInterface +{ + /** + * Returns whether the given record activates the handler. + */ + public function isHandlerActivated(LogRecord $record): bool; +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php new file mode 100644 index 000000000000..383e19af962d --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +use Monolog\Level; +use Monolog\Logger; +use Psr\Log\LogLevel; +use Monolog\LogRecord; + +/** + * Channel and Error level based monolog activation strategy. Allows to trigger activation + * based on level per channel. e.g. trigger activation on level 'ERROR' by default, except + * for records of the 'sql' channel; those should trigger activation on level 'WARN'. + * + * Example: + * + * + * $activationStrategy = new ChannelLevelActivationStrategy( + * Level::Critical, + * array( + * 'request' => Level::Alert, + * 'sensitive' => Level::Error, + * ) + * ); + * $handler = new FingersCrossedHandler(new StreamHandler('php://stderr'), $activationStrategy); + * + * + * @author Mike Meessen + */ +class ChannelLevelActivationStrategy implements ActivationStrategyInterface +{ + private Level $defaultActionLevel; + + /** + * @var array + */ + private array $channelToActionLevel; + + /** + * @param int|string|Level|LogLevel::* $defaultActionLevel The default action level to be used if the record's category doesn't match any + * @param array $channelToActionLevel An array that maps channel names to action levels. + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $defaultActionLevel + * @phpstan-param array|value-of|Level|LogLevel::*> $channelToActionLevel + */ + public function __construct(int|string|Level $defaultActionLevel, array $channelToActionLevel = []) + { + $this->defaultActionLevel = Logger::toMonologLevel($defaultActionLevel); + $this->channelToActionLevel = array_map(Logger::toMonologLevel(...), $channelToActionLevel); + } + + public function isHandlerActivated(LogRecord $record): bool + { + if (isset($this->channelToActionLevel[$record->channel])) { + return $record->level->value >= $this->channelToActionLevel[$record->channel]->value; + } + + return $record->level->value >= $this->defaultActionLevel->value; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php new file mode 100644 index 000000000000..c3ca2967ab6b --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +use Monolog\Level; +use Monolog\LogRecord; +use Monolog\Logger; +use Psr\Log\LogLevel; + +/** + * Error level based activation strategy. + * + * @author Johannes M. Schmitt + */ +class ErrorLevelActivationStrategy implements ActivationStrategyInterface +{ + private Level $actionLevel; + + /** + * @param int|string|Level $actionLevel Level or name or value + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $actionLevel + */ + public function __construct(int|string|Level $actionLevel) + { + $this->actionLevel = Logger::toMonologLevel($actionLevel); + } + + public function isHandlerActivated(LogRecord $record): bool + { + return $record->level->value >= $this->actionLevel->value; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php new file mode 100644 index 000000000000..1c3df386d52c --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php @@ -0,0 +1,242 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Closure; +use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; +use Monolog\Handler\FingersCrossed\ActivationStrategyInterface; +use Monolog\Level; +use Monolog\Logger; +use Monolog\ResettableInterface; +use Monolog\Formatter\FormatterInterface; +use Psr\Log\LogLevel; +use Monolog\LogRecord; + +/** + * Buffers all records until a certain level is reached + * + * The advantage of this approach is that you don't get any clutter in your log files. + * Only requests which actually trigger an error (or whatever your actionLevel is) will be + * in the logs, but they will contain all records, not only those above the level threshold. + * + * You can then have a passthruLevel as well which means that at the end of the request, + * even if it did not get activated, it will still send through log records of e.g. at least a + * warning level. + * + * You can find the various activation strategies in the + * Monolog\Handler\FingersCrossed\ namespace. + * + * @author Jordi Boggiano + */ +class FingersCrossedHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface, FormattableHandlerInterface +{ + use ProcessableHandlerTrait; + + /** + * Handler or factory Closure($record, $this) + * + * @phpstan-var (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface + */ + protected Closure|HandlerInterface $handler; + + protected ActivationStrategyInterface $activationStrategy; + + protected bool $buffering = true; + + protected int $bufferSize; + + /** @var LogRecord[] */ + protected array $buffer = []; + + protected bool $stopBuffering; + + protected Level|null $passthruLevel = null; + + protected bool $bubble; + + /** + * @phpstan-param (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface $handler + * + * @param Closure|HandlerInterface $handler Handler or factory Closure($record|null, $fingersCrossedHandler). + * @param int|string|Level|LogLevel::* $activationStrategy Strategy which determines when this handler takes action, or a level name/value at which the handler is activated + * @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param bool $stopBuffering Whether the handler should stop buffering after being triggered (default true) + * @param int|string|Level|LogLevel::*|null $passthruLevel Minimum level to always flush to handler on close, even if strategy not triggered + * + * @phpstan-param value-of|value-of|Level|LogLevel::*|ActivationStrategyInterface $activationStrategy + * @phpstan-param value-of|value-of|Level|LogLevel::* $passthruLevel + */ + public function __construct(Closure|HandlerInterface $handler, int|string|Level|ActivationStrategyInterface $activationStrategy = null, int $bufferSize = 0, bool $bubble = true, bool $stopBuffering = true, int|string|Level|null $passthruLevel = null) + { + if (null === $activationStrategy) { + $activationStrategy = new ErrorLevelActivationStrategy(Level::Warning); + } + + // convert simple int activationStrategy to an object + if (!$activationStrategy instanceof ActivationStrategyInterface) { + $activationStrategy = new ErrorLevelActivationStrategy($activationStrategy); + } + + $this->handler = $handler; + $this->activationStrategy = $activationStrategy; + $this->bufferSize = $bufferSize; + $this->bubble = $bubble; + $this->stopBuffering = $stopBuffering; + + if ($passthruLevel !== null) { + $this->passthruLevel = Logger::toMonologLevel($passthruLevel); + } + } + + /** + * @inheritDoc + */ + public function isHandling(LogRecord $record): bool + { + return true; + } + + /** + * Manually activate this logger regardless of the activation strategy + */ + public function activate(): void + { + if ($this->stopBuffering) { + $this->buffering = false; + } + + $this->getHandler(end($this->buffer) ?: null)->handleBatch($this->buffer); + $this->buffer = []; + } + + /** + * @inheritDoc + */ + public function handle(LogRecord $record): bool + { + if (\count($this->processors) > 0) { + $record = $this->processRecord($record); + } + + if ($this->buffering) { + $this->buffer[] = $record; + if ($this->bufferSize > 0 && count($this->buffer) > $this->bufferSize) { + array_shift($this->buffer); + } + if ($this->activationStrategy->isHandlerActivated($record)) { + $this->activate(); + } + } else { + $this->getHandler($record)->handle($record); + } + + return false === $this->bubble; + } + + /** + * @inheritDoc + */ + public function close(): void + { + $this->flushBuffer(); + + $this->getHandler()->close(); + } + + public function reset(): void + { + $this->flushBuffer(); + + $this->resetProcessors(); + + if ($this->getHandler() instanceof ResettableInterface) { + $this->getHandler()->reset(); + } + } + + /** + * Clears the buffer without flushing any messages down to the wrapped handler. + * + * It also resets the handler to its initial buffering state. + */ + public function clear(): void + { + $this->buffer = []; + $this->reset(); + } + + /** + * Resets the state of the handler. Stops forwarding records to the wrapped handler. + */ + private function flushBuffer(): void + { + if (null !== $this->passthruLevel) { + $passthruLevel = $this->passthruLevel; + $this->buffer = array_filter($this->buffer, static function ($record) use ($passthruLevel) { + return $passthruLevel->includes($record->level); + }); + if (count($this->buffer) > 0) { + $this->getHandler(end($this->buffer))->handleBatch($this->buffer); + } + } + + $this->buffer = []; + $this->buffering = true; + } + + /** + * Return the nested handler + * + * If the handler was provided as a factory, this will trigger the handler's instantiation. + */ + public function getHandler(LogRecord $record = null): HandlerInterface + { + if (!$this->handler instanceof HandlerInterface) { + $handler = ($this->handler)($record, $this); + if (!$handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory Closure should return a HandlerInterface"); + } + $this->handler = $handler; + } + + return $this->handler; + } + + /** + * @inheritDoc + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); + } + + /** + * @inheritDoc + */ + public function getFormatter(): FormatterInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + return $handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php new file mode 100644 index 000000000000..6b9e5103a9e7 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php @@ -0,0 +1,174 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\WildfireFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\LogRecord; + +/** + * Simple FirePHP Handler (http://www.firephp.org/), which uses the Wildfire protocol. + * + * @author Eric Clemmons (@ericclemmons) + */ +class FirePHPHandler extends AbstractProcessingHandler +{ + use WebRequestRecognizerTrait; + + /** + * WildFire JSON header message format + */ + protected const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'; + + /** + * FirePHP structure for parsing messages & their presentation + */ + protected const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'; + + /** + * Must reference a "known" plugin, otherwise headers won't display in FirePHP + */ + protected const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3'; + + /** + * Header prefix for Wildfire to recognize & parse headers + */ + protected const HEADER_PREFIX = 'X-Wf'; + + /** + * Whether or not Wildfire vendor-specific headers have been generated & sent yet + */ + protected static bool $initialized = false; + + /** + * Shared static message index between potentially multiple handlers + */ + protected static int $messageIndex = 1; + + protected static bool $sendHeaders = true; + + /** + * Base header creation function used by init headers & record headers + * + * @param array $meta Wildfire Plugin, Protocol & Structure Indexes + * @param string $message Log message + * + * @return array Complete header string ready for the client as key and message as value + * + * @phpstan-return non-empty-array + */ + protected function createHeader(array $meta, string $message): array + { + $header = sprintf('%s-%s', static::HEADER_PREFIX, join('-', $meta)); + + return [$header => $message]; + } + + /** + * Creates message header from record + * + * @return array + * + * @phpstan-return non-empty-array + * + * @see createHeader() + */ + protected function createRecordHeader(LogRecord $record): array + { + // Wildfire is extensible to support multiple protocols & plugins in a single request, + // but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake. + return $this->createHeader( + [1, 1, 1, self::$messageIndex++], + $record->formatted + ); + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new WildfireFormatter(); + } + + /** + * Wildfire initialization headers to enable message parsing + * + * @see createHeader() + * @see sendHeader() + * + * @return array + */ + protected function getInitHeaders(): array + { + // Initial payload consists of required headers for Wildfire + return array_merge( + $this->createHeader(['Protocol', 1], static::PROTOCOL_URI), + $this->createHeader([1, 'Structure', 1], static::STRUCTURE_URI), + $this->createHeader([1, 'Plugin', 1], static::PLUGIN_URI) + ); + } + + /** + * Send header string to the client + */ + protected function sendHeader(string $header, string $content): void + { + if (!headers_sent() && self::$sendHeaders) { + header(sprintf('%s: %s', $header, $content)); + } + } + + /** + * Creates & sends header for a record, ensuring init headers have been sent prior + * + * @see sendHeader() + * @see sendInitHeaders() + */ + protected function write(LogRecord $record): void + { + if (!self::$sendHeaders || !$this->isWebRequest()) { + return; + } + + // WildFire-specific headers must be sent prior to any messages + if (!self::$initialized) { + self::$initialized = true; + + self::$sendHeaders = $this->headersAccepted(); + if (!self::$sendHeaders) { + return; + } + + foreach ($this->getInitHeaders() as $header => $content) { + $this->sendHeader($header, $content); + } + } + + $header = $this->createRecordHeader($record); + if (trim(current($header)) !== '') { + $this->sendHeader(key($header), current($header)); + } + } + + /** + * Verifies if the headers are accepted by the current user agent + */ + protected function headersAccepted(): bool + { + if (isset($_SERVER['HTTP_USER_AGENT']) && 1 === preg_match('{\bFirePHP/\d+\.\d+\b}', $_SERVER['HTTP_USER_AGENT'])) { + return true; + } + + return isset($_SERVER['HTTP_X_FIREPHP_VERSION']); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php new file mode 100644 index 000000000000..220648223f6c --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; +use Monolog\Level; +use Monolog\LogRecord; + +/** + * Sends logs to Fleep.io using Webhook integrations + * + * You'll need a Fleep.io account to use this handler. + * + * @see https://fleep.io/integrations/webhooks/ Fleep Webhooks Documentation + * @author Ando Roots + */ +class FleepHookHandler extends SocketHandler +{ + protected const FLEEP_HOST = 'fleep.io'; + + protected const FLEEP_HOOK_URI = '/hook/'; + + /** + * @var string Webhook token (specifies the conversation where logs are sent) + */ + protected string $token; + + /** + * Construct a new Fleep.io Handler. + * + * For instructions on how to create a new web hook in your conversations + * see https://fleep.io/integrations/webhooks/ + * + * @param string $token Webhook token + * @throws MissingExtensionException if OpenSSL is missing + */ + public function __construct( + string $token, + $level = Level::Debug, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + if (!extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FleepHookHandler'); + } + + $this->token = $token; + + $connectionString = 'ssl://' . static::FLEEP_HOST . ':443'; + parent::__construct( + $connectionString, + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); + } + + /** + * Returns the default formatter to use with this handler + * + * Overloaded to remove empty context and extra arrays from the end of the log message. + * + * @return LineFormatter + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter(null, null, true, true); + } + + /** + * Handles a log record + */ + public function write(LogRecord $record): void + { + parent::write($record); + $this->closeSocket(); + } + + /** + * @inheritDoc + */ + protected function generateDataStream(LogRecord $record): string + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the header of the API Call + */ + private function buildHeader(string $content): string + { + $header = "POST " . static::FLEEP_HOOK_URI . $this->token . " HTTP/1.1\r\n"; + $header .= "Host: " . static::FLEEP_HOST . "\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + /** + * Builds the body of API call + */ + private function buildContent(LogRecord $record): string + { + $dataArray = [ + 'message' => $record->formatted, + ]; + + return http_build_query($dataArray); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php new file mode 100644 index 000000000000..d24bec40b7e6 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Utils; +use Monolog\Formatter\FlowdockFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\LogRecord; + +/** + * Sends notifications through the Flowdock push API + * + * This must be configured with a FlowdockFormatter instance via setFormatter() + * + * Notes: + * API token - Flowdock API token + * + * @author Dominik Liebler + * @see https://www.flowdock.com/api/push + * @deprecated Since 2.9.0 and 3.3.0, Flowdock was shutdown we will thus drop this handler in Monolog 4 + */ +class FlowdockHandler extends SocketHandler +{ + protected string $apiToken; + + /** + * @throws MissingExtensionException if OpenSSL is missing + */ + public function __construct( + string $apiToken, + $level = Level::Debug, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + if (!extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FlowdockHandler'); + } + + parent::__construct( + 'ssl://api.flowdock.com:443', + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); + $this->apiToken = $apiToken; + } + + /** + * @inheritDoc + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if (!$formatter instanceof FlowdockFormatter) { + throw new \InvalidArgumentException('The FlowdockHandler requires an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); + } + + return parent::setFormatter($formatter); + } + + /** + * Gets the default formatter. + */ + protected function getDefaultFormatter(): FormatterInterface + { + throw new \InvalidArgumentException('The FlowdockHandler must be configured (via setFormatter) with an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + parent::write($record); + + $this->closeSocket(); + } + + /** + * @inheritDoc + */ + protected function generateDataStream(LogRecord $record): string + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the body of API call + */ + private function buildContent(LogRecord $record): string + { + return Utils::jsonEncode($record->formatted); + } + + /** + * Builds the header of the API Call + */ + private function buildHeader(string $content): string + { + $header = "POST /v1/messages/team_inbox/" . $this->apiToken . " HTTP/1.1\r\n"; + $header .= "Host: api.flowdock.com\r\n"; + $header .= "Content-Type: application/json\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php new file mode 100644 index 000000000000..72da59e1c587 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * Interface to describe loggers that have a formatter + * + * @author Jordi Boggiano + */ +interface FormattableHandlerInterface +{ + /** + * Sets the formatter. + * + * @return HandlerInterface self + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface; + + /** + * Gets the formatter. + */ + public function getFormatter(): FormatterInterface; +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php new file mode 100644 index 000000000000..c044e0786ca6 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; + +/** + * Helper trait for implementing FormattableInterface + * + * @author Jordi Boggiano + */ +trait FormattableHandlerTrait +{ + protected FormatterInterface|null $formatter = null; + + /** + * @inheritDoc + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $this->formatter = $formatter; + + return $this; + } + + /** + * @inheritDoc + */ + public function getFormatter(): FormatterInterface + { + if (null === $this->formatter) { + $this->formatter = $this->getDefaultFormatter(); + } + + return $this->formatter; + } + + /** + * Gets the default formatter. + * + * Overwrite this if the LineFormatter is not a good default for your handler. + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter(); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/GelfHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/GelfHandler.php new file mode 100644 index 000000000000..ba5bb975d85b --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/GelfHandler.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Gelf\PublisherInterface; +use Monolog\Level; +use Monolog\Formatter\GelfMessageFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\LogRecord; + +/** + * Handler to send messages to a Graylog2 (http://www.graylog2.org) server + * + * @author Matt Lehner + * @author Benjamin Zikarsky + */ +class GelfHandler extends AbstractProcessingHandler +{ + /** + * @var PublisherInterface the publisher object that sends the message to the server + */ + protected PublisherInterface $publisher; + + /** + * @param PublisherInterface $publisher a gelf publisher object + */ + public function __construct(PublisherInterface $publisher, int|string|Level $level = Level::Debug, bool $bubble = true) + { + parent::__construct($level, $bubble); + + $this->publisher = $publisher; + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $this->publisher->publish($record->formatted); + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new GelfMessageFormatter(); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/GroupHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/GroupHandler.php new file mode 100644 index 000000000000..854b31abf62e --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/GroupHandler.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\ResettableInterface; +use Monolog\LogRecord; + +/** + * Forwards records to multiple handlers + * + * @author Lenar Lõhmus + */ +class GroupHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface +{ + use ProcessableHandlerTrait; + + /** @var HandlerInterface[] */ + protected array $handlers; + protected bool $bubble; + + /** + * @param HandlerInterface[] $handlers Array of Handlers. + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * + * @throws \InvalidArgumentException if an unsupported handler is set + */ + public function __construct(array $handlers, bool $bubble = true) + { + foreach ($handlers as $handler) { + if (!$handler instanceof HandlerInterface) { + throw new \InvalidArgumentException('The first argument of the GroupHandler must be an array of HandlerInterface instances.'); + } + } + + $this->handlers = $handlers; + $this->bubble = $bubble; + } + + /** + * @inheritDoc + */ + public function isHandling(LogRecord $record): bool + { + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return true; + } + } + + return false; + } + + /** + * @inheritDoc + */ + public function handle(LogRecord $record): bool + { + if (\count($this->processors) > 0) { + $record = $this->processRecord($record); + } + + foreach ($this->handlers as $handler) { + $handler->handle($record); + } + + return false === $this->bubble; + } + + /** + * @inheritDoc + */ + public function handleBatch(array $records): void + { + if (\count($this->processors) > 0) { + $processed = []; + foreach ($records as $record) { + $processed[] = $this->processRecord($record); + } + $records = $processed; + } + + foreach ($this->handlers as $handler) { + $handler->handleBatch($records); + } + } + + public function reset(): void + { + $this->resetProcessors(); + + foreach ($this->handlers as $handler) { + if ($handler instanceof ResettableInterface) { + $handler->reset(); + } + } + } + + public function close(): void + { + parent::close(); + + foreach ($this->handlers as $handler) { + $handler->close(); + } + } + + /** + * @inheritDoc + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + foreach ($this->handlers as $handler) { + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); + } + } + + return $this; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/Handler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/Handler.php new file mode 100644 index 000000000000..e89f969b8ffd --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/Handler.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Base Handler class providing basic close() support as well as handleBatch + * + * @author Jordi Boggiano + */ +abstract class Handler implements HandlerInterface +{ + /** + * @inheritDoc + */ + public function handleBatch(array $records): void + { + foreach ($records as $record) { + $this->handle($record); + } + } + + /** + * @inheritDoc + */ + public function close(): void + { + } + + public function __destruct() + { + try { + $this->close(); + } catch (\Throwable $e) { + // do nothing + } + } + + public function __sleep() + { + $this->close(); + + $reflClass = new \ReflectionClass($this); + + $keys = []; + foreach ($reflClass->getProperties() as $reflProp) { + if (!$reflProp->isStatic()) { + $keys[] = $reflProp->getName(); + } + } + + return $keys; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/HandlerInterface.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/HandlerInterface.php new file mode 100644 index 000000000000..83905c323d15 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/HandlerInterface.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\LogRecord; + +/** + * Interface that all Monolog Handlers must implement + * + * @author Jordi Boggiano + */ +interface HandlerInterface +{ + /** + * Checks whether the given record will be handled by this handler. + * + * This is mostly done for performance reasons, to avoid calling processors for nothing. + * + * Handlers should still check the record levels within handle(), returning false in isHandling() + * is no guarantee that handle() will not be called, and isHandling() might not be called + * for a given record. + * + * @param LogRecord $record Partial log record having only a level initialized + */ + public function isHandling(LogRecord $record): bool; + + /** + * Handles a record. + * + * All records may be passed to this method, and the handler should discard + * those that it does not want to handle. + * + * The return value of this function controls the bubbling process of the handler stack. + * Unless the bubbling is interrupted (by returning true), the Logger class will keep on + * calling further handlers in the stack with a given log record. + * + * @param LogRecord $record The record to handle + * @return bool true means that this handler handled the record, and that bubbling is not permitted. + * false means the record was either not processed or that this handler allows bubbling. + */ + public function handle(LogRecord $record): bool; + + /** + * Handles a set of records at once. + * + * @param array $records The records to handle + */ + public function handleBatch(array $records): void; + + /** + * Closes the handler. + * + * Ends a log cycle and frees all resources used by the handler. + * + * Closing a Handler means flushing all buffers and freeing any open resources/handles. + * + * Implementations have to be idempotent (i.e. it should be possible to call close several times without breakage) + * and ideally handlers should be able to reopen themselves on handle() after they have been closed. + * + * This is useful at the end of a request and will be called automatically when the object + * is destroyed if you extend Monolog\Handler\Handler. + * + * If you are thinking of calling this method yourself, most likely you should be + * calling ResettableInterface::reset instead. Have a look. + */ + public function close(): void; +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php new file mode 100644 index 000000000000..541ec2541a23 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\ResettableInterface; +use Monolog\Formatter\FormatterInterface; +use Monolog\LogRecord; + +/** + * This simple wrapper class can be used to extend handlers functionality. + * + * Example: A custom filtering that can be applied to any handler. + * + * Inherit from this class and override handle() like this: + * + * public function handle(LogRecord $record) + * { + * if ($record meets certain conditions) { + * return false; + * } + * return $this->handler->handle($record); + * } + * + * @author Alexey Karapetov + */ +class HandlerWrapper implements HandlerInterface, ProcessableHandlerInterface, FormattableHandlerInterface, ResettableInterface +{ + protected HandlerInterface $handler; + + public function __construct(HandlerInterface $handler) + { + $this->handler = $handler; + } + + /** + * @inheritDoc + */ + public function isHandling(LogRecord $record): bool + { + return $this->handler->isHandling($record); + } + + /** + * @inheritDoc + */ + public function handle(LogRecord $record): bool + { + return $this->handler->handle($record); + } + + /** + * @inheritDoc + */ + public function handleBatch(array $records): void + { + $this->handler->handleBatch($records); + } + + /** + * @inheritDoc + */ + public function close(): void + { + $this->handler->close(); + } + + /** + * @inheritDoc + */ + public function pushProcessor(callable $callback): HandlerInterface + { + if ($this->handler instanceof ProcessableHandlerInterface) { + $this->handler->pushProcessor($callback); + + return $this; + } + + throw new \LogicException('The wrapped handler does not implement ' . ProcessableHandlerInterface::class); + } + + /** + * @inheritDoc + */ + public function popProcessor(): callable + { + if ($this->handler instanceof ProcessableHandlerInterface) { + return $this->handler->popProcessor(); + } + + throw new \LogicException('The wrapped handler does not implement ' . ProcessableHandlerInterface::class); + } + + /** + * @inheritDoc + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + $this->handler->setFormatter($formatter); + + return $this; + } + + throw new \LogicException('The wrapped handler does not implement ' . FormattableHandlerInterface::class); + } + + /** + * @inheritDoc + */ + public function getFormatter(): FormatterInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + return $this->handler->getFormatter(); + } + + throw new \LogicException('The wrapped handler does not implement ' . FormattableHandlerInterface::class); + } + + public function reset(): void + { + if ($this->handler instanceof ResettableInterface) { + $this->handler->reset(); + } + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php new file mode 100644 index 000000000000..418f2ba03d1c --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * IFTTTHandler uses cURL to trigger IFTTT Maker actions + * + * Register a secret key and trigger/event name at https://ifttt.com/maker + * + * value1 will be the channel from monolog's Logger constructor, + * value2 will be the level name (ERROR, WARNING, ..) + * value3 will be the log record's message + * + * @author Nehal Patel + */ +class IFTTTHandler extends AbstractProcessingHandler +{ + private string $eventName; + private string $secretKey; + + /** + * @param string $eventName The name of the IFTTT Maker event that should be triggered + * @param string $secretKey A valid IFTTT secret key + * + * @throws MissingExtensionException If the curl extension is missing + */ + public function __construct(string $eventName, string $secretKey, int|string|Level $level = Level::Error, bool $bubble = true) + { + if (!extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the IFTTTHandler'); + } + + $this->eventName = $eventName; + $this->secretKey = $secretKey; + + parent::__construct($level, $bubble); + } + + /** + * @inheritDoc + */ + public function write(LogRecord $record): void + { + $postData = [ + "value1" => $record->channel, + "value2" => $record["level_name"], + "value3" => $record->message, + ]; + $postString = Utils::jsonEncode($postData); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, "https://maker.ifttt.com/trigger/" . $this->eventName . "/with/key/" . $this->secretKey); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $postString); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + "Content-Type: application/json", + ]); + + Curl\Util::execute($ch); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php new file mode 100644 index 000000000000..abb2f88f7cf6 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\LogRecord; + +/** + * Inspired on LogEntriesHandler. + * + * @author Robert Kaufmann III + * @author Gabriel Machado + */ +class InsightOpsHandler extends SocketHandler +{ + protected string $logToken; + + /** + * @param string $token Log token supplied by InsightOps + * @param string $region Region where InsightOps account is hosted. Could be 'us' or 'eu'. + * @param bool $useSSL Whether or not SSL encryption should be used + * + * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing + */ + public function __construct( + string $token, + string $region = 'us', + bool $useSSL = true, + $level = Level::Debug, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + if ($useSSL && !extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for InsightOpsHandler'); + } + + $endpoint = $useSSL + ? 'ssl://' . $region . '.data.logs.insight.rapid7.com:443' + : $region . '.data.logs.insight.rapid7.com:80'; + + parent::__construct( + $endpoint, + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); + $this->logToken = $token; + } + + /** + * @inheritDoc + */ + protected function generateDataStream(LogRecord $record): string + { + return $this->logToken . ' ' . $record->formatted; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php new file mode 100644 index 000000000000..00259834eb3e --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\LogRecord; + +/** + * @author Robert Kaufmann III + */ +class LogEntriesHandler extends SocketHandler +{ + protected string $logToken; + + /** + * @param string $token Log token supplied by LogEntries + * @param bool $useSSL Whether or not SSL encryption should be used. + * @param string $host Custom hostname to send the data to if needed + * + * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing + */ + public function __construct( + string $token, + bool $useSSL = true, + $level = Level::Debug, + bool $bubble = true, + string $host = 'data.logentries.com', + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + if ($useSSL && !extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for LogEntriesHandler'); + } + + $endpoint = $useSSL ? 'ssl://' . $host . ':443' : $host . ':80'; + parent::__construct( + $endpoint, + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); + $this->logToken = $token; + } + + /** + * @inheritDoc + */ + protected function generateDataStream(LogRecord $record): string + { + return $this->logToken . ' ' . $record->formatted; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/LogglyHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/LogglyHandler.php new file mode 100644 index 000000000000..2d8e66f18017 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/LogglyHandler.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LogglyFormatter; +use function array_key_exists; +use CurlHandle; +use Monolog\LogRecord; + +/** + * Sends errors to Loggly. + * + * @author Przemek Sobstel + * @author Adam Pancutt + * @author Gregory Barchard + */ +class LogglyHandler extends AbstractProcessingHandler +{ + protected const HOST = 'logs-01.loggly.com'; + protected const ENDPOINT_SINGLE = 'inputs'; + protected const ENDPOINT_BATCH = 'bulk'; + + /** + * Caches the curl handlers for every given endpoint. + * + * @var CurlHandle[] + */ + protected array $curlHandlers = []; + + protected string $token; + + /** @var string[] */ + protected array $tag = []; + + /** + * @param string $token API token supplied by Loggly + * + * @throws MissingExtensionException If the curl extension is missing + */ + public function __construct(string $token, int|string|Level $level = Level::Debug, bool $bubble = true) + { + if (!extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the LogglyHandler'); + } + + $this->token = $token; + + parent::__construct($level, $bubble); + } + + /** + * Loads and returns the shared curl handler for the given endpoint. + */ + protected function getCurlHandler(string $endpoint): CurlHandle + { + if (!array_key_exists($endpoint, $this->curlHandlers)) { + $this->curlHandlers[$endpoint] = $this->loadCurlHandle($endpoint); + } + + return $this->curlHandlers[$endpoint]; + } + + /** + * Starts a fresh curl session for the given endpoint and returns its handler. + */ + private function loadCurlHandle(string $endpoint): CurlHandle + { + $url = sprintf("https://%s/%s/%s/", static::HOST, $endpoint, $this->token); + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + return $ch; + } + + /** + * @param string[]|string $tag + */ + public function setTag(string|array $tag): self + { + if ('' === $tag || [] === $tag) { + $this->tag = []; + } else { + $this->tag = is_array($tag) ? $tag : [$tag]; + } + + return $this; + } + + /** + * @param string[]|string $tag + */ + public function addTag(string|array $tag): self + { + if ('' !== $tag) { + $tag = is_array($tag) ? $tag : [$tag]; + $this->tag = array_unique(array_merge($this->tag, $tag)); + } + + return $this; + } + + protected function write(LogRecord $record): void + { + $this->send($record->formatted, static::ENDPOINT_SINGLE); + } + + public function handleBatch(array $records): void + { + $level = $this->level; + + $records = array_filter($records, function ($record) use ($level) { + return ($record->level >= $level); + }); + + if (\count($records) > 0) { + $this->send($this->getFormatter()->formatBatch($records), static::ENDPOINT_BATCH); + } + } + + protected function send(string $data, string $endpoint): void + { + $ch = $this->getCurlHandler($endpoint); + + $headers = ['Content-Type: application/json']; + + if (\count($this->tag) > 0) { + $headers[] = 'X-LOGGLY-TAG: '.implode(',', $this->tag); + } + + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + + Curl\Util::execute($ch, 5, false); + } + + protected function getDefaultFormatter(): FormatterInterface + { + return new LogglyFormatter(); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php new file mode 100644 index 000000000000..876b1a953ffb --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LogmaticFormatter; +use Monolog\LogRecord; + +/** + * @author Julien Breux + */ +class LogmaticHandler extends SocketHandler +{ + private string $logToken; + + private string $hostname; + + private string $appName; + + /** + * @param string $token Log token supplied by Logmatic. + * @param string $hostname Host name supplied by Logmatic. + * @param string $appName Application name supplied by Logmatic. + * @param bool $useSSL Whether or not SSL encryption should be used. + * + * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing + */ + public function __construct( + string $token, + string $hostname = '', + string $appName = '', + bool $useSSL = true, + $level = Level::Debug, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + if ($useSSL && !extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use SSL encrypted connection for LogmaticHandler'); + } + + $endpoint = $useSSL ? 'ssl://api.logmatic.io:10515' : 'api.logmatic.io:10514'; + $endpoint .= '/v1/'; + + parent::__construct( + $endpoint, + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); + + $this->logToken = $token; + $this->hostname = $hostname; + $this->appName = $appName; + } + + /** + * @inheritDoc + */ + protected function generateDataStream(LogRecord $record): string + { + return $this->logToken . ' ' . $record->formatted; + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + $formatter = new LogmaticFormatter(); + + if ($this->hostname !== '') { + $formatter->setHostname($this->hostname); + } + if ($this->appName !== '') { + $formatter->setAppName($this->appName); + } + + return $formatter; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/MailHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/MailHandler.php new file mode 100644 index 000000000000..b6c822772664 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/MailHandler.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\HtmlFormatter; +use Monolog\LogRecord; + +/** + * Base class for all mail handlers + * + * @author Gyula Sallai + */ +abstract class MailHandler extends AbstractProcessingHandler +{ + /** + * @inheritDoc + */ + public function handleBatch(array $records): void + { + $messages = []; + + foreach ($records as $record) { + if ($record->level->isLowerThan($this->level)) { + continue; + } + + $message = $this->processRecord($record); + $messages[] = $message; + } + + if (\count($messages) > 0) { + $this->send((string) $this->getFormatter()->formatBatch($messages), $messages); + } + } + + /** + * Send a mail with the given content + * + * @param string $content formatted email body to be sent + * @param array $records the array of log records that formed this content + * + * @phpstan-param non-empty-array $records + */ + abstract protected function send(string $content, array $records): void; + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $this->send((string) $record->formatted, [$record]); + } + + /** + * @phpstan-param non-empty-array $records + */ + protected function getHighestRecord(array $records): LogRecord + { + $highestRecord = null; + foreach ($records as $record) { + if ($highestRecord === null || $record->level->isHigherThan($highestRecord->level)) { + $highestRecord = $record; + } + } + + return $highestRecord; + } + + protected function isHtmlBody(string $body): bool + { + return ($body[0] ?? null) === '<'; + } + + /** + * Gets the default formatter. + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new HtmlFormatter(); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/MandrillHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/MandrillHandler.php new file mode 100644 index 000000000000..64e16c9deeb5 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/MandrillHandler.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Swift; +use Swift_Message; + +/** + * MandrillHandler uses cURL to send the emails to the Mandrill API + * + * @author Adam Nicholson + */ +class MandrillHandler extends MailHandler +{ + protected Swift_Message $message; + protected string $apiKey; + + /** + * @phpstan-param (Swift_Message|callable(): Swift_Message) $message + * + * @param string $apiKey A valid Mandrill API key + * @param callable|Swift_Message $message An example message for real messages, only the body will be replaced + * + * @throws \InvalidArgumentException if not a Swift Message is set + */ + public function __construct(string $apiKey, callable|Swift_Message $message, int|string|Level $level = Level::Error, bool $bubble = true) + { + parent::__construct($level, $bubble); + + if (!$message instanceof Swift_Message) { + $message = $message(); + } + if (!$message instanceof Swift_Message) { + throw new \InvalidArgumentException('You must provide either a Swift_Message instance or a callable returning it'); + } + $this->message = $message; + $this->apiKey = $apiKey; + } + + /** + * @inheritDoc + */ + protected function send(string $content, array $records): void + { + $mime = 'text/plain'; + if ($this->isHtmlBody($content)) { + $mime = 'text/html'; + } + + $message = clone $this->message; + $message->setBody($content, $mime); + /** @phpstan-ignore-next-line */ + if (version_compare(Swift::VERSION, '6.0.0', '>=')) { + $message->setDate(new \DateTimeImmutable()); + } else { + /** @phpstan-ignore-next-line */ + $message->setDate(time()); + } + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, 'https://mandrillapp.com/api/1.0/messages/send-raw.json'); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([ + 'key' => $this->apiKey, + 'raw_message' => (string) $message, + 'async' => false, + ])); + + Curl\Util::execute($ch); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php new file mode 100644 index 000000000000..33ab68c6d080 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use MongoDB\Driver\BulkWrite; +use MongoDB\Driver\Manager; +use MongoDB\Client; +use Monolog\Level; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\MongoDBFormatter; +use Monolog\LogRecord; + +/** + * Logs to a MongoDB database. + * + * Usage example: + * + * $log = new \Monolog\Logger('application'); + * $client = new \MongoDB\Client('mongodb://localhost:27017'); + * $mongodb = new \Monolog\Handler\MongoDBHandler($client, 'logs', 'prod'); + * $log->pushHandler($mongodb); + * + * The above examples uses the MongoDB PHP library's client class; however, the + * MongoDB\Driver\Manager class from ext-mongodb is also supported. + */ +class MongoDBHandler extends AbstractProcessingHandler +{ + private \MongoDB\Collection $collection; + + private Client|Manager $manager; + + private string|null $namespace = null; + + /** + * Constructor. + * + * @param Client|Manager $mongodb MongoDB library or driver client + * @param string $database Database name + * @param string $collection Collection name + */ + public function __construct(Client|Manager $mongodb, string $database, string $collection, int|string|Level $level = Level::Debug, bool $bubble = true) + { + if ($mongodb instanceof Client) { + $this->collection = $mongodb->selectCollection($database, $collection); + } else { + $this->manager = $mongodb; + $this->namespace = $database . '.' . $collection; + } + + parent::__construct($level, $bubble); + } + + protected function write(LogRecord $record): void + { + if (isset($this->collection)) { + $this->collection->insertOne($record->formatted); + } + + if (isset($this->manager, $this->namespace)) { + $bulk = new BulkWrite; + $bulk->insert($record->formatted); + $this->manager->executeBulkWrite($this->namespace, $bulk); + } + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new MongoDBFormatter; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php new file mode 100644 index 000000000000..d4c9d801079c --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Formatter\LineFormatter; + +/** + * NativeMailerHandler uses the mail() function to send the emails + * + * @author Christophe Coevoet + * @author Mark Garrett + */ +class NativeMailerHandler extends MailHandler +{ + /** + * The email addresses to which the message will be sent + * @var string[] + */ + protected array $to; + + /** + * The subject of the email + */ + protected string $subject; + + /** + * Optional headers for the message + * @var string[] + */ + protected array $headers = []; + + /** + * Optional parameters for the message + * @var string[] + */ + protected array $parameters = []; + + /** + * The wordwrap length for the message + */ + protected int $maxColumnWidth; + + /** + * The Content-type for the message + */ + protected string|null $contentType = null; + + /** + * The encoding for the message + */ + protected string $encoding = 'utf-8'; + + /** + * @param string|string[] $to The receiver of the mail + * @param string $subject The subject of the mail + * @param string $from The sender of the mail + * @param int $maxColumnWidth The maximum column width that the message lines will have + */ + public function __construct(string|array $to, string $subject, string $from, int|string|Level $level = Level::Error, bool $bubble = true, int $maxColumnWidth = 70) + { + parent::__construct($level, $bubble); + $this->to = (array) $to; + $this->subject = $subject; + $this->addHeader(sprintf('From: %s', $from)); + $this->maxColumnWidth = $maxColumnWidth; + } + + /** + * Add headers to the message + * + * @param string|string[] $headers Custom added headers + */ + public function addHeader($headers): self + { + foreach ((array) $headers as $header) { + if (strpos($header, "\n") !== false || strpos($header, "\r") !== false) { + throw new \InvalidArgumentException('Headers can not contain newline characters for security reasons'); + } + $this->headers[] = $header; + } + + return $this; + } + + /** + * Add parameters to the message + * + * @param string|string[] $parameters Custom added parameters + */ + public function addParameter($parameters): self + { + $this->parameters = array_merge($this->parameters, (array) $parameters); + + return $this; + } + + /** + * @inheritDoc + */ + protected function send(string $content, array $records): void + { + $contentType = $this->getContentType() ?? ($this->isHtmlBody($content) ? 'text/html' : 'text/plain'); + + if ($contentType !== 'text/html') { + $content = wordwrap($content, $this->maxColumnWidth); + } + + $headers = ltrim(implode("\r\n", $this->headers) . "\r\n", "\r\n"); + $headers .= 'Content-type: ' . $contentType . '; charset=' . $this->getEncoding() . "\r\n"; + if ($contentType === 'text/html' && false === strpos($headers, 'MIME-Version:')) { + $headers .= 'MIME-Version: 1.0' . "\r\n"; + } + + $subjectFormatter = new LineFormatter($this->subject); + $subject = $subjectFormatter->format($this->getHighestRecord($records)); + + $parameters = implode(' ', $this->parameters); + foreach ($this->to as $to) { + mail($to, $subject, $content, $headers, $parameters); + } + } + + public function getContentType(): ?string + { + return $this->contentType; + } + + public function getEncoding(): string + { + return $this->encoding; + } + + /** + * @param string $contentType The content type of the email - Defaults to text/plain. Use text/html for HTML messages. + */ + public function setContentType(string $contentType): self + { + if (strpos($contentType, "\n") !== false || strpos($contentType, "\r") !== false) { + throw new \InvalidArgumentException('The content type can not contain newline characters to prevent email header injection'); + } + + $this->contentType = $contentType; + + return $this; + } + + public function setEncoding(string $encoding): self + { + if (strpos($encoding, "\n") !== false || strpos($encoding, "\r") !== false) { + throw new \InvalidArgumentException('The encoding can not contain newline characters to prevent email header injection'); + } + + $this->encoding = $encoding; + + return $this; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php new file mode 100644 index 000000000000..b8cb3785b830 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php @@ -0,0 +1,180 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Utils; +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\LogRecord; + +/** + * Class to record a log on a NewRelic application. + * Enabling New Relic High Security mode may prevent capture of useful information. + * + * This handler requires a NormalizerFormatter to function and expects an array in $record->formatted + * + * @see https://docs.newrelic.com/docs/agents/php-agent + * @see https://docs.newrelic.com/docs/accounts-partnerships/accounts/security/high-security + */ +class NewRelicHandler extends AbstractProcessingHandler +{ + /** + * @inheritDoc + */ + public function __construct( + int|string|Level $level = Level::Error, + bool $bubble = true, + + /** + * Name of the New Relic application that will receive logs from this handler. + */ + protected string|null $appName = null, + + /** + * Some context and extra data is passed into the handler as arrays of values. Do we send them as is + * (useful if we are using the API), or explode them for display on the NewRelic RPM website? + */ + protected bool $explodeArrays = false, + + /** + * Name of the current transaction + */ + protected string|null $transactionName = null + ) { + parent::__construct($level, $bubble); + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + if (!$this->isNewRelicEnabled()) { + throw new MissingExtensionException('The newrelic PHP extension is required to use the NewRelicHandler'); + } + + if (null !== ($appName = $this->getAppName($record->context))) { + $this->setNewRelicAppName($appName); + } + + if (null !== ($transactionName = $this->getTransactionName($record->context))) { + $this->setNewRelicTransactionName($transactionName); + unset($record->formatted['context']['transaction_name']); + } + + if (isset($record->context['exception']) && $record->context['exception'] instanceof \Throwable) { + newrelic_notice_error($record->message, $record->context['exception']); + unset($record->formatted['context']['exception']); + } else { + newrelic_notice_error($record->message); + } + + if (isset($record->formatted['context']) && is_array($record->formatted['context'])) { + foreach ($record->formatted['context'] as $key => $parameter) { + if (is_array($parameter) && $this->explodeArrays) { + foreach ($parameter as $paramKey => $paramValue) { + $this->setNewRelicParameter('context_' . $key . '_' . $paramKey, $paramValue); + } + } else { + $this->setNewRelicParameter('context_' . $key, $parameter); + } + } + } + + if (isset($record->formatted['extra']) && is_array($record->formatted['extra'])) { + foreach ($record->formatted['extra'] as $key => $parameter) { + if (is_array($parameter) && $this->explodeArrays) { + foreach ($parameter as $paramKey => $paramValue) { + $this->setNewRelicParameter('extra_' . $key . '_' . $paramKey, $paramValue); + } + } else { + $this->setNewRelicParameter('extra_' . $key, $parameter); + } + } + } + } + + /** + * Checks whether the NewRelic extension is enabled in the system. + */ + protected function isNewRelicEnabled(): bool + { + return extension_loaded('newrelic'); + } + + /** + * Returns the appname where this log should be sent. Each log can override the default appname, set in this + * handler's constructor, by providing the appname in it's context. + * + * @param mixed[] $context + */ + protected function getAppName(array $context): ?string + { + if (isset($context['appname'])) { + return $context['appname']; + } + + return $this->appName; + } + + /** + * Returns the name of the current transaction. Each log can override the default transaction name, set in this + * handler's constructor, by providing the transaction_name in it's context + * + * @param mixed[] $context + */ + protected function getTransactionName(array $context): ?string + { + if (isset($context['transaction_name'])) { + return $context['transaction_name']; + } + + return $this->transactionName; + } + + /** + * Sets the NewRelic application that should receive this log. + */ + protected function setNewRelicAppName(string $appName): void + { + newrelic_set_appname($appName); + } + + /** + * Overwrites the name of the current transaction + */ + protected function setNewRelicTransactionName(string $transactionName): void + { + newrelic_name_transaction($transactionName); + } + + /** + * @param mixed $value + */ + protected function setNewRelicParameter(string $key, $value): void + { + if (null === $value || is_scalar($value)) { + newrelic_add_custom_parameter($key, $value); + } else { + newrelic_add_custom_parameter($key, Utils::jsonEncode($value, null, true)); + } + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new NormalizerFormatter(); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NoopHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NoopHandler.php new file mode 100644 index 000000000000..d9fea180c50d --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NoopHandler.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\LogRecord; + +/** + * No-op + * + * This handler handles anything, but does nothing, and does not stop bubbling to the rest of the stack. + * This can be used for testing, or to disable a handler when overriding a configuration without + * influencing the rest of the stack. + * + * @author Roel Harbers + */ +class NoopHandler extends Handler +{ + /** + * @inheritDoc + */ + public function isHandling(LogRecord $record): bool + { + return true; + } + + /** + * @inheritDoc + */ + public function handle(LogRecord $record): bool + { + return false; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NullHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NullHandler.php new file mode 100644 index 000000000000..1aa84e4f8d71 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NullHandler.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Psr\Log\LogLevel; +use Monolog\Logger; +use Monolog\LogRecord; + +/** + * Blackhole + * + * Any record it can handle will be thrown away. This can be used + * to put on top of an existing stack to override it temporarily. + * + * @author Jordi Boggiano + */ +class NullHandler extends Handler +{ + private Level $level; + + /** + * @param string|int|Level $level The minimum logging level at which this handler will be triggered + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + public function __construct(string|int|Level $level = Level::Debug) + { + $this->level = Logger::toMonologLevel($level); + } + + /** + * @inheritDoc + */ + public function isHandling(LogRecord $record): bool + { + return $record->level->value >= $this->level->value; + } + + /** + * @inheritDoc + */ + public function handle(LogRecord $record): bool + { + return $record->level->value >= $this->level->value; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/OverflowHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/OverflowHandler.php new file mode 100644 index 000000000000..a72b7a11d771 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/OverflowHandler.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Formatter\FormatterInterface; +use Monolog\LogRecord; + +/** + * Handler to only pass log messages when a certain threshold of number of messages is reached. + * + * This can be useful in cases of processing a batch of data, but you're for example only interested + * in case it fails catastrophically instead of a warning for 1 or 2 events. Worse things can happen, right? + * + * Usage example: + * + * ``` + * $log = new Logger('application'); + * $handler = new SomeHandler(...) + * + * // Pass all warnings to the handler when more than 10 & all error messages when more then 5 + * $overflow = new OverflowHandler($handler, [Level::Warning->value => 10, Level::Error->value => 5]); + * + * $log->pushHandler($overflow); + *``` + * + * @author Kris Buist + */ +class OverflowHandler extends AbstractHandler implements FormattableHandlerInterface +{ + private HandlerInterface $handler; + + /** @var array */ + private array $thresholdMap = []; + + /** + * Buffer of all messages passed to the handler before the threshold was reached + * + * @var mixed[][] + */ + private array $buffer = []; + + /** + * @param array $thresholdMap Dictionary of log level value => threshold + */ + public function __construct( + HandlerInterface $handler, + array $thresholdMap = [], + $level = Level::Debug, + bool $bubble = true + ) { + $this->handler = $handler; + foreach ($thresholdMap as $thresholdLevel => $threshold) { + $this->thresholdMap[$thresholdLevel] = $threshold; + } + parent::__construct($level, $bubble); + } + + /** + * Handles a record. + * + * All records may be passed to this method, and the handler should discard + * those that it does not want to handle. + * + * The return value of this function controls the bubbling process of the handler stack. + * Unless the bubbling is interrupted (by returning true), the Logger class will keep on + * calling further handlers in the stack with a given log record. + * + * @inheritDoc + */ + public function handle(LogRecord $record): bool + { + if ($record->level->isLowerThan($this->level)) { + return false; + } + + $level = $record->level->value; + + if (!isset($this->thresholdMap[$level])) { + $this->thresholdMap[$level] = 0; + } + + if ($this->thresholdMap[$level] > 0) { + // The overflow threshold is not yet reached, so we're buffering the record and lowering the threshold by 1 + $this->thresholdMap[$level]--; + $this->buffer[$level][] = $record; + + return false === $this->bubble; + } + + if ($this->thresholdMap[$level] == 0) { + // This current message is breaking the threshold. Flush the buffer and continue handling the current record + foreach ($this->buffer[$level] ?? [] as $buffered) { + $this->handler->handle($buffered); + } + $this->thresholdMap[$level]--; + unset($this->buffer[$level]); + } + + $this->handler->handle($record); + + return false === $this->bubble; + } + + /** + * @inheritDoc + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + $this->handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); + } + + /** + * @inheritDoc + */ + public function getFormatter(): FormatterInterface + { + if ($this->handler instanceof FormattableHandlerInterface) { + return $this->handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php new file mode 100644 index 000000000000..8aa78e4c4298 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php @@ -0,0 +1,303 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Level; +use Monolog\Utils; +use PhpConsole\Connector; +use PhpConsole\Handler as VendorPhpConsoleHandler; +use PhpConsole\Helper; +use Monolog\LogRecord; +use PhpConsole\Storage; + +/** + * Monolog handler for Google Chrome extension "PHP Console" + * + * Display PHP error/debug log messages in Google Chrome console and notification popups, executes PHP code remotely + * + * Usage: + * 1. Install Google Chrome extension [now dead and removed from the chrome store] + * 2. See overview https://github.com/barbushin/php-console#overview + * 3. Install PHP Console library https://github.com/barbushin/php-console#installation + * 4. Example (result will looks like http://i.hizliresim.com/vg3Pz4.png) + * + * $logger = new \Monolog\Logger('all', array(new \Monolog\Handler\PHPConsoleHandler())); + * \Monolog\ErrorHandler::register($logger); + * echo $undefinedVar; + * $logger->debug('SELECT * FROM users', array('db', 'time' => 0.012)); + * PC::debug($_SERVER); // PHP Console debugger for any type of vars + * + * @author Sergey Barbushin https://www.linkedin.com/in/barbushin + * @phpstan-type Options array{ + * enabled: bool, + * classesPartialsTraceIgnore: string[], + * debugTagsKeysInContext: array, + * useOwnErrorsHandler: bool, + * useOwnExceptionsHandler: bool, + * sourcesBasePath: string|null, + * registerHelper: bool, + * serverEncoding: string|null, + * headersLimit: int|null, + * password: string|null, + * enableSslOnlyMode: bool, + * ipMasks: string[], + * enableEvalListener: bool, + * dumperDetectCallbacks: bool, + * dumperLevelLimit: int, + * dumperItemsCountLimit: int, + * dumperItemSizeLimit: int, + * dumperDumpSizeLimit: int, + * detectDumpTraceAndSource: bool, + * dataStorage: Storage|null + * } + * @phpstan-type InputOptions array{ + * enabled?: bool, + * classesPartialsTraceIgnore?: string[], + * debugTagsKeysInContext?: array, + * useOwnErrorsHandler?: bool, + * useOwnExceptionsHandler?: bool, + * sourcesBasePath?: string|null, + * registerHelper?: bool, + * serverEncoding?: string|null, + * headersLimit?: int|null, + * password?: string|null, + * enableSslOnlyMode?: bool, + * ipMasks?: string[], + * enableEvalListener?: bool, + * dumperDetectCallbacks?: bool, + * dumperLevelLimit?: int, + * dumperItemsCountLimit?: int, + * dumperItemSizeLimit?: int, + * dumperDumpSizeLimit?: int, + * detectDumpTraceAndSource?: bool, + * dataStorage?: Storage|null + * } + * + * @deprecated Since 2.8.0 and 3.2.0, PHPConsole is abandoned and thus we will drop this handler in Monolog 4 + */ +class PHPConsoleHandler extends AbstractProcessingHandler +{ + /** + * @phpstan-var Options + */ + private array $options = [ + 'enabled' => true, // bool Is PHP Console server enabled + 'classesPartialsTraceIgnore' => ['Monolog\\'], // array Hide calls of classes started with... + 'debugTagsKeysInContext' => [0, 'tag'], // bool Is PHP Console server enabled + 'useOwnErrorsHandler' => false, // bool Enable errors handling + 'useOwnExceptionsHandler' => false, // bool Enable exceptions handling + 'sourcesBasePath' => null, // string Base path of all project sources to strip in errors source paths + 'registerHelper' => true, // bool Register PhpConsole\Helper that allows short debug calls like PC::debug($var, 'ta.g.s') + 'serverEncoding' => null, // string|null Server internal encoding + 'headersLimit' => null, // int|null Set headers size limit for your web-server + 'password' => null, // string|null Protect PHP Console connection by password + 'enableSslOnlyMode' => false, // bool Force connection by SSL for clients with PHP Console installed + 'ipMasks' => [], // array Set IP masks of clients that will be allowed to connect to PHP Console: array('192.168.*.*', '127.0.0.1') + 'enableEvalListener' => false, // bool Enable eval request to be handled by eval dispatcher(if enabled, 'password' option is also required) + 'dumperDetectCallbacks' => false, // bool Convert callback items in dumper vars to (callback SomeClass::someMethod) strings + 'dumperLevelLimit' => 5, // int Maximum dumped vars array or object nested dump level + 'dumperItemsCountLimit' => 100, // int Maximum dumped var same level array items or object properties number + 'dumperItemSizeLimit' => 5000, // int Maximum length of any string or dumped array item + 'dumperDumpSizeLimit' => 500000, // int Maximum approximate size of dumped vars result formatted in JSON + 'detectDumpTraceAndSource' => false, // bool Autodetect and append trace data to debug + 'dataStorage' => null, // \PhpConsole\Storage|null Fixes problem with custom $_SESSION handler(see http://goo.gl/Ne8juJ) + ]; + + private Connector $connector; + + /** + * @param array $options See \Monolog\Handler\PHPConsoleHandler::$options for more details + * @param Connector|null $connector Instance of \PhpConsole\Connector class (optional) + * @throws \RuntimeException + * @phpstan-param InputOptions $options + */ + public function __construct(array $options = [], ?Connector $connector = null, int|string|Level $level = Level::Debug, bool $bubble = true) + { + if (!class_exists('PhpConsole\Connector')) { + throw new \RuntimeException('PHP Console library not found. See https://github.com/barbushin/php-console#installation'); + } + parent::__construct($level, $bubble); + $this->options = $this->initOptions($options); + $this->connector = $this->initConnector($connector); + } + + /** + * @param array $options + * @return array + * + * @phpstan-param InputOptions $options + * @phpstan-return Options + */ + private function initOptions(array $options): array + { + $wrongOptions = array_diff(array_keys($options), array_keys($this->options)); + if (\count($wrongOptions) > 0) { + throw new \RuntimeException('Unknown options: ' . implode(', ', $wrongOptions)); + } + + return array_replace($this->options, $options); + } + + private function initConnector(?Connector $connector = null): Connector + { + if (null === $connector) { + if ($this->options['dataStorage'] instanceof Storage) { + Connector::setPostponeStorage($this->options['dataStorage']); + } + $connector = Connector::getInstance(); + } + + if ($this->options['registerHelper'] && !Helper::isRegistered()) { + Helper::register(); + } + + if ($this->options['enabled'] && $connector->isActiveClient()) { + if ($this->options['useOwnErrorsHandler'] || $this->options['useOwnExceptionsHandler']) { + $handler = VendorPhpConsoleHandler::getInstance(); + $handler->setHandleErrors($this->options['useOwnErrorsHandler']); + $handler->setHandleExceptions($this->options['useOwnExceptionsHandler']); + $handler->start(); + } + if (null !== $this->options['sourcesBasePath']) { + $connector->setSourcesBasePath($this->options['sourcesBasePath']); + } + if (null !== $this->options['serverEncoding']) { + $connector->setServerEncoding($this->options['serverEncoding']); + } + if (null !== $this->options['password']) { + $connector->setPassword($this->options['password']); + } + if ($this->options['enableSslOnlyMode']) { + $connector->enableSslOnlyMode(); + } + if (\count($this->options['ipMasks']) > 0) { + $connector->setAllowedIpMasks($this->options['ipMasks']); + } + if (null !== $this->options['headersLimit'] && $this->options['headersLimit'] > 0) { + $connector->setHeadersLimit($this->options['headersLimit']); + } + if ($this->options['detectDumpTraceAndSource']) { + $connector->getDebugDispatcher()->detectTraceAndSource = true; + } + $dumper = $connector->getDumper(); + $dumper->levelLimit = $this->options['dumperLevelLimit']; + $dumper->itemsCountLimit = $this->options['dumperItemsCountLimit']; + $dumper->itemSizeLimit = $this->options['dumperItemSizeLimit']; + $dumper->dumpSizeLimit = $this->options['dumperDumpSizeLimit']; + $dumper->detectCallbacks = $this->options['dumperDetectCallbacks']; + if ($this->options['enableEvalListener']) { + $connector->startEvalRequestsListener(); + } + } + + return $connector; + } + + public function getConnector(): Connector + { + return $this->connector; + } + + /** + * @return array + */ + public function getOptions(): array + { + return $this->options; + } + + public function handle(LogRecord $record): bool + { + if ($this->options['enabled'] && $this->connector->isActiveClient()) { + return parent::handle($record); + } + + return !$this->bubble; + } + + /** + * Writes the record down to the log of the implementing handler + */ + protected function write(LogRecord $record): void + { + if ($record->level->isLowerThan(Level::Notice)) { + $this->handleDebugRecord($record); + } elseif (isset($record->context['exception']) && $record->context['exception'] instanceof \Throwable) { + $this->handleExceptionRecord($record); + } else { + $this->handleErrorRecord($record); + } + } + + private function handleDebugRecord(LogRecord $record): void + { + [$tags, $filteredContext] = $this->getRecordTags($record); + $message = $record->message; + if (\count($filteredContext) > 0) { + $message .= ' ' . Utils::jsonEncode($this->connector->getDumper()->dump(array_filter($filteredContext)), null, true); + } + $this->connector->getDebugDispatcher()->dispatchDebug($message, $tags, $this->options['classesPartialsTraceIgnore']); + } + + private function handleExceptionRecord(LogRecord $record): void + { + $this->connector->getErrorsDispatcher()->dispatchException($record->context['exception']); + } + + private function handleErrorRecord(LogRecord $record): void + { + $context = $record->context; + + $this->connector->getErrorsDispatcher()->dispatchError( + $context['code'] ?? null, + $context['message'] ?? $record->message, + $context['file'] ?? null, + $context['line'] ?? null, + $this->options['classesPartialsTraceIgnore'] + ); + } + + /** + * @return array{string, mixed[]} + */ + private function getRecordTags(LogRecord $record): array + { + $tags = null; + $filteredContext = []; + if ($record->context !== []) { + $filteredContext = $record->context; + foreach ($this->options['debugTagsKeysInContext'] as $key) { + if (isset($filteredContext[$key])) { + $tags = $filteredContext[$key]; + if ($key === 0) { + array_shift($filteredContext); + } else { + unset($filteredContext[$key]); + } + break; + } + } + } + + return [$tags ?? $record->level->toPsrLogLevel(), $filteredContext]; + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter('%message%'); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ProcessHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ProcessHandler.php new file mode 100644 index 000000000000..9edc9ac543d6 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ProcessHandler.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\LogRecord; + +/** + * Stores to STDIN of any process, specified by a command. + * + * Usage example: + *
+ * $log = new Logger('myLogger');
+ * $log->pushHandler(new ProcessHandler('/usr/bin/php /var/www/monolog/someScript.php'));
+ * 
+ * + * @author Kolja Zuelsdorf + */ +class ProcessHandler extends AbstractProcessingHandler +{ + /** + * Holds the process to receive data on its STDIN. + * + * @var resource|bool|null + */ + private $process; + + private string $command; + + private ?string $cwd; + + /** + * @var resource[] + */ + private array $pipes = []; + + /** + * @var array + */ + protected const DESCRIPTOR_SPEC = [ + 0 => ['pipe', 'r'], // STDIN is a pipe that the child will read from + 1 => ['pipe', 'w'], // STDOUT is a pipe that the child will write to + 2 => ['pipe', 'w'], // STDERR is a pipe to catch the any errors + ]; + + /** + * @param string $command Command for the process to start. Absolute paths are recommended, + * especially if you do not use the $cwd parameter. + * @param string|null $cwd "Current working directory" (CWD) for the process to be executed in. + * @throws \InvalidArgumentException + */ + public function __construct(string $command, int|string|Level $level = Level::Debug, bool $bubble = true, ?string $cwd = null) + { + if ($command === '') { + throw new \InvalidArgumentException('The command argument must be a non-empty string.'); + } + if ($cwd === '') { + throw new \InvalidArgumentException('The optional CWD argument must be a non-empty string or null.'); + } + + parent::__construct($level, $bubble); + + $this->command = $command; + $this->cwd = $cwd; + } + + /** + * Writes the record down to the log of the implementing handler + * + * @throws \UnexpectedValueException + */ + protected function write(LogRecord $record): void + { + $this->ensureProcessIsStarted(); + + $this->writeProcessInput($record->formatted); + + $errors = $this->readProcessErrors(); + if ($errors !== '') { + throw new \UnexpectedValueException(sprintf('Errors while writing to process: %s', $errors)); + } + } + + /** + * Makes sure that the process is actually started, and if not, starts it, + * assigns the stream pipes, and handles startup errors, if any. + */ + private function ensureProcessIsStarted(): void + { + if (is_resource($this->process) === false) { + $this->startProcess(); + + $this->handleStartupErrors(); + } + } + + /** + * Starts the actual process and sets all streams to non-blocking. + */ + private function startProcess(): void + { + $this->process = proc_open($this->command, static::DESCRIPTOR_SPEC, $this->pipes, $this->cwd); + + foreach ($this->pipes as $pipe) { + stream_set_blocking($pipe, false); + } + } + + /** + * Selects the STDERR stream, handles upcoming startup errors, and throws an exception, if any. + * + * @throws \UnexpectedValueException + */ + private function handleStartupErrors(): void + { + $selected = $this->selectErrorStream(); + if (false === $selected) { + throw new \UnexpectedValueException('Something went wrong while selecting a stream.'); + } + + $errors = $this->readProcessErrors(); + + if (is_resource($this->process) === false || $errors !== '') { + throw new \UnexpectedValueException( + sprintf('The process "%s" could not be opened: ' . $errors, $this->command) + ); + } + } + + /** + * Selects the STDERR stream. + * + * @return int|bool + */ + protected function selectErrorStream() + { + $empty = []; + $errorPipes = [$this->pipes[2]]; + + return stream_select($errorPipes, $empty, $empty, 1); + } + + /** + * Reads the errors of the process, if there are any. + * + * @codeCoverageIgnore + * @return string Empty string if there are no errors. + */ + protected function readProcessErrors(): string + { + return (string) stream_get_contents($this->pipes[2]); + } + + /** + * Writes to the input stream of the opened process. + * + * @codeCoverageIgnore + */ + protected function writeProcessInput(string $string): void + { + fwrite($this->pipes[0], $string); + } + + /** + * @inheritDoc + */ + public function close(): void + { + if (is_resource($this->process)) { + foreach ($this->pipes as $pipe) { + fclose($pipe); + } + proc_close($this->process); + $this->process = null; + } + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php new file mode 100644 index 000000000000..9fb290faadbc --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Processor\ProcessorInterface; +use Monolog\LogRecord; + +/** + * Interface to describe loggers that have processors + * + * @author Jordi Boggiano + */ +interface ProcessableHandlerInterface +{ + /** + * Adds a processor in the stack. + * + * @phpstan-param ProcessorInterface|(callable(LogRecord): LogRecord) $callback + * + * @param ProcessorInterface|callable $callback + * @return HandlerInterface self + */ + public function pushProcessor(callable $callback): HandlerInterface; + + /** + * Removes the processor on top of the stack and returns it. + * + * @phpstan-return ProcessorInterface|(callable(LogRecord): LogRecord) $callback + * + * @throws \LogicException In case the processor stack is empty + * @return callable|ProcessorInterface + */ + public function popProcessor(): callable; +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php new file mode 100644 index 000000000000..74eeddddcec3 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\ResettableInterface; +use Monolog\Processor\ProcessorInterface; +use Monolog\LogRecord; + +/** + * Helper trait for implementing ProcessableInterface + * + * @author Jordi Boggiano + */ +trait ProcessableHandlerTrait +{ + /** + * @var callable[] + * @phpstan-var array<(callable(LogRecord): LogRecord)|ProcessorInterface> + */ + protected array $processors = []; + + /** + * @inheritDoc + */ + public function pushProcessor(callable $callback): HandlerInterface + { + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * @inheritDoc + */ + public function popProcessor(): callable + { + if (\count($this->processors) === 0) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + protected function processRecord(LogRecord $record): LogRecord + { + foreach ($this->processors as $processor) { + $record = $processor($record); + } + + return $record; + } + + protected function resetProcessors(): void + { + foreach ($this->processors as $processor) { + if ($processor instanceof ResettableInterface) { + $processor->reset(); + } + } + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/PsrHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/PsrHandler.php new file mode 100644 index 000000000000..6599a83b4245 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/PsrHandler.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Psr\Log\LoggerInterface; +use Monolog\Formatter\FormatterInterface; +use Monolog\LogRecord; + +/** + * Proxies log messages to an existing PSR-3 compliant logger. + * + * If a formatter is configured, the formatter's output MUST be a string and the + * formatted message will be fed to the wrapped PSR logger instead of the original + * log record's message. + * + * @author Michael Moussa + */ +class PsrHandler extends AbstractHandler implements FormattableHandlerInterface +{ + /** + * PSR-3 compliant logger + */ + protected LoggerInterface $logger; + + protected FormatterInterface|null $formatter = null; + + /** + * @param LoggerInterface $logger The underlying PSR-3 compliant logger to which messages will be proxied + */ + public function __construct(LoggerInterface $logger, int|string|Level $level = Level::Debug, bool $bubble = true) + { + parent::__construct($level, $bubble); + + $this->logger = $logger; + } + + /** + * @inheritDoc + */ + public function handle(LogRecord $record): bool + { + if (!$this->isHandling($record)) { + return false; + } + + if ($this->formatter !== null) { + $formatted = $this->formatter->format($record); + $this->logger->log($record->level->toPsrLogLevel(), (string) $formatted, $record->context); + } else { + $this->logger->log($record->level->toPsrLogLevel(), $record->message, $record->context); + } + + return false === $this->bubble; + } + + /** + * Sets the formatter. + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $this->formatter = $formatter; + + return $this; + } + + /** + * Gets the formatter. + */ + public function getFormatter(): FormatterInterface + { + if ($this->formatter === null) { + throw new \LogicException('No formatter has been set and this handler does not have a default formatter'); + } + + return $this->formatter; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/PushoverHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/PushoverHandler.php new file mode 100644 index 000000000000..118f5760a110 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/PushoverHandler.php @@ -0,0 +1,242 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Logger; +use Monolog\Utils; +use Psr\Log\LogLevel; +use Monolog\LogRecord; + +/** + * Sends notifications through the pushover api to mobile phones + * + * @author Sebastian Göttschkes + * @see https://www.pushover.net/api + */ +class PushoverHandler extends SocketHandler +{ + private string $token; + + /** @var array */ + private array $users; + + private string $title; + + private string|int|null $user = null; + + private int $retry; + + private int $expire; + + private Level $highPriorityLevel; + + private Level $emergencyLevel; + + private bool $useFormattedMessage = false; + + /** + * All parameters that can be sent to Pushover + * @see https://pushover.net/api + * @var array + */ + private array $parameterNames = [ + 'token' => true, + 'user' => true, + 'message' => true, + 'device' => true, + 'title' => true, + 'url' => true, + 'url_title' => true, + 'priority' => true, + 'timestamp' => true, + 'sound' => true, + 'retry' => true, + 'expire' => true, + 'callback' => true, + ]; + + /** + * Sounds the api supports by default + * @see https://pushover.net/api#sounds + * @var string[] + */ + private array $sounds = [ + 'pushover', 'bike', 'bugle', 'cashregister', 'classical', 'cosmic', 'falling', 'gamelan', 'incoming', + 'intermission', 'magic', 'mechanical', 'pianobar', 'siren', 'spacealarm', 'tugboat', 'alien', 'climb', + 'persistent', 'echo', 'updown', 'none', + ]; + + /** + * @param string $token Pushover api token + * @param string|array $users Pushover user id or array of ids the message will be sent to + * @param string|null $title Title sent to the Pushover API + * @param bool $useSSL Whether to connect via SSL. Required when pushing messages to users that are not + * the pushover.net app owner. OpenSSL is required for this option. + * @param int $retry The retry parameter specifies how often (in seconds) the Pushover servers will + * send the same notification to the user. + * @param int $expire The expire parameter specifies how many seconds your notification will continue + * to be retried for (every retry seconds). + * + * @param int|string|Level|LogLevel::* $highPriorityLevel The minimum logging level at which this handler will start + * sending "high priority" requests to the Pushover API + * @param int|string|Level|LogLevel::* $emergencyLevel The minimum logging level at which this handler will start + * sending "emergency" requests to the Pushover API + * + * + * @phpstan-param string|array $users + * @phpstan-param value-of|value-of|Level|LogLevel::* $highPriorityLevel + * @phpstan-param value-of|value-of|Level|LogLevel::* $emergencyLevel + */ + public function __construct( + string $token, + $users, + ?string $title = null, + int|string|Level $level = Level::Critical, + bool $bubble = true, + bool $useSSL = true, + int|string|Level $highPriorityLevel = Level::Critical, + int|string|Level $emergencyLevel = Level::Emergency, + int $retry = 30, + int $expire = 25200, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + $connectionString = $useSSL ? 'ssl://api.pushover.net:443' : 'api.pushover.net:80'; + parent::__construct( + $connectionString, + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); + + $this->token = $token; + $this->users = (array) $users; + $this->title = $title ?? (string) gethostname(); + $this->highPriorityLevel = Logger::toMonologLevel($highPriorityLevel); + $this->emergencyLevel = Logger::toMonologLevel($emergencyLevel); + $this->retry = $retry; + $this->expire = $expire; + } + + protected function generateDataStream(LogRecord $record): string + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + private function buildContent(LogRecord $record): string + { + // Pushover has a limit of 512 characters on title and message combined. + $maxMessageLength = 512 - strlen($this->title); + + $message = ($this->useFormattedMessage) ? $record->formatted : $record->message; + $message = Utils::substr($message, 0, $maxMessageLength); + + $timestamp = $record->datetime->getTimestamp(); + + $dataArray = [ + 'token' => $this->token, + 'user' => $this->user, + 'message' => $message, + 'title' => $this->title, + 'timestamp' => $timestamp, + ]; + + if ($record->level->value >= $this->emergencyLevel->value) { + $dataArray['priority'] = 2; + $dataArray['retry'] = $this->retry; + $dataArray['expire'] = $this->expire; + } elseif ($record->level->value >= $this->highPriorityLevel->value) { + $dataArray['priority'] = 1; + } + + // First determine the available parameters + $context = array_intersect_key($record->context, $this->parameterNames); + $extra = array_intersect_key($record->extra, $this->parameterNames); + + // Least important info should be merged with subsequent info + $dataArray = array_merge($extra, $context, $dataArray); + + // Only pass sounds that are supported by the API + if (isset($dataArray['sound']) && !in_array($dataArray['sound'], $this->sounds, true)) { + unset($dataArray['sound']); + } + + return http_build_query($dataArray); + } + + private function buildHeader(string $content): string + { + $header = "POST /1/messages.json HTTP/1.1\r\n"; + $header .= "Host: api.pushover.net\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + protected function write(LogRecord $record): void + { + foreach ($this->users as $user) { + $this->user = $user; + + parent::write($record); + $this->closeSocket(); + } + + $this->user = null; + } + + /** + * @param int|string|Level|LogLevel::* $level + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + public function setHighPriorityLevel(int|string|Level $level): self + { + $this->highPriorityLevel = Logger::toMonologLevel($level); + + return $this; + } + + /** + * @param int|string|Level|LogLevel::* $level + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + public function setEmergencyLevel(int|string|Level $level): self + { + $this->emergencyLevel = Logger::toMonologLevel($level); + + return $this; + } + + /** + * Use the formatted message? + */ + public function useFormattedMessage(bool $useFormattedMessage): self + { + $this->useFormattedMessage = $useFormattedMessage; + + return $this; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RedisHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RedisHandler.php new file mode 100644 index 000000000000..5eee5dc693bb --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RedisHandler.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Level; +use Monolog\LogRecord; +use Predis\Client as Predis; +use Redis; + +/** + * Logs to a Redis key using rpush + * + * usage example: + * + * $log = new Logger('application'); + * $redis = new RedisHandler(new Predis\Client("tcp://localhost:6379"), "logs", "prod"); + * $log->pushHandler($redis); + * + * @author Thomas Tourlourat + */ +class RedisHandler extends AbstractProcessingHandler +{ + /** @var Predis|Redis */ + private Predis|Redis $redisClient; + private string $redisKey; + protected int $capSize; + + /** + * @param Predis|Redis $redis The redis instance + * @param string $key The key name to push records to + * @param int $capSize Number of entries to limit list size to, 0 = unlimited + */ + public function __construct(Predis|Redis $redis, string $key, int|string|Level $level = Level::Debug, bool $bubble = true, int $capSize = 0) + { + $this->redisClient = $redis; + $this->redisKey = $key; + $this->capSize = $capSize; + + parent::__construct($level, $bubble); + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + if ($this->capSize > 0) { + $this->writeCapped($record); + } else { + $this->redisClient->rpush($this->redisKey, $record->formatted); + } + } + + /** + * Write and cap the collection + * Writes the record to the redis list and caps its + */ + protected function writeCapped(LogRecord $record): void + { + if ($this->redisClient instanceof Redis) { + $mode = defined('Redis::MULTI') ? Redis::MULTI : 1; + $this->redisClient->multi($mode) + ->rPush($this->redisKey, $record->formatted) + ->ltrim($this->redisKey, -$this->capSize, -1) + ->exec(); + } else { + $redisKey = $this->redisKey; + $capSize = $this->capSize; + $this->redisClient->transaction(function ($tx) use ($record, $redisKey, $capSize) { + $tx->rpush($redisKey, $record->formatted); + $tx->ltrim($redisKey, -$capSize, -1); + }); + } + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter(); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php new file mode 100644 index 000000000000..fa8e9e9ffd33 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Level; +use Monolog\LogRecord; +use Predis\Client as Predis; +use Redis; + +/** + * Sends the message to a Redis Pub/Sub channel using PUBLISH + * + * usage example: + * + * $log = new Logger('application'); + * $redis = new RedisPubSubHandler(new Predis\Client("tcp://localhost:6379"), "logs", Level::Warning); + * $log->pushHandler($redis); + * + * @author Gaëtan Faugère + */ +class RedisPubSubHandler extends AbstractProcessingHandler +{ + /** @var Predis|Redis */ + private Predis|Redis $redisClient; + private string $channelKey; + + /** + * @param Predis|Redis $redis The redis instance + * @param string $key The channel key to publish records to + */ + public function __construct(Predis|Redis $redis, string $key, int|string|Level $level = Level::Debug, bool $bubble = true) + { + $this->redisClient = $redis; + $this->channelKey = $key; + + parent::__construct($level, $bubble); + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $this->redisClient->publish($this->channelKey, $record->formatted); + } + + /** + * @inheritDoc + */ + protected function getDefaultFormatter(): FormatterInterface + { + return new LineFormatter(); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RollbarHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RollbarHandler.php new file mode 100644 index 000000000000..1d124723b49d --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RollbarHandler.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Rollbar\RollbarLogger; +use Throwable; +use Monolog\LogRecord; + +/** + * Sends errors to Rollbar + * + * If the context data contains a `payload` key, that is used as an array + * of payload options to RollbarLogger's log method. + * + * Rollbar's context info will contain the context + extra keys from the log record + * merged, and then on top of that a few keys: + * + * - level (rollbar level name) + * - monolog_level (monolog level name, raw level, as rollbar only has 5 but monolog 8) + * - channel + * - datetime (unix timestamp) + * + * @author Paul Statezny + */ +class RollbarHandler extends AbstractProcessingHandler +{ + protected RollbarLogger $rollbarLogger; + + /** + * Records whether any log records have been added since the last flush of the rollbar notifier + */ + private bool $hasRecords = false; + + protected bool $initialized = false; + + /** + * @param RollbarLogger $rollbarLogger RollbarLogger object constructed with valid token + */ + public function __construct(RollbarLogger $rollbarLogger, int|string|Level $level = Level::Error, bool $bubble = true) + { + $this->rollbarLogger = $rollbarLogger; + + parent::__construct($level, $bubble); + } + + /** + * Translates Monolog log levels to Rollbar levels. + * + * @return 'debug'|'info'|'warning'|'error'|'critical' + */ + protected function toRollbarLevel(Level $level): string + { + return match ($level) { + Level::Debug => 'debug', + Level::Info => 'info', + Level::Notice => 'info', + Level::Warning => 'warning', + Level::Error => 'error', + Level::Critical => 'critical', + Level::Alert => 'critical', + Level::Emergency => 'critical', + }; + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + if (!$this->initialized) { + // __destructor() doesn't get called on Fatal errors + register_shutdown_function([$this, 'close']); + $this->initialized = true; + } + + $context = $record->context; + $context = array_merge($context, $record->extra, [ + 'level' => $this->toRollbarLevel($record->level), + 'monolog_level' => $record->level->getName(), + 'channel' => $record->channel, + 'datetime' => $record->datetime->format('U'), + ]); + + if (isset($context['exception']) && $context['exception'] instanceof Throwable) { + $exception = $context['exception']; + unset($context['exception']); + $toLog = $exception; + } else { + $toLog = $record->message; + } + + // @phpstan-ignore-next-line + $this->rollbarLogger->log($context['level'], $toLog, $context); + + $this->hasRecords = true; + } + + public function flush(): void + { + if ($this->hasRecords) { + $this->rollbarLogger->flush(); + $this->hasRecords = false; + } + } + + /** + * @inheritDoc + */ + public function close(): void + { + $this->flush(); + } + + /** + * @inheritDoc + */ + public function reset(): void + { + $this->flush(); + + parent::reset(); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php new file mode 100644 index 000000000000..75081db5f591 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php @@ -0,0 +1,214 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use InvalidArgumentException; +use Monolog\Level; +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * Stores logs to files that are rotated every day and a limited number of files are kept. + * + * This rotation is only intended to be used as a workaround. Using logrotate to + * handle the rotation is strongly encouraged when you can use it. + * + * @author Christophe Coevoet + * @author Jordi Boggiano + */ +class RotatingFileHandler extends StreamHandler +{ + public const FILE_PER_DAY = 'Y-m-d'; + public const FILE_PER_MONTH = 'Y-m'; + public const FILE_PER_YEAR = 'Y'; + + protected string $filename; + protected int $maxFiles; + protected bool|null $mustRotate = null; + protected \DateTimeImmutable $nextRotation; + protected string $filenameFormat; + protected string $dateFormat; + + /** + * @param int $maxFiles The maximal amount of files to keep (0 means unlimited) + * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) + * @param bool $useLocking Try to lock log file before doing any writes + */ + public function __construct(string $filename, int $maxFiles = 0, int|string|Level $level = Level::Debug, bool $bubble = true, ?int $filePermission = null, bool $useLocking = false, string $dateFormat = self::FILE_PER_DAY, string $filenameFormat = '{filename}-{date}') + { + $this->filename = Utils::canonicalizePath($filename); + $this->maxFiles = $maxFiles; + $this->setFilenameFormat($filenameFormat, $dateFormat); + $this->nextRotation = $this->getNextRotation(); + + parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking); + } + + /** + * @inheritDoc + */ + public function close(): void + { + parent::close(); + + if (true === $this->mustRotate) { + $this->rotate(); + } + } + + /** + * @inheritDoc + */ + public function reset(): void + { + parent::reset(); + + if (true === $this->mustRotate) { + $this->rotate(); + } + } + + public function setFilenameFormat(string $filenameFormat, string $dateFormat): self + { + $this->setDateFormat($dateFormat); + if (substr_count($filenameFormat, '{date}') === 0) { + throw new InvalidArgumentException( + 'Invalid filename format - format must contain at least `{date}`, because otherwise rotating is impossible.' + ); + } + $this->filenameFormat = $filenameFormat; + $this->url = $this->getTimedFilename(); + $this->close(); + + return $this; + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + // on the first record written, if the log is new, we should rotate (once per day) + if (null === $this->mustRotate) { + $this->mustRotate = null === $this->url || !file_exists($this->url); + } + + if ($this->nextRotation <= $record->datetime) { + $this->mustRotate = true; + $this->close(); + } + + parent::write($record); + } + + /** + * Rotates the files. + */ + protected function rotate(): void + { + // update filename + $this->url = $this->getTimedFilename(); + $this->nextRotation = $this->getNextRotation(); + + // skip GC of old logs if files are unlimited + if (0 === $this->maxFiles) { + return; + } + + $logFiles = glob($this->getGlobPattern()); + if (false === $logFiles) { + // failed to glob + return; + } + + if ($this->maxFiles >= count($logFiles)) { + // no files to remove + return; + } + + // Sorting the files by name to remove the older ones + usort($logFiles, function ($a, $b) { + return strcmp($b, $a); + }); + + foreach (array_slice($logFiles, $this->maxFiles) as $file) { + if (is_writable($file)) { + // suppress errors here as unlink() might fail if two processes + // are cleaning up/rotating at the same time + set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline): bool { + return false; + }); + unlink($file); + restore_error_handler(); + } + } + + $this->mustRotate = false; + } + + protected function getTimedFilename(): string + { + $fileInfo = pathinfo($this->filename); + $timedFilename = str_replace( + ['{filename}', '{date}'], + [$fileInfo['filename'], date($this->dateFormat)], + ($fileInfo['dirname'] ?? '') . '/' . $this->filenameFormat + ); + + if (isset($fileInfo['extension'])) { + $timedFilename .= '.'.$fileInfo['extension']; + } + + return $timedFilename; + } + + protected function getGlobPattern(): string + { + $fileInfo = pathinfo($this->filename); + $glob = str_replace( + ['{filename}', '{date}'], + [$fileInfo['filename'], str_replace( + ['Y', 'y', 'm', 'd'], + ['[0-9][0-9][0-9][0-9]', '[0-9][0-9]', '[0-9][0-9]', '[0-9][0-9]'], + $this->dateFormat) + ], + ($fileInfo['dirname'] ?? '') . '/' . $this->filenameFormat + ); + if (isset($fileInfo['extension'])) { + $glob .= '.'.$fileInfo['extension']; + } + + return $glob; + } + + protected function setDateFormat(string $dateFormat): void + { + if (0 === preg_match('{^[Yy](([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) { + throw new InvalidArgumentException( + 'Invalid date format - format must be one of '. + 'RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), RotatingFileHandler::FILE_PER_MONTH ("Y-m") '. + 'or RotatingFileHandler::FILE_PER_YEAR ("Y"), or you can set one of the '. + 'date formats using slashes, underscores and/or dots instead of dashes.' + ); + } + $this->dateFormat = $dateFormat; + } + + protected function getNextRotation(): \DateTimeImmutable + { + return match (str_replace(['/','_','.'], '-', $this->dateFormat)) { + self::FILE_PER_MONTH => (new \DateTimeImmutable('first day of next month'))->setTime(0, 0, 0), + self::FILE_PER_YEAR => (new \DateTimeImmutable('first day of January next year'))->setTime(0, 0, 0), + default => (new \DateTimeImmutable('tomorrow'))->setTime(0, 0, 0), + }; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SamplingHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SamplingHandler.php new file mode 100644 index 000000000000..511ec585421f --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SamplingHandler.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Closure; +use Monolog\Formatter\FormatterInterface; +use Monolog\LogRecord; + +/** + * Sampling handler + * + * A sampled event stream can be useful for logging high frequency events in + * a production environment where you only need an idea of what is happening + * and are not concerned with capturing every occurrence. Since the decision to + * handle or not handle a particular event is determined randomly, the + * resulting sampled log is not guaranteed to contain 1/N of the events that + * occurred in the application, but based on the Law of large numbers, it will + * tend to be close to this ratio with a large number of attempts. + * + * @author Bryan Davis + * @author Kunal Mehta + */ +class SamplingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface +{ + use ProcessableHandlerTrait; + + /** + * Handler or factory Closure($record, $this) + * + * @phpstan-var (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface + */ + protected Closure|HandlerInterface $handler; + + protected int $factor; + + /** + * @phpstan-param (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface $handler + * + * @param Closure|HandlerInterface $handler Handler or factory Closure($record|null, $samplingHandler). + * @param int $factor Sample factor (e.g. 10 means every ~10th record is sampled) + */ + public function __construct(Closure|HandlerInterface $handler, int $factor) + { + parent::__construct(); + $this->handler = $handler; + $this->factor = $factor; + } + + public function isHandling(LogRecord $record): bool + { + return $this->getHandler($record)->isHandling($record); + } + + public function handle(LogRecord $record): bool + { + if ($this->isHandling($record) && mt_rand(1, $this->factor) === 1) { + if (\count($this->processors) > 0) { + $record = $this->processRecord($record); + } + + $this->getHandler($record)->handle($record); + } + + return false === $this->bubble; + } + + /** + * Return the nested handler + * + * If the handler was provided as a factory, this will trigger the handler's instantiation. + */ + public function getHandler(LogRecord $record = null): HandlerInterface + { + if (!$this->handler instanceof HandlerInterface) { + $handler = ($this->handler)($record, $this); + if (!$handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory Closure should return a HandlerInterface"); + } + $this->handler = $handler; + } + + return $this->handler; + } + + /** + * @inheritDoc + */ + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + $handler->setFormatter($formatter); + + return $this; + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); + } + + /** + * @inheritDoc + */ + public function getFormatter(): FormatterInterface + { + $handler = $this->getHandler(); + if ($handler instanceof FormattableHandlerInterface) { + return $handler->getFormatter(); + } + + throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SendGridHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SendGridHandler.php new file mode 100644 index 000000000000..b8f574bb44cc --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SendGridHandler.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; + +/** + * SendGridrHandler uses the SendGrid API v2 function to send Log emails, more information in https://sendgrid.com/docs/API_Reference/Web_API/mail.html + * + * @author Ricardo Fontanelli + */ +class SendGridHandler extends MailHandler +{ + /** + * The SendGrid API User + */ + protected string $apiUser; + + /** + * The SendGrid API Key + */ + protected string $apiKey; + + /** + * The email addresses to which the message will be sent + */ + protected string $from; + + /** + * The email addresses to which the message will be sent + * @var string[] + */ + protected array $to; + + /** + * The subject of the email + */ + protected string $subject; + + /** + * @param string $apiUser The SendGrid API User + * @param string $apiKey The SendGrid API Key + * @param string $from The sender of the email + * @param string|string[] $to The recipients of the email + * @param string $subject The subject of the mail + * + * @throws MissingExtensionException If the curl extension is missing + */ + public function __construct(string $apiUser, string $apiKey, string $from, string|array $to, string $subject, int|string|Level $level = Level::Error, bool $bubble = true) + { + if (!extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the SendGridHandler'); + } + + parent::__construct($level, $bubble); + $this->apiUser = $apiUser; + $this->apiKey = $apiKey; + $this->from = $from; + $this->to = (array) $to; + $this->subject = $subject; + } + + /** + * @inheritDoc + */ + protected function send(string $content, array $records): void + { + $message = []; + $message['api_user'] = $this->apiUser; + $message['api_key'] = $this->apiKey; + $message['from'] = $this->from; + foreach ($this->to as $recipient) { + $message['to[]'] = $recipient; + } + $message['subject'] = $this->subject; + $message['date'] = date('r'); + + if ($this->isHtmlBody($content)) { + $message['html'] = $content; + } else { + $message['text'] = $content; + } + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, 'https://api.sendgrid.com/api/mail.send.json'); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($message)); + Curl\Util::execute($ch, 2); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php new file mode 100644 index 000000000000..7e9cccc9df4a --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php @@ -0,0 +1,367 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\Slack; + +use Monolog\Level; +use Monolog\Utils; +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\LogRecord; + +/** + * Slack record utility helping to log to Slack webhooks or API. + * + * @author Greg Kedzierski + * @author Haralan Dobrev + * @see https://api.slack.com/incoming-webhooks + * @see https://api.slack.com/docs/message-attachments + */ +class SlackRecord +{ + public const COLOR_DANGER = 'danger'; + + public const COLOR_WARNING = 'warning'; + + public const COLOR_GOOD = 'good'; + + public const COLOR_DEFAULT = '#e3e4e6'; + + /** + * Slack channel (encoded ID or name) + */ + private string|null $channel; + + /** + * Name of a bot + */ + private string|null $username; + + /** + * User icon e.g. 'ghost', 'http://example.com/user.png' + */ + private string|null $userIcon; + + /** + * Whether the message should be added to Slack as attachment (plain text otherwise) + */ + private bool $useAttachment; + + /** + * Whether the the context/extra messages added to Slack as attachments are in a short style + */ + private bool $useShortAttachment; + + /** + * Whether the attachment should include context and extra data + */ + private bool $includeContextAndExtra; + + /** + * Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + * @var string[] + */ + private array $excludeFields; + + private FormatterInterface|null $formatter; + + private NormalizerFormatter $normalizerFormatter; + + /** + * @param string[] $excludeFields + */ + public function __construct( + ?string $channel = null, + ?string $username = null, + bool $useAttachment = true, + ?string $userIcon = null, + bool $useShortAttachment = false, + bool $includeContextAndExtra = false, + array $excludeFields = [], + FormatterInterface $formatter = null + ) { + $this + ->setChannel($channel) + ->setUsername($username) + ->useAttachment($useAttachment) + ->setUserIcon($userIcon) + ->useShortAttachment($useShortAttachment) + ->includeContextAndExtra($includeContextAndExtra) + ->excludeFields($excludeFields) + ->setFormatter($formatter); + + if ($this->includeContextAndExtra) { + $this->normalizerFormatter = new NormalizerFormatter(); + } + } + + /** + * Returns required data in format that Slack + * is expecting. + * + * @phpstan-return mixed[] + */ + public function getSlackData(LogRecord $record): array + { + $dataArray = []; + + if ($this->username !== null) { + $dataArray['username'] = $this->username; + } + + if ($this->channel !== null) { + $dataArray['channel'] = $this->channel; + } + + if ($this->formatter !== null && !$this->useAttachment) { + $message = $this->formatter->format($record); + } else { + $message = $record->message; + } + + $recordData = $this->removeExcludedFields($record); + + if ($this->useAttachment) { + $attachment = [ + 'fallback' => $message, + 'text' => $message, + 'color' => $this->getAttachmentColor($record->level), + 'fields' => [], + 'mrkdwn_in' => ['fields'], + 'ts' => $recordData['datetime']->getTimestamp(), + 'footer' => $this->username, + 'footer_icon' => $this->userIcon, + ]; + + if ($this->useShortAttachment) { + $attachment['title'] = $recordData['level_name']; + } else { + $attachment['title'] = 'Message'; + $attachment['fields'][] = $this->generateAttachmentField('Level', $recordData['level_name']); + } + + if ($this->includeContextAndExtra) { + foreach (['extra', 'context'] as $key) { + if (!isset($recordData[$key]) || \count($recordData[$key]) === 0) { + continue; + } + + if ($this->useShortAttachment) { + $attachment['fields'][] = $this->generateAttachmentField( + $key, + $recordData[$key] + ); + } else { + // Add all extra fields as individual fields in attachment + $attachment['fields'] = array_merge( + $attachment['fields'], + $this->generateAttachmentFields($recordData[$key]) + ); + } + } + } + + $dataArray['attachments'] = [$attachment]; + } else { + $dataArray['text'] = $message; + } + + if ($this->userIcon !== null) { + if (false !== ($iconUrl = filter_var($this->userIcon, FILTER_VALIDATE_URL))) { + $dataArray['icon_url'] = $iconUrl; + } else { + $dataArray['icon_emoji'] = ":{$this->userIcon}:"; + } + } + + return $dataArray; + } + + /** + * Returns a Slack message attachment color associated with + * provided level. + */ + public function getAttachmentColor(Level $level): string + { + return match ($level) { + Level::Error, Level::Critical, Level::Alert, Level::Emergency => static::COLOR_DANGER, + Level::Warning => static::COLOR_WARNING, + Level::Info, Level::Notice => static::COLOR_GOOD, + Level::Debug => static::COLOR_DEFAULT + }; + } + + /** + * Stringifies an array of key/value pairs to be used in attachment fields + * + * @param mixed[] $fields + */ + public function stringify(array $fields): string + { + /** @var array $normalized */ + $normalized = $this->normalizerFormatter->normalizeValue($fields); + + $hasSecondDimension = \count(array_filter($normalized, 'is_array')) > 0; + $hasOnlyNonNumericKeys = \count(array_filter(array_keys($normalized), 'is_numeric')) === 0; + + return $hasSecondDimension || $hasOnlyNonNumericKeys + ? Utils::jsonEncode($normalized, JSON_PRETTY_PRINT|Utils::DEFAULT_JSON_FLAGS) + : Utils::jsonEncode($normalized, Utils::DEFAULT_JSON_FLAGS); + } + + /** + * Channel used by the bot when posting + * + * @param ?string $channel + * + * @return static + */ + public function setChannel(?string $channel = null): self + { + $this->channel = $channel; + + return $this; + } + + /** + * Username used by the bot when posting + * + * @param ?string $username + * + * @return static + */ + public function setUsername(?string $username = null): self + { + $this->username = $username; + + return $this; + } + + public function useAttachment(bool $useAttachment = true): self + { + $this->useAttachment = $useAttachment; + + return $this; + } + + public function setUserIcon(?string $userIcon = null): self + { + $this->userIcon = $userIcon; + + if (\is_string($userIcon)) { + $this->userIcon = trim($userIcon, ':'); + } + + return $this; + } + + public function useShortAttachment(bool $useShortAttachment = false): self + { + $this->useShortAttachment = $useShortAttachment; + + return $this; + } + + public function includeContextAndExtra(bool $includeContextAndExtra = false): self + { + $this->includeContextAndExtra = $includeContextAndExtra; + + if ($this->includeContextAndExtra) { + $this->normalizerFormatter = new NormalizerFormatter(); + } + + return $this; + } + + /** + * @param string[] $excludeFields + */ + public function excludeFields(array $excludeFields = []): self + { + $this->excludeFields = $excludeFields; + + return $this; + } + + public function setFormatter(?FormatterInterface $formatter = null): self + { + $this->formatter = $formatter; + + return $this; + } + + /** + * Generates attachment field + * + * @param string|mixed[] $value + * + * @return array{title: string, value: string, short: false} + */ + private function generateAttachmentField(string $title, $value): array + { + $value = is_array($value) + ? sprintf('```%s```', substr($this->stringify($value), 0, 1990)) + : $value; + + return [ + 'title' => ucfirst($title), + 'value' => $value, + 'short' => false, + ]; + } + + /** + * Generates a collection of attachment fields from array + * + * @param mixed[] $data + * + * @return array + */ + private function generateAttachmentFields(array $data): array + { + /** @var array $normalized */ + $normalized = $this->normalizerFormatter->normalizeValue($data); + + $fields = []; + foreach ($normalized as $key => $value) { + $fields[] = $this->generateAttachmentField((string) $key, $value); + } + + return $fields; + } + + /** + * Get a copy of record with fields excluded according to $this->excludeFields + * + * @return mixed[] + */ + private function removeExcludedFields(LogRecord $record): array + { + $recordData = $record->toArray(); + foreach ($this->excludeFields as $field) { + $keys = explode('.', $field); + $node = &$recordData; + $lastKey = end($keys); + foreach ($keys as $key) { + if (!isset($node[$key])) { + break; + } + if ($lastKey === $key) { + unset($node[$key]); + break; + } + $node = &$node[$key]; + } + } + + return $recordData; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SlackHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SlackHandler.php new file mode 100644 index 000000000000..321d8660faaf --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SlackHandler.php @@ -0,0 +1,250 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Level; +use Monolog\Utils; +use Monolog\Handler\Slack\SlackRecord; +use Monolog\LogRecord; + +/** + * Sends notifications through Slack API + * + * @author Greg Kedzierski + * @see https://api.slack.com/ + */ +class SlackHandler extends SocketHandler +{ + /** + * Slack API token + */ + private string $token; + + /** + * Instance of the SlackRecord util class preparing data for Slack API. + */ + private SlackRecord $slackRecord; + + /** + * @param string $token Slack API token + * @param string $channel Slack channel (encoded ID or name) + * @param string|null $username Name of a bot + * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) + * @param string|null $iconEmoji The emoji name to use (or null) + * @param bool $useShortAttachment Whether the context/extra messages added to Slack as attachments are in a short style + * @param bool $includeContextAndExtra Whether the attachment should include context and extra data + * @param string[] $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + * @throws MissingExtensionException If no OpenSSL PHP extension configured + */ + public function __construct( + string $token, + string $channel, + ?string $username = null, + bool $useAttachment = true, + ?string $iconEmoji = null, + $level = Level::Critical, + bool $bubble = true, + bool $useShortAttachment = false, + bool $includeContextAndExtra = false, + array $excludeFields = [], + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + if (!extension_loaded('openssl')) { + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the SlackHandler'); + } + + parent::__construct( + 'ssl://slack.com:443', + $level, + $bubble, + $persistent, + $timeout, + $writingTimeout, + $connectionTimeout, + $chunkSize + ); + + $this->slackRecord = new SlackRecord( + $channel, + $username, + $useAttachment, + $iconEmoji, + $useShortAttachment, + $includeContextAndExtra, + $excludeFields + ); + + $this->token = $token; + } + + public function getSlackRecord(): SlackRecord + { + return $this->slackRecord; + } + + public function getToken(): string + { + return $this->token; + } + + /** + * @inheritDoc + */ + protected function generateDataStream(LogRecord $record): string + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the body of API call + */ + private function buildContent(LogRecord $record): string + { + $dataArray = $this->prepareContentData($record); + + return http_build_query($dataArray); + } + + /** + * @return string[] + */ + protected function prepareContentData(LogRecord $record): array + { + $dataArray = $this->slackRecord->getSlackData($record); + $dataArray['token'] = $this->token; + + if (isset($dataArray['attachments']) && is_array($dataArray['attachments']) && \count($dataArray['attachments']) > 0) { + $dataArray['attachments'] = Utils::jsonEncode($dataArray['attachments']); + } + + return $dataArray; + } + + /** + * Builds the header of the API Call + */ + private function buildHeader(string $content): string + { + $header = "POST /api/chat.postMessage HTTP/1.1\r\n"; + $header .= "Host: slack.com\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + parent::write($record); + $this->finalizeWrite(); + } + + /** + * Finalizes the request by reading some bytes and then closing the socket + * + * If we do not read some but close the socket too early, slack sometimes + * drops the request entirely. + */ + protected function finalizeWrite(): void + { + $res = $this->getResource(); + if (is_resource($res)) { + @fread($res, 2048); + } + $this->closeSocket(); + } + + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + parent::setFormatter($formatter); + $this->slackRecord->setFormatter($formatter); + + return $this; + } + + public function getFormatter(): FormatterInterface + { + $formatter = parent::getFormatter(); + $this->slackRecord->setFormatter($formatter); + + return $formatter; + } + + /** + * Channel used by the bot when posting + */ + public function setChannel(string $channel): self + { + $this->slackRecord->setChannel($channel); + + return $this; + } + + /** + * Username used by the bot when posting + */ + public function setUsername(string $username): self + { + $this->slackRecord->setUsername($username); + + return $this; + } + + public function useAttachment(bool $useAttachment): self + { + $this->slackRecord->useAttachment($useAttachment); + + return $this; + } + + public function setIconEmoji(string $iconEmoji): self + { + $this->slackRecord->setUserIcon($iconEmoji); + + return $this; + } + + public function useShortAttachment(bool $useShortAttachment): self + { + $this->slackRecord->useShortAttachment($useShortAttachment); + + return $this; + } + + public function includeContextAndExtra(bool $includeContextAndExtra): self + { + $this->slackRecord->includeContextAndExtra($includeContextAndExtra); + + return $this; + } + + /** + * @param string[] $excludeFields + */ + public function excludeFields(array $excludeFields): self + { + $this->slackRecord->excludeFields($excludeFields); + + return $this; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php new file mode 100644 index 000000000000..6466ba3af238 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Level; +use Monolog\Utils; +use Monolog\Handler\Slack\SlackRecord; +use Monolog\LogRecord; + +/** + * Sends notifications through Slack Webhooks + * + * @author Haralan Dobrev + * @see https://api.slack.com/incoming-webhooks + */ +class SlackWebhookHandler extends AbstractProcessingHandler +{ + /** + * Slack Webhook token + */ + private string $webhookUrl; + + /** + * Instance of the SlackRecord util class preparing data for Slack API. + */ + private SlackRecord $slackRecord; + + /** + * @param string $webhookUrl Slack Webhook URL + * @param string|null $channel Slack channel (encoded ID or name) + * @param string|null $username Name of a bot + * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) + * @param string|null $iconEmoji The emoji name to use (or null) + * @param bool $useShortAttachment Whether the the context/extra messages added to Slack as attachments are in a short style + * @param bool $includeContextAndExtra Whether the attachment should include context and extra data + * @param string[] $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] + * + * @throws MissingExtensionException If the curl extension is missing + */ + public function __construct( + string $webhookUrl, + ?string $channel = null, + ?string $username = null, + bool $useAttachment = true, + ?string $iconEmoji = null, + bool $useShortAttachment = false, + bool $includeContextAndExtra = false, + $level = Level::Critical, + bool $bubble = true, + array $excludeFields = [] + ) { + if (!extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the SlackWebhookHandler'); + } + + parent::__construct($level, $bubble); + + $this->webhookUrl = $webhookUrl; + + $this->slackRecord = new SlackRecord( + $channel, + $username, + $useAttachment, + $iconEmoji, + $useShortAttachment, + $includeContextAndExtra, + $excludeFields + ); + } + + public function getSlackRecord(): SlackRecord + { + return $this->slackRecord; + } + + public function getWebhookUrl(): string + { + return $this->webhookUrl; + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $postData = $this->slackRecord->getSlackData($record); + $postString = Utils::jsonEncode($postData); + + $ch = curl_init(); + $options = [ + CURLOPT_URL => $this->webhookUrl, + CURLOPT_POST => true, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => ['Content-type: application/json'], + CURLOPT_POSTFIELDS => $postString, + ]; + if (defined('CURLOPT_SAFE_UPLOAD')) { + $options[CURLOPT_SAFE_UPLOAD] = true; + } + + curl_setopt_array($ch, $options); + + Curl\Util::execute($ch); + } + + public function setFormatter(FormatterInterface $formatter): HandlerInterface + { + parent::setFormatter($formatter); + $this->slackRecord->setFormatter($formatter); + + return $this; + } + + public function getFormatter(): FormatterInterface + { + $formatter = parent::getFormatter(); + $this->slackRecord->setFormatter($formatter); + + return $formatter; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SocketHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SocketHandler.php new file mode 100644 index 000000000000..c5f70888476a --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SocketHandler.php @@ -0,0 +1,429 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\LogRecord; + +/** + * Stores to any socket - uses fsockopen() or pfsockopen(). + * + * @author Pablo de Leon Belloc + * @see http://php.net/manual/en/function.fsockopen.php + */ +class SocketHandler extends AbstractProcessingHandler +{ + private string $connectionString; + private float $connectionTimeout; + /** @var resource|null */ + private $resource; + private float $timeout; + private float $writingTimeout; + private int|null $lastSentBytes = null; + private int|null $chunkSize; + private bool $persistent; + private int|null $errno = null; + private string|null $errstr = null; + private float|null $lastWritingAt = null; + + /** + * @param string $connectionString Socket connection string + * @param bool $persistent Flag to enable/disable persistent connections + * @param float $timeout Socket timeout to wait until the request is being aborted + * @param float $writingTimeout Socket timeout to wait until the request should've been sent/written + * @param float|null $connectionTimeout Socket connect timeout to wait until the connection should've been + * established + * @param int|null $chunkSize Sets the chunk size. Only has effect during connection in the writing cycle + * + * @throws \InvalidArgumentException If an invalid timeout value (less than 0) is passed. + */ + public function __construct( + string $connectionString, + $level = Level::Debug, + bool $bubble = true, + bool $persistent = false, + float $timeout = 0.0, + float $writingTimeout = 10.0, + ?float $connectionTimeout = null, + ?int $chunkSize = null + ) { + parent::__construct($level, $bubble); + $this->connectionString = $connectionString; + + if ($connectionTimeout !== null) { + $this->validateTimeout($connectionTimeout); + } + + $this->connectionTimeout = $connectionTimeout ?? (float) ini_get('default_socket_timeout'); + $this->persistent = $persistent; + $this->validateTimeout($timeout); + $this->timeout = $timeout; + $this->validateTimeout($writingTimeout); + $this->writingTimeout = $writingTimeout; + $this->chunkSize = $chunkSize; + } + + /** + * Connect (if necessary) and write to the socket + * + * @inheritDoc + * + * @throws \UnexpectedValueException + * @throws \RuntimeException + */ + protected function write(LogRecord $record): void + { + $this->connectIfNotConnected(); + $data = $this->generateDataStream($record); + $this->writeToSocket($data); + } + + /** + * We will not close a PersistentSocket instance so it can be reused in other requests. + */ + public function close(): void + { + if (!$this->isPersistent()) { + $this->closeSocket(); + } + } + + /** + * Close socket, if open + */ + public function closeSocket(): void + { + if (is_resource($this->resource)) { + fclose($this->resource); + $this->resource = null; + } + } + + /** + * Set socket connection to be persistent. It only has effect before the connection is initiated. + */ + public function setPersistent(bool $persistent): self + { + $this->persistent = $persistent; + + return $this; + } + + /** + * Set connection timeout. Only has effect before we connect. + * + * @see http://php.net/manual/en/function.fsockopen.php + */ + public function setConnectionTimeout(float $seconds): self + { + $this->validateTimeout($seconds); + $this->connectionTimeout = $seconds; + + return $this; + } + + /** + * Set write timeout. Only has effect before we connect. + * + * @see http://php.net/manual/en/function.stream-set-timeout.php + */ + public function setTimeout(float $seconds): self + { + $this->validateTimeout($seconds); + $this->timeout = $seconds; + + return $this; + } + + /** + * Set writing timeout. Only has effect during connection in the writing cycle. + * + * @param float $seconds 0 for no timeout + */ + public function setWritingTimeout(float $seconds): self + { + $this->validateTimeout($seconds); + $this->writingTimeout = $seconds; + + return $this; + } + + /** + * Set chunk size. Only has effect during connection in the writing cycle. + */ + public function setChunkSize(int $bytes): self + { + $this->chunkSize = $bytes; + + return $this; + } + + /** + * Get current connection string + */ + public function getConnectionString(): string + { + return $this->connectionString; + } + + /** + * Get persistent setting + */ + public function isPersistent(): bool + { + return $this->persistent; + } + + /** + * Get current connection timeout setting + */ + public function getConnectionTimeout(): float + { + return $this->connectionTimeout; + } + + /** + * Get current in-transfer timeout + */ + public function getTimeout(): float + { + return $this->timeout; + } + + /** + * Get current local writing timeout + */ + public function getWritingTimeout(): float + { + return $this->writingTimeout; + } + + /** + * Get current chunk size + */ + public function getChunkSize(): ?int + { + return $this->chunkSize; + } + + /** + * Check to see if the socket is currently available. + * + * UDP might appear to be connected but might fail when writing. See http://php.net/fsockopen for details. + */ + public function isConnected(): bool + { + return is_resource($this->resource) + && !feof($this->resource); // on TCP - other party can close connection. + } + + /** + * Wrapper to allow mocking + * + * @return resource|false + */ + protected function pfsockopen() + { + return @pfsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); + } + + /** + * Wrapper to allow mocking + * + * @return resource|false + */ + protected function fsockopen() + { + return @fsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); + } + + /** + * Wrapper to allow mocking + * + * @see http://php.net/manual/en/function.stream-set-timeout.php + */ + protected function streamSetTimeout(): bool + { + $seconds = floor($this->timeout); + $microseconds = round(($this->timeout - $seconds) * 1e6); + + if (!is_resource($this->resource)) { + throw new \LogicException('streamSetTimeout called but $this->resource is not a resource'); + } + + return stream_set_timeout($this->resource, (int) $seconds, (int) $microseconds); + } + + /** + * Wrapper to allow mocking + * + * @see http://php.net/manual/en/function.stream-set-chunk-size.php + * + * @return int|false + */ + protected function streamSetChunkSize(): int|bool + { + if (!is_resource($this->resource)) { + throw new \LogicException('streamSetChunkSize called but $this->resource is not a resource'); + } + + if (null === $this->chunkSize) { + throw new \LogicException('streamSetChunkSize called but $this->chunkSize is not set'); + } + + return stream_set_chunk_size($this->resource, $this->chunkSize); + } + + /** + * Wrapper to allow mocking + * + * @return int|false + */ + protected function fwrite(string $data): int|bool + { + if (!is_resource($this->resource)) { + throw new \LogicException('fwrite called but $this->resource is not a resource'); + } + + return @fwrite($this->resource, $data); + } + + /** + * Wrapper to allow mocking + * + * @return mixed[]|bool + */ + protected function streamGetMetadata(): array|bool + { + if (!is_resource($this->resource)) { + throw new \LogicException('streamGetMetadata called but $this->resource is not a resource'); + } + + return stream_get_meta_data($this->resource); + } + + private function validateTimeout(float $value): void + { + if ($value < 0) { + throw new \InvalidArgumentException("Timeout must be 0 or a positive float (got $value)"); + } + } + + private function connectIfNotConnected(): void + { + if ($this->isConnected()) { + return; + } + $this->connect(); + } + + protected function generateDataStream(LogRecord $record): string + { + return (string) $record->formatted; + } + + /** + * @return resource|null + */ + protected function getResource() + { + return $this->resource; + } + + private function connect(): void + { + $this->createSocketResource(); + $this->setSocketTimeout(); + $this->setStreamChunkSize(); + } + + private function createSocketResource(): void + { + if ($this->isPersistent()) { + $resource = $this->pfsockopen(); + } else { + $resource = $this->fsockopen(); + } + if (is_bool($resource)) { + throw new \UnexpectedValueException("Failed connecting to $this->connectionString ($this->errno: $this->errstr)"); + } + $this->resource = $resource; + } + + private function setSocketTimeout(): void + { + if (!$this->streamSetTimeout()) { + throw new \UnexpectedValueException("Failed setting timeout with stream_set_timeout()"); + } + } + + private function setStreamChunkSize(): void + { + if (null !== $this->chunkSize && false === $this->streamSetChunkSize()) { + throw new \UnexpectedValueException("Failed setting chunk size with stream_set_chunk_size()"); + } + } + + private function writeToSocket(string $data): void + { + $length = strlen($data); + $sent = 0; + $this->lastSentBytes = $sent; + while ($this->isConnected() && $sent < $length) { + if (0 == $sent) { + $chunk = $this->fwrite($data); + } else { + $chunk = $this->fwrite(substr($data, $sent)); + } + if ($chunk === false) { + throw new \RuntimeException("Could not write to socket"); + } + $sent += $chunk; + $socketInfo = $this->streamGetMetadata(); + if (is_array($socketInfo) && (bool) $socketInfo['timed_out']) { + throw new \RuntimeException("Write timed-out"); + } + + if ($this->writingIsTimedOut($sent)) { + throw new \RuntimeException("Write timed-out, no data sent for `{$this->writingTimeout}` seconds, probably we got disconnected (sent $sent of $length)"); + } + } + if (!$this->isConnected() && $sent < $length) { + throw new \RuntimeException("End-of-file reached, probably we got disconnected (sent $sent of $length)"); + } + } + + private function writingIsTimedOut(int $sent): bool + { + // convert to ms + if (0.0 == $this->writingTimeout) { + return false; + } + + if ($sent !== $this->lastSentBytes) { + $this->lastWritingAt = microtime(true); + $this->lastSentBytes = $sent; + + return false; + } else { + usleep(100); + } + + if ((microtime(true) - (float) $this->lastWritingAt) >= $this->writingTimeout) { + $this->closeSocket(); + + return true; + } + + return false; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SqsHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SqsHandler.php new file mode 100644 index 000000000000..b4512a601b0c --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SqsHandler.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Aws\Sqs\SqsClient; +use Monolog\Level; +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * Writes to any sqs queue. + * + * @author Martijn van Calker + */ +class SqsHandler extends AbstractProcessingHandler +{ + /** 256 KB in bytes - maximum message size in SQS */ + protected const MAX_MESSAGE_SIZE = 262144; + /** 100 KB in bytes - head message size for new error log */ + protected const HEAD_MESSAGE_SIZE = 102400; + + private SqsClient $client; + private string $queueUrl; + + public function __construct(SqsClient $sqsClient, string $queueUrl, int|string|Level $level = Level::Debug, bool $bubble = true) + { + parent::__construct($level, $bubble); + + $this->client = $sqsClient; + $this->queueUrl = $queueUrl; + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + if (!isset($record->formatted) || 'string' !== gettype($record->formatted)) { + throw new \InvalidArgumentException('SqsHandler accepts only formatted records as a string' . Utils::getRecordMessageForException($record)); + } + + $messageBody = $record->formatted; + if (strlen($messageBody) >= static::MAX_MESSAGE_SIZE) { + $messageBody = Utils::substr($messageBody, 0, static::HEAD_MESSAGE_SIZE); + } + + $this->client->sendMessage([ + 'QueueUrl' => $this->queueUrl, + 'MessageBody' => $messageBody, + ]); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/StreamHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/StreamHandler.php new file mode 100644 index 000000000000..027a7217d503 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/StreamHandler.php @@ -0,0 +1,202 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * Stores to any stream resource + * + * Can be used to store into php://stderr, remote and local files, etc. + * + * @author Jordi Boggiano + */ +class StreamHandler extends AbstractProcessingHandler +{ + protected const MAX_CHUNK_SIZE = 2147483647; + /** 10MB */ + protected const DEFAULT_CHUNK_SIZE = 10 * 1024 * 1024; + protected int $streamChunkSize; + /** @var resource|null */ + protected $stream; + protected string|null $url = null; + private string|null $errorMessage = null; + protected int|null $filePermission; + protected bool $useLocking; + /** @var true|null */ + private bool|null $dirCreated = null; + + /** + * @param resource|string $stream If a missing path can't be created, an UnexpectedValueException will be thrown on first write + * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) + * @param bool $useLocking Try to lock log file before doing any writes + * + * @throws \InvalidArgumentException If stream is not a resource or string + */ + public function __construct($stream, int|string|Level $level = Level::Debug, bool $bubble = true, ?int $filePermission = null, bool $useLocking = false) + { + parent::__construct($level, $bubble); + + if (($phpMemoryLimit = Utils::expandIniShorthandBytes(ini_get('memory_limit'))) !== false) { + if ($phpMemoryLimit > 0) { + // use max 10% of allowed memory for the chunk size, and at least 100KB + $this->streamChunkSize = min(static::MAX_CHUNK_SIZE, max((int) ($phpMemoryLimit / 10), 100 * 1024)); + } else { + // memory is unlimited, set to the default 10MB + $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE; + } + } else { + // no memory limit information, set to the default 10MB + $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE; + } + + if (is_resource($stream)) { + $this->stream = $stream; + + stream_set_chunk_size($this->stream, $this->streamChunkSize); + } elseif (is_string($stream)) { + $this->url = Utils::canonicalizePath($stream); + } else { + throw new \InvalidArgumentException('A stream must either be a resource or a string.'); + } + + $this->filePermission = $filePermission; + $this->useLocking = $useLocking; + } + + /** + * @inheritDoc + */ + public function close(): void + { + if (null !== $this->url && is_resource($this->stream)) { + fclose($this->stream); + } + $this->stream = null; + $this->dirCreated = null; + } + + /** + * Return the currently active stream if it is open + * + * @return resource|null + */ + public function getStream() + { + return $this->stream; + } + + /** + * Return the stream URL if it was configured with a URL and not an active resource + */ + public function getUrl(): ?string + { + return $this->url; + } + + public function getStreamChunkSize(): int + { + return $this->streamChunkSize; + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + if (!is_resource($this->stream)) { + $url = $this->url; + if (null === $url || '' === $url) { + throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().' . Utils::getRecordMessageForException($record)); + } + $this->createDir($url); + $this->errorMessage = null; + set_error_handler([$this, 'customErrorHandler']); + $stream = fopen($url, 'a'); + if ($this->filePermission !== null) { + @chmod($url, $this->filePermission); + } + restore_error_handler(); + if (!is_resource($stream)) { + $this->stream = null; + + throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened in append mode: '.$this->errorMessage, $url) . Utils::getRecordMessageForException($record)); + } + stream_set_chunk_size($stream, $this->streamChunkSize); + $this->stream = $stream; + } + + $stream = $this->stream; + if ($this->useLocking) { + // ignoring errors here, there's not much we can do about them + flock($stream, LOCK_EX); + } + + $this->streamWrite($stream, $record); + + if ($this->useLocking) { + flock($stream, LOCK_UN); + } + } + + /** + * Write to stream + * @param resource $stream + */ + protected function streamWrite($stream, LogRecord $record): void + { + fwrite($stream, (string) $record->formatted); + } + + private function customErrorHandler(int $code, string $msg): bool + { + $this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg); + + return true; + } + + private function getDirFromStream(string $stream): ?string + { + $pos = strpos($stream, '://'); + if ($pos === false) { + return dirname($stream); + } + + if ('file://' === substr($stream, 0, 7)) { + return dirname(substr($stream, 7)); + } + + return null; + } + + private function createDir(string $url): void + { + // Do not try to create dir if it has already been tried. + if (true === $this->dirCreated) { + return; + } + + $dir = $this->getDirFromStream($url); + if (null !== $dir && !is_dir($dir)) { + $this->errorMessage = null; + set_error_handler([$this, 'customErrorHandler']); + $status = mkdir($dir, 0777, true); + restore_error_handler(); + if (false === $status && !is_dir($dir) && strpos((string) $this->errorMessage, 'File exists') === false) { + throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and it could not be created: '.$this->errorMessage, $dir)); + } + } + $this->dirCreated = true; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php new file mode 100644 index 000000000000..842b6577fa48 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Closure; +use Monolog\Level; +use Monolog\Logger; +use Monolog\LogRecord; +use Monolog\Utils; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; +use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Mailer\Transport\TransportInterface; +use Symfony\Component\Mime\Email; + +/** + * SymfonyMailerHandler uses Symfony's Mailer component to send the emails + * + * @author Jordi Boggiano + */ +class SymfonyMailerHandler extends MailHandler +{ + protected MailerInterface|TransportInterface $mailer; + /** @var Email|Closure(string, LogRecord[]): Email */ + private Email|Closure $emailTemplate; + + /** + * @phpstan-param Email|Closure(string, LogRecord[]): Email $email + * + * @param MailerInterface|TransportInterface $mailer The mailer to use + * @param Closure|Email $email An email template, the subject/body will be replaced + */ + public function __construct($mailer, Email|Closure $email, int|string|Level $level = Level::Error, bool $bubble = true) + { + parent::__construct($level, $bubble); + + $this->mailer = $mailer; + $this->emailTemplate = $email; + } + + /** + * {@inheritDoc} + */ + protected function send(string $content, array $records): void + { + $this->mailer->send($this->buildMessage($content, $records)); + } + + /** + * Gets the formatter for the Swift_Message subject. + * + * @param string|null $format The format of the subject + */ + protected function getSubjectFormatter(?string $format): FormatterInterface + { + return new LineFormatter($format); + } + + /** + * Creates instance of Email to be sent + * + * @param string $content formatted email body to be sent + * @param LogRecord[] $records Log records that formed the content + */ + protected function buildMessage(string $content, array $records): Email + { + $message = null; + if ($this->emailTemplate instanceof Email) { + $message = clone $this->emailTemplate; + } elseif (is_callable($this->emailTemplate)) { + $message = ($this->emailTemplate)($content, $records); + } + + if (!$message instanceof Email) { + $record = reset($records); + throw new \InvalidArgumentException('Could not resolve message as instance of Email or a callable returning it' . ($record instanceof LogRecord ? Utils::getRecordMessageForException($record) : '')); + } + + if (\count($records) > 0) { + $subjectFormatter = $this->getSubjectFormatter($message->getSubject()); + $message->subject($subjectFormatter->format($this->getHighestRecord($records))); + } + + if ($this->isHtmlBody($content)) { + if (null !== ($charset = $message->getHtmlCharset())) { + $message->html($content, $charset); + } else { + $message->html($content); + } + } else { + if (null !== ($charset = $message->getTextCharset())) { + $message->text($content, $charset); + } else { + $message->text($content); + } + } + + return $message->date(new \DateTimeImmutable()); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SyslogHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SyslogHandler.php new file mode 100644 index 000000000000..99507a170ec3 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SyslogHandler.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * Logs to syslog service. + * + * usage example: + * + * $log = new Logger('application'); + * $syslog = new SyslogHandler('myfacility', 'local6'); + * $formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%"); + * $syslog->setFormatter($formatter); + * $log->pushHandler($syslog); + * + * @author Sven Paulus + */ +class SyslogHandler extends AbstractSyslogHandler +{ + protected string $ident; + protected int $logopts; + + /** + * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant + * @param int $logopts Option flags for the openlog() call, defaults to LOG_PID + */ + public function __construct(string $ident, string|int $facility = LOG_USER, int|string|Level $level = Level::Debug, bool $bubble = true, int $logopts = LOG_PID) + { + parent::__construct($facility, $level, $bubble); + + $this->ident = $ident; + $this->logopts = $logopts; + } + + /** + * @inheritDoc + */ + public function close(): void + { + closelog(); + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + openlog($this->ident, $this->logopts, $this->facility); + syslog($this->toSyslogPriority($record->level), (string) $record->formatted); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php new file mode 100644 index 000000000000..6a4833450be1 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\SyslogUdp; + +use Monolog\Utils; +use Socket; + +class UdpSocket +{ + protected const DATAGRAM_MAX_LENGTH = 65023; + + protected string $ip; + protected int $port; + protected ?Socket $socket = null; + + public function __construct(string $ip, int $port = 514) + { + $this->ip = $ip; + $this->port = $port; + } + + public function write(string $line, string $header = ""): void + { + $this->send($this->assembleMessage($line, $header)); + } + + public function close(): void + { + if ($this->socket instanceof Socket) { + socket_close($this->socket); + $this->socket = null; + } + } + + protected function getSocket(): Socket + { + if (null !== $this->socket) { + return $this->socket; + } + + $domain = AF_INET; + $protocol = SOL_UDP; + // Check if we are using unix sockets. + if ($this->port === 0) { + $domain = AF_UNIX; + $protocol = IPPROTO_IP; + } + + $socket = socket_create($domain, SOCK_DGRAM, $protocol); + if ($socket instanceof Socket) { + return $this->socket = $socket; + } + + throw new \RuntimeException('The UdpSocket to '.$this->ip.':'.$this->port.' could not be opened via socket_create'); + } + + protected function send(string $chunk): void + { + socket_sendto($this->getSocket(), $chunk, strlen($chunk), $flags = 0, $this->ip, $this->port); + } + + protected function assembleMessage(string $line, string $header): string + { + $chunkSize = static::DATAGRAM_MAX_LENGTH - strlen($header); + + return $header . Utils::substr($line, 0, $chunkSize); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php new file mode 100644 index 000000000000..16a7286a1996 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use DateTimeInterface; +use Monolog\Handler\SyslogUdp\UdpSocket; +use Monolog\Level; +use Monolog\LogRecord; +use Monolog\Utils; + +/** + * A Handler for logging to a remote syslogd server. + * + * @author Jesper Skovgaard Nielsen + * @author Dominik Kukacka + */ +class SyslogUdpHandler extends AbstractSyslogHandler +{ + const RFC3164 = 0; + const RFC5424 = 1; + const RFC5424e = 2; + + /** @var array */ + private array $dateFormats = [ + self::RFC3164 => 'M d H:i:s', + self::RFC5424 => \DateTime::RFC3339, + self::RFC5424e => \DateTime::RFC3339_EXTENDED, + ]; + + protected UdpSocket $socket; + protected string $ident; + /** @var self::RFC* */ + protected int $rfc; + + /** + * @param string $host Either IP/hostname or a path to a unix socket (port must be 0 then) + * @param int $port Port number, or 0 if $host is a unix socket + * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param string $ident Program name or tag for each log message. + * @param int $rfc RFC to format the message for. + * @throws MissingExtensionException when there is no socket extension + * + * @phpstan-param self::RFC* $rfc + */ + public function __construct(string $host, int $port = 514, string|int $facility = LOG_USER, int|string|Level $level = Level::Debug, bool $bubble = true, string $ident = 'php', int $rfc = self::RFC5424) + { + if (!extension_loaded('sockets')) { + throw new MissingExtensionException('The sockets extension is required to use the SyslogUdpHandler'); + } + + parent::__construct($facility, $level, $bubble); + + $this->ident = $ident; + $this->rfc = $rfc; + + $this->socket = new UdpSocket($host, $port); + } + + protected function write(LogRecord $record): void + { + $lines = $this->splitMessageIntoLines($record->formatted); + + $header = $this->makeCommonSyslogHeader($this->toSyslogPriority($record->level), $record->datetime); + + foreach ($lines as $line) { + $this->socket->write($line, $header); + } + } + + public function close(): void + { + $this->socket->close(); + } + + /** + * @param string|string[] $message + * @return string[] + */ + private function splitMessageIntoLines($message): array + { + if (is_array($message)) { + $message = implode("\n", $message); + } + + $lines = preg_split('/$\R?^/m', (string) $message, -1, PREG_SPLIT_NO_EMPTY); + if (false === $lines) { + $pcreErrorCode = preg_last_error(); + + throw new \RuntimeException('Could not preg_split: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode)); + } + + return $lines; + } + + /** + * Make common syslog header (see rfc5424 or rfc3164) + */ + protected function makeCommonSyslogHeader(int $severity, DateTimeInterface $datetime): string + { + $priority = $severity + $this->facility; + + $pid = getmypid(); + if (false === $pid) { + $pid = '-'; + } + + $hostname = gethostname(); + if (false === $hostname) { + $hostname = '-'; + } + + if ($this->rfc === self::RFC3164) { + // see https://github.com/phpstan/phpstan/issues/5348 + // @phpstan-ignore-next-line + $dateNew = $datetime->setTimezone(new \DateTimeZone('UTC')); + $date = $dateNew->format($this->dateFormats[$this->rfc]); + + return "<$priority>" . + $date . " " . + $hostname . " " . + $this->ident . "[" . $pid . "]: "; + } + + $date = $datetime->format($this->dateFormats[$this->rfc]); + + return "<$priority>1 " . + $date . " " . + $hostname . " " . + $this->ident . " " . + $pid . " - - "; + } + + /** + * Inject your own socket, mainly used for testing + */ + public function setSocket(UdpSocket $socket): self + { + $this->socket = $socket; + + return $this; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php new file mode 100644 index 000000000000..4302a03a9bf4 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php @@ -0,0 +1,279 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use RuntimeException; +use Monolog\Level; +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * Handler send logs to Telegram using Telegram Bot API. + * + * How to use: + * 1) Create telegram bot with https://telegram.me/BotFather + * 2) Create a telegram channel where logs will be recorded. + * 3) Add created bot from step 1 to the created channel from step 2. + * + * Use telegram bot API key from step 1 and channel name with '@' prefix from step 2 to create instance of TelegramBotHandler + * + * @link https://core.telegram.org/bots/api + * + * @author Mazur Alexandr + */ +class TelegramBotHandler extends AbstractProcessingHandler +{ + private const BOT_API = 'https://api.telegram.org/bot'; + + /** + * The available values of parseMode according to the Telegram api documentation + */ + private const AVAILABLE_PARSE_MODES = [ + 'HTML', + 'MarkdownV2', + 'Markdown', // legacy mode without underline and strikethrough, use MarkdownV2 instead + ]; + + /** + * The maximum number of characters allowed in a message according to the Telegram api documentation + */ + private const MAX_MESSAGE_LENGTH = 4096; + + /** + * Telegram bot access token provided by BotFather. + * Create telegram bot with https://telegram.me/BotFather and use access token from it. + */ + private string $apiKey; + + /** + * Telegram channel name. + * Since to start with '@' symbol as prefix. + */ + private string $channel; + + /** + * The kind of formatting that is used for the message. + * See available options at https://core.telegram.org/bots/api#formatting-options + * or in AVAILABLE_PARSE_MODES + */ + private string|null $parseMode; + + /** + * Disables link previews for links in the message. + */ + private bool|null $disableWebPagePreview; + + /** + * Sends the message silently. Users will receive a notification with no sound. + */ + private bool|null $disableNotification; + + /** + * True - split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages. + * False - truncates a message that is too long. + */ + private bool $splitLongMessages; + + /** + * Adds 1-second delay between sending a split message (according to Telegram API to avoid 429 Too Many Requests). + */ + private bool $delayBetweenMessages; + + /** + * Telegram message thread id, unique identifier for the target message thread (topic) of the forum; for forum supergroups only + * See how to get the `message_thread_id` https://stackoverflow.com/a/75178418 + */ + private int|null $topic; + + /** + * @param string $apiKey Telegram bot access token provided by BotFather + * @param string $channel Telegram channel name + * @param bool $splitLongMessages Split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages + * @param bool $delayBetweenMessages Adds delay between sending a split message according to Telegram API + * @param int $topic Telegram message thread id, unique identifier for the target message thread (topic) of the forum + * @throws MissingExtensionException If the curl extension is missing + */ + public function __construct( + string $apiKey, + string $channel, + $level = Level::Debug, + bool $bubble = true, + string $parseMode = null, + bool $disableWebPagePreview = null, + bool $disableNotification = null, + bool $splitLongMessages = false, + bool $delayBetweenMessages = false, + int $topic = null + ) { + if (!extension_loaded('curl')) { + throw new MissingExtensionException('The curl extension is needed to use the TelegramBotHandler'); + } + + parent::__construct($level, $bubble); + + $this->apiKey = $apiKey; + $this->channel = $channel; + $this->setParseMode($parseMode); + $this->disableWebPagePreview($disableWebPagePreview); + $this->disableNotification($disableNotification); + $this->splitLongMessages($splitLongMessages); + $this->delayBetweenMessages($delayBetweenMessages); + $this->setTopic($topic); + } + + public function setParseMode(string $parseMode = null): self + { + if ($parseMode !== null && !in_array($parseMode, self::AVAILABLE_PARSE_MODES, true)) { + throw new \InvalidArgumentException('Unknown parseMode, use one of these: ' . implode(', ', self::AVAILABLE_PARSE_MODES) . '.'); + } + + $this->parseMode = $parseMode; + + return $this; + } + + public function disableWebPagePreview(bool $disableWebPagePreview = null): self + { + $this->disableWebPagePreview = $disableWebPagePreview; + + return $this; + } + + public function disableNotification(bool $disableNotification = null): self + { + $this->disableNotification = $disableNotification; + + return $this; + } + + /** + * True - split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages. + * False - truncates a message that is too long. + * @return $this + */ + public function splitLongMessages(bool $splitLongMessages = false): self + { + $this->splitLongMessages = $splitLongMessages; + + return $this; + } + + /** + * Adds 1-second delay between sending a split message (according to Telegram API to avoid 429 Too Many Requests). + * @return $this + */ + public function delayBetweenMessages(bool $delayBetweenMessages = false): self + { + $this->delayBetweenMessages = $delayBetweenMessages; + + return $this; + } + + public function setTopic(int $topic = null): self + { + $this->topic = $topic; + + return $this; + } + + /** + * @inheritDoc + */ + public function handleBatch(array $records): void + { + $messages = []; + + foreach ($records as $record) { + if (!$this->isHandling($record)) { + continue; + } + + if (\count($this->processors) > 0) { + $record = $this->processRecord($record); + } + + $messages[] = $record; + } + + if (\count($messages) > 0) { + $this->send((string) $this->getFormatter()->formatBatch($messages)); + } + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $this->send($record->formatted); + } + + /** + * Send request to @link https://api.telegram.org/bot on SendMessage action. + */ + protected function send(string $message): void + { + $messages = $this->handleMessageLength($message); + + foreach ($messages as $key => $msg) { + if ($this->delayBetweenMessages && $key > 0) { + sleep(1); + } + + $this->sendCurl($msg); + } + } + + protected function sendCurl(string $message): void + { + $ch = curl_init(); + $url = self::BOT_API . $this->apiKey . '/SendMessage'; + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + $params = [ + 'text' => $message, + 'chat_id' => $this->channel, + 'parse_mode' => $this->parseMode, + 'disable_web_page_preview' => $this->disableWebPagePreview, + 'disable_notification' => $this->disableNotification, + ]; + if ($this->topic !== null) { + $params['message_thread_id'] = $this->topic; + } + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params)); + + $result = Curl\Util::execute($ch); + if (!is_string($result)) { + throw new RuntimeException('Telegram API error. Description: No response'); + } + $result = json_decode($result, true); + + if ($result['ok'] === false) { + throw new RuntimeException('Telegram API error. Description: ' . $result['description']); + } + } + + /** + * Handle a message that is too long: truncates or splits into several + * @return string[] + */ + private function handleMessageLength(string $message): array + { + $truncatedMarker = ' (...truncated)'; + if (!$this->splitLongMessages && strlen($message) > self::MAX_MESSAGE_LENGTH) { + return [Utils::substr($message, 0, self::MAX_MESSAGE_LENGTH - strlen($truncatedMarker)) . $truncatedMarker]; + } + + return str_split($message, self::MAX_MESSAGE_LENGTH); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/TestHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/TestHandler.php new file mode 100644 index 000000000000..8e356ef31c02 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/TestHandler.php @@ -0,0 +1,195 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Level; +use Monolog\Logger; +use Psr\Log\LogLevel; +use Monolog\LogRecord; + +/** + * Used for testing purposes. + * + * It records all records and gives you access to them for verification. + * + * @author Jordi Boggiano + * + * @method bool hasEmergency(string|array $recordAssertions) + * @method bool hasAlert(string|array $recordAssertions) + * @method bool hasCritical(string|array $recordAssertions) + * @method bool hasError(string|array $recordAssertions) + * @method bool hasWarning(string|array $recordAssertions) + * @method bool hasNotice(string|array $recordAssertions) + * @method bool hasInfo(string|array $recordAssertions) + * @method bool hasDebug(string|array $recordAssertions) + * + * @method bool hasEmergencyRecords() + * @method bool hasAlertRecords() + * @method bool hasCriticalRecords() + * @method bool hasErrorRecords() + * @method bool hasWarningRecords() + * @method bool hasNoticeRecords() + * @method bool hasInfoRecords() + * @method bool hasDebugRecords() + * + * @method bool hasEmergencyThatContains(string $message) + * @method bool hasAlertThatContains(string $message) + * @method bool hasCriticalThatContains(string $message) + * @method bool hasErrorThatContains(string $message) + * @method bool hasWarningThatContains(string $message) + * @method bool hasNoticeThatContains(string $message) + * @method bool hasInfoThatContains(string $message) + * @method bool hasDebugThatContains(string $message) + * + * @method bool hasEmergencyThatMatches(string $regex) + * @method bool hasAlertThatMatches(string $regex) + * @method bool hasCriticalThatMatches(string $regex) + * @method bool hasErrorThatMatches(string $regex) + * @method bool hasWarningThatMatches(string $regex) + * @method bool hasNoticeThatMatches(string $regex) + * @method bool hasInfoThatMatches(string $regex) + * @method bool hasDebugThatMatches(string $regex) + * + * @method bool hasEmergencyThatPasses(callable $predicate) + * @method bool hasAlertThatPasses(callable $predicate) + * @method bool hasCriticalThatPasses(callable $predicate) + * @method bool hasErrorThatPasses(callable $predicate) + * @method bool hasWarningThatPasses(callable $predicate) + * @method bool hasNoticeThatPasses(callable $predicate) + * @method bool hasInfoThatPasses(callable $predicate) + * @method bool hasDebugThatPasses(callable $predicate) + */ +class TestHandler extends AbstractProcessingHandler +{ + /** @var LogRecord[] */ + protected array $records = []; + /** @phpstan-var array, LogRecord[]> */ + protected array $recordsByLevel = []; + private bool $skipReset = false; + + /** + * @return array + */ + public function getRecords(): array + { + return $this->records; + } + + public function clear(): void + { + $this->records = []; + $this->recordsByLevel = []; + } + + public function reset(): void + { + if (!$this->skipReset) { + $this->clear(); + } + } + + public function setSkipReset(bool $skipReset): void + { + $this->skipReset = $skipReset; + } + + /** + * @param int|string|Level|LogLevel::* $level Logging level value or name + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + public function hasRecords(int|string|Level $level): bool + { + return isset($this->recordsByLevel[Logger::toMonologLevel($level)->value]); + } + + /** + * @param string|array $recordAssertions Either a message string or an array containing message and optionally context keys that will be checked against all records + * + * @phpstan-param array{message: string, context?: mixed[]}|string $recordAssertions + */ + public function hasRecord(string|array $recordAssertions, Level $level): bool + { + if (is_string($recordAssertions)) { + $recordAssertions = ['message' => $recordAssertions]; + } + + return $this->hasRecordThatPasses(function (LogRecord $rec) use ($recordAssertions) { + if ($rec->message !== $recordAssertions['message']) { + return false; + } + if (isset($recordAssertions['context']) && $rec->context !== $recordAssertions['context']) { + return false; + } + + return true; + }, $level); + } + + public function hasRecordThatContains(string $message, Level $level): bool + { + return $this->hasRecordThatPasses(fn (LogRecord $rec) => str_contains($rec->message, $message), $level); + } + + public function hasRecordThatMatches(string $regex, Level $level): bool + { + return $this->hasRecordThatPasses(fn (LogRecord $rec) => preg_match($regex, $rec->message) > 0, $level); + } + + /** + * @phpstan-param callable(LogRecord, int): mixed $predicate + */ + public function hasRecordThatPasses(callable $predicate, Level $level): bool + { + $level = Logger::toMonologLevel($level); + + if (!isset($this->recordsByLevel[$level->value])) { + return false; + } + + foreach ($this->recordsByLevel[$level->value] as $i => $rec) { + if ((bool) $predicate($rec, $i)) { + return true; + } + } + + return false; + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $this->recordsByLevel[$record->level->value][] = $record; + $this->records[] = $record; + } + + /** + * @param mixed[] $args + */ + public function __call(string $method, array $args): bool + { + if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { + $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; + $level = constant(Level::class.'::' . $matches[2]); + $callback = [$this, $genericMethod]; + if (is_callable($callback)) { + $args[] = $level; + + return call_user_func_array($callback, $args); + } + } + + throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()'); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php new file mode 100644 index 000000000000..9c12c3d5698e --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +trait WebRequestRecognizerTrait +{ + /** + * Checks if PHP's serving a web request + */ + protected function isWebRequest(): bool + { + return 'cli' !== \PHP_SAPI && 'phpdbg' !== \PHP_SAPI; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php new file mode 100644 index 000000000000..2d4e817a0559 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\LogRecord; +use Throwable; + +/** + * Forwards records to multiple handlers suppressing failures of each handler + * and continuing through to give every handler a chance to succeed. + * + * @author Craig D'Amelio + */ +class WhatFailureGroupHandler extends GroupHandler +{ + /** + * @inheritDoc + */ + public function handle(LogRecord $record): bool + { + if (\count($this->processors) > 0) { + $record = $this->processRecord($record); + } + + foreach ($this->handlers as $handler) { + try { + $handler->handle($record); + } catch (Throwable) { + // What failure? + } + } + + return false === $this->bubble; + } + + /** + * @inheritDoc + */ + public function handleBatch(array $records): void + { + if (\count($this->processors) > 0) { + $processed = []; + foreach ($records as $record) { + $processed[] = $this->processRecord($record); + } + $records = $processed; + } + + foreach ($this->handlers as $handler) { + try { + $handler->handleBatch($records); + } catch (Throwable) { + // What failure? + } + } + } + + /** + * {@inheritDoc} + */ + public function close(): void + { + foreach ($this->handlers as $handler) { + try { + $handler->close(); + } catch (\Throwable $e) { + // What failure? + } + } + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php new file mode 100644 index 000000000000..1e71194bc086 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Level; +use Monolog\LogRecord; + +/** + * Handler sending logs to Zend Monitor + * + * @author Christian Bergau + * @author Jason Davis + */ +class ZendMonitorHandler extends AbstractProcessingHandler +{ + /** + * @throws MissingExtensionException + */ + public function __construct(int|string|Level $level = Level::Debug, bool $bubble = true) + { + if (!function_exists('zend_monitor_custom_event')) { + throw new MissingExtensionException( + 'You must have Zend Server installed with Zend Monitor enabled in order to use this handler' + ); + } + + parent::__construct($level, $bubble); + } + + /** + * Translates Monolog log levels to ZendMonitor levels. + */ + protected function toZendMonitorLevel(Level $level): int + { + return match ($level) { + Level::Debug => \ZEND_MONITOR_EVENT_SEVERITY_INFO, + Level::Info => \ZEND_MONITOR_EVENT_SEVERITY_INFO, + Level::Notice => \ZEND_MONITOR_EVENT_SEVERITY_INFO, + Level::Warning => \ZEND_MONITOR_EVENT_SEVERITY_WARNING, + Level::Error => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, + Level::Critical => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, + Level::Alert => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, + Level::Emergency => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, + }; + } + + /** + * @inheritDoc + */ + protected function write(LogRecord $record): void + { + $this->writeZendMonitorCustomEvent( + $record->level->getName(), + $record->message, + $record->formatted, + $this->toZendMonitorLevel($record->level) + ); + } + + /** + * Write to Zend Monitor Events + * @param string $type Text displayed in "Class Name (custom)" field + * @param string $message Text displayed in "Error String" + * @param array $formatted Displayed in Custom Variables tab + * @param int $severity Set the event severity level (-1,0,1) + */ + protected function writeZendMonitorCustomEvent(string $type, string $message, array $formatted, int $severity): void + { + zend_monitor_custom_event($type, $message, $formatted, $severity); + } + + /** + * @inheritDoc + */ + public function getDefaultFormatter(): FormatterInterface + { + return new NormalizerFormatter(); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Level.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Level.php new file mode 100644 index 000000000000..097d42135bd9 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Level.php @@ -0,0 +1,209 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Psr\Log\LogLevel; + +/** + * Represents the log levels + * + * Monolog supports the logging levels described by RFC 5424 {@see https://datatracker.ietf.org/doc/html/rfc5424} + * but due to BC the severity values used internally are not 0-7. + * + * To get the level name/value out of a Level there are several options: + * + * - Use ->getName() to get the standard Monolog name which is full uppercased (e.g. "DEBUG") + * - Use ->toPsrLogLevel() to get the standard PSR-3 name which is full lowercased (e.g. "debug") + * - Use ->toRFC5424Level() to get the standard RFC 5424 value (e.g. 7 for debug, 0 for emergency) + * - Use ->name to get the enum case's name which is capitalized (e.g. "Debug") + * + * To get the internal value for filtering, if the includes/isLowerThan/isHigherThan methods are + * not enough, you can use ->value to get the enum case's integer value. + */ +enum Level: int +{ + /** + * Detailed debug information + */ + case Debug = 100; + + /** + * Interesting events + * + * Examples: User logs in, SQL logs. + */ + case Info = 200; + + /** + * Uncommon events + */ + case Notice = 250; + + /** + * Exceptional occurrences that are not errors + * + * Examples: Use of deprecated APIs, poor use of an API, + * undesirable things that are not necessarily wrong. + */ + case Warning = 300; + + /** + * Runtime errors + */ + case Error = 400; + + /** + * Critical conditions + * + * Example: Application component unavailable, unexpected exception. + */ + case Critical = 500; + + /** + * Action must be taken immediately + * + * Example: Entire website down, database unavailable, etc. + * This should trigger the SMS alerts and wake you up. + */ + case Alert = 550; + + /** + * Urgent alert. + */ + case Emergency = 600; + + /** + * @param value-of|LogLevel::*|'Debug'|'Info'|'Notice'|'Warning'|'Error'|'Critical'|'Alert'|'Emergency' $name + * @return static + */ + public static function fromName(string $name): self + { + return match ($name) { + 'debug', 'Debug', 'DEBUG' => self::Debug, + 'info', 'Info', 'INFO' => self::Info, + 'notice', 'Notice', 'NOTICE' => self::Notice, + 'warning', 'Warning', 'WARNING' => self::Warning, + 'error', 'Error', 'ERROR' => self::Error, + 'critical', 'Critical', 'CRITICAL' => self::Critical, + 'alert', 'Alert', 'ALERT' => self::Alert, + 'emergency', 'Emergency', 'EMERGENCY' => self::Emergency, + }; + } + + /** + * @param value-of $value + * @return static + */ + public static function fromValue(int $value): self + { + return self::from($value); + } + + /** + * Returns true if the passed $level is higher or equal to $this + */ + public function includes(Level $level): bool + { + return $this->value <= $level->value; + } + + public function isHigherThan(Level $level): bool + { + return $this->value > $level->value; + } + + public function isLowerThan(Level $level): bool + { + return $this->value < $level->value; + } + + /** + * Returns the monolog standardized all-capitals name of the level + * + * Use this instead of $level->name which returns the enum case name (e.g. Debug vs DEBUG if you use getName()) + * + * @return value-of + */ + public function getName(): string + { + return match ($this) { + self::Debug => 'DEBUG', + self::Info => 'INFO', + self::Notice => 'NOTICE', + self::Warning => 'WARNING', + self::Error => 'ERROR', + self::Critical => 'CRITICAL', + self::Alert => 'ALERT', + self::Emergency => 'EMERGENCY', + }; + } + + /** + * Returns the PSR-3 level matching this instance + * + * @phpstan-return \Psr\Log\LogLevel::* + */ + public function toPsrLogLevel(): string + { + return match ($this) { + self::Debug => LogLevel::DEBUG, + self::Info => LogLevel::INFO, + self::Notice => LogLevel::NOTICE, + self::Warning => LogLevel::WARNING, + self::Error => LogLevel::ERROR, + self::Critical => LogLevel::CRITICAL, + self::Alert => LogLevel::ALERT, + self::Emergency => LogLevel::EMERGENCY, + }; + } + + /** + * Returns the RFC 5424 level matching this instance + * + * @phpstan-return int<0, 7> + */ + public function toRFC5424Level(): int + { + return match ($this) { + self::Debug => 7, + self::Info => 6, + self::Notice => 5, + self::Warning => 4, + self::Error => 3, + self::Critical => 2, + self::Alert => 1, + self::Emergency => 0, + }; + } + + public const VALUES = [ + 100, + 200, + 250, + 300, + 400, + 500, + 550, + 600, + ]; + + public const NAMES = [ + 'DEBUG', + 'INFO', + 'NOTICE', + 'WARNING', + 'ERROR', + 'CRITICAL', + 'ALERT', + 'EMERGENCY', + ]; +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/LogRecord.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/LogRecord.php new file mode 100644 index 000000000000..df758c58bf9c --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/LogRecord.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use ArrayAccess; + +/** + * Monolog log record + * + * @author Jordi Boggiano + * @template-implements ArrayAccess<'message'|'level'|'context'|'level_name'|'channel'|'datetime'|'extra', int|string|\DateTimeImmutable|array> + */ +class LogRecord implements ArrayAccess +{ + private const MODIFIABLE_FIELDS = [ + 'extra' => true, + 'formatted' => true, + ]; + + public function __construct( + public readonly \DateTimeImmutable $datetime, + public readonly string $channel, + public readonly Level $level, + public readonly string $message, + /** @var array */ + public readonly array $context = [], + /** @var array */ + public array $extra = [], + public mixed $formatted = null, + ) { + } + + public function offsetSet(mixed $offset, mixed $value): void + { + if ($offset === 'extra') { + if (!is_array($value)) { + throw new \InvalidArgumentException('extra must be an array'); + } + + $this->extra = $value; + + return; + } + + if ($offset === 'formatted') { + $this->formatted = $value; + + return; + } + + throw new \LogicException('Unsupported operation: setting '.$offset); + } + + public function offsetExists(mixed $offset): bool + { + if ($offset === 'level_name') { + return true; + } + + return isset($this->{$offset}); + } + + public function offsetUnset(mixed $offset): void + { + throw new \LogicException('Unsupported operation'); + } + + public function &offsetGet(mixed $offset): mixed + { + if ($offset === 'level_name' || $offset === 'level') { + // avoid returning readonly props by ref as this is illegal + if ($offset === 'level_name') { + $copy = $this->level->getName(); + } else { + $copy = $this->level->value; + } + + return $copy; + } + + if (isset(self::MODIFIABLE_FIELDS[$offset])) { + return $this->{$offset}; + } + + // avoid returning readonly props by ref as this is illegal + $copy = $this->{$offset}; + + return $copy; + } + + /** + * @phpstan-return array{message: string, context: mixed[], level: value-of, level_name: value-of, channel: string, datetime: \DateTimeImmutable, extra: mixed[]} + */ + public function toArray(): array + { + return [ + 'message' => $this->message, + 'context' => $this->context, + 'level' => $this->level->value, + 'level_name' => $this->level->getName(), + 'channel' => $this->channel, + 'datetime' => $this->datetime, + 'extra' => $this->extra, + ]; + } + + public function with(mixed ...$args): self + { + foreach (['message', 'context', 'level', 'channel', 'datetime', 'extra'] as $prop) { + $args[$prop] ??= $this->{$prop}; + } + + return new self(...$args); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Logger.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Logger.php new file mode 100644 index 000000000000..db8bc21babe8 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Logger.php @@ -0,0 +1,735 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Closure; +use DateTimeZone; +use Fiber; +use Monolog\Handler\HandlerInterface; +use Monolog\Processor\ProcessorInterface; +use Psr\Log\LoggerInterface; +use Psr\Log\InvalidArgumentException; +use Psr\Log\LogLevel; +use Throwable; +use Stringable; +use WeakMap; + +/** + * Monolog log channel + * + * It contains a stack of Handlers and a stack of Processors, + * and uses them to store records that are added to it. + * + * @author Jordi Boggiano + * @final + */ +class Logger implements LoggerInterface, ResettableInterface +{ + /** + * Detailed debug information + * + * @deprecated Use \Monolog\Level::Debug + */ + public const DEBUG = 100; + + /** + * Interesting events + * + * Examples: User logs in, SQL logs. + * + * @deprecated Use \Monolog\Level::Info + */ + public const INFO = 200; + + /** + * Uncommon events + * + * @deprecated Use \Monolog\Level::Notice + */ + public const NOTICE = 250; + + /** + * Exceptional occurrences that are not errors + * + * Examples: Use of deprecated APIs, poor use of an API, + * undesirable things that are not necessarily wrong. + * + * @deprecated Use \Monolog\Level::Warning + */ + public const WARNING = 300; + + /** + * Runtime errors + * + * @deprecated Use \Monolog\Level::Error + */ + public const ERROR = 400; + + /** + * Critical conditions + * + * Example: Application component unavailable, unexpected exception. + * + * @deprecated Use \Monolog\Level::Critical + */ + public const CRITICAL = 500; + + /** + * Action must be taken immediately + * + * Example: Entire website down, database unavailable, etc. + * This should trigger the SMS alerts and wake you up. + * + * @deprecated Use \Monolog\Level::Alert + */ + public const ALERT = 550; + + /** + * Urgent alert. + * + * @deprecated Use \Monolog\Level::Emergency + */ + public const EMERGENCY = 600; + + /** + * Monolog API version + * + * This is only bumped when API breaks are done and should + * follow the major version of the library + */ + public const API = 3; + + /** + * Mapping between levels numbers defined in RFC 5424 and Monolog ones + * + * @phpstan-var array $rfc_5424_levels + */ + private const RFC_5424_LEVELS = [ + 7 => Level::Debug, + 6 => Level::Info, + 5 => Level::Notice, + 4 => Level::Warning, + 3 => Level::Error, + 2 => Level::Critical, + 1 => Level::Alert, + 0 => Level::Emergency, + ]; + + protected string $name; + + /** + * The handler stack + * + * @var list + */ + protected array $handlers; + + /** + * Processors that will process all log records + * + * To process records of a single handler instead, add the processor on that specific handler + * + * @var array<(callable(LogRecord): LogRecord)|ProcessorInterface> + */ + protected array $processors; + + protected bool $microsecondTimestamps = true; + + protected DateTimeZone $timezone; + + protected Closure|null $exceptionHandler = null; + + /** + * Keeps track of depth to prevent infinite logging loops + */ + private int $logDepth = 0; + + /** + * @var WeakMap, int> Keeps track of depth inside fibers to prevent infinite logging loops + */ + private WeakMap $fiberLogDepth; + + /** + * Whether to detect infinite logging loops + * This can be disabled via {@see useLoggingLoopDetection} if you have async handlers that do not play well with this + */ + private bool $detectCycles = true; + + /** + * @param string $name The logging channel, a simple descriptive name that is attached to all log records + * @param HandlerInterface[] $handlers Optional stack of handlers, the first one in the array is called first, etc. + * @param callable[] $processors Optional array of processors + * @param DateTimeZone|null $timezone Optional timezone, if not provided date_default_timezone_get() will be used + * + * @phpstan-param array<(callable(LogRecord): LogRecord)|ProcessorInterface> $processors + */ + public function __construct(string $name, array $handlers = [], array $processors = [], DateTimeZone|null $timezone = null) + { + $this->name = $name; + $this->setHandlers($handlers); + $this->processors = $processors; + $this->timezone = $timezone ?? new DateTimeZone(date_default_timezone_get()); + $this->fiberLogDepth = new \WeakMap(); + } + + public function getName(): string + { + return $this->name; + } + + /** + * Return a new cloned instance with the name changed + */ + public function withName(string $name): self + { + $new = clone $this; + $new->name = $name; + + return $new; + } + + /** + * Pushes a handler on to the stack. + */ + public function pushHandler(HandlerInterface $handler): self + { + array_unshift($this->handlers, $handler); + + return $this; + } + + /** + * Pops a handler from the stack + * + * @throws \LogicException If empty handler stack + */ + public function popHandler(): HandlerInterface + { + if (0 === \count($this->handlers)) { + throw new \LogicException('You tried to pop from an empty handler stack.'); + } + + return array_shift($this->handlers); + } + + /** + * Set handlers, replacing all existing ones. + * + * If a map is passed, keys will be ignored. + * + * @param list $handlers + */ + public function setHandlers(array $handlers): self + { + $this->handlers = []; + foreach (array_reverse($handlers) as $handler) { + $this->pushHandler($handler); + } + + return $this; + } + + /** + * @return list + */ + public function getHandlers(): array + { + return $this->handlers; + } + + /** + * Adds a processor on to the stack. + * + * @phpstan-param ProcessorInterface|(callable(LogRecord): LogRecord) $callback + */ + public function pushProcessor(ProcessorInterface|callable $callback): self + { + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * Removes the processor on top of the stack and returns it. + * + * @phpstan-return ProcessorInterface|(callable(LogRecord): LogRecord) + * @throws \LogicException If empty processor stack + */ + public function popProcessor(): callable + { + if (0 === \count($this->processors)) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * @return callable[] + * @phpstan-return array + */ + public function getProcessors(): array + { + return $this->processors; + } + + /** + * Control the use of microsecond resolution timestamps in the 'datetime' + * member of new records. + * + * As of PHP7.1 microseconds are always included by the engine, so + * there is no performance penalty and Monolog 2 enabled microseconds + * by default. This function lets you disable them though in case you want + * to suppress microseconds from the output. + * + * @param bool $micro True to use microtime() to create timestamps + */ + public function useMicrosecondTimestamps(bool $micro): self + { + $this->microsecondTimestamps = $micro; + + return $this; + } + + public function useLoggingLoopDetection(bool $detectCycles): self + { + $this->detectCycles = $detectCycles; + + return $this; + } + + /** + * Adds a log record. + * + * @param int $level The logging level (a Monolog or RFC 5424 level) + * @param string $message The log message + * @param mixed[] $context The log context + * @param DateTimeImmutable $datetime Optional log date to log into the past or future + * @return bool Whether the record has been processed + * + * @phpstan-param value-of|Level $level + */ + public function addRecord(int|Level $level, string $message, array $context = [], DateTimeImmutable $datetime = null): bool + { + if (is_int($level) && isset(self::RFC_5424_LEVELS[$level])) { + $level = self::RFC_5424_LEVELS[$level]; + } + + if ($this->detectCycles) { + if (null !== ($fiber = Fiber::getCurrent())) { + $logDepth = $this->fiberLogDepth[$fiber] = ($this->fiberLogDepth[$fiber] ?? 0) + 1; + } else { + $logDepth = ++$this->logDepth; + } + } else { + $logDepth = 0; + } + + if ($logDepth === 3) { + $this->warning('A possible infinite logging loop was detected and aborted. It appears some of your handler code is triggering logging, see the previous log record for a hint as to what may be the cause.'); + return false; + } elseif ($logDepth >= 5) { // log depth 4 is let through, so we can log the warning above + return false; + } + + try { + $recordInitialized = count($this->processors) === 0; + + $record = new LogRecord( + message: $message, + context: $context, + level: self::toMonologLevel($level), + channel: $this->name, + datetime: $datetime ?? new DateTimeImmutable($this->microsecondTimestamps, $this->timezone), + extra: [], + ); + $handled = false; + + foreach ($this->handlers as $handler) { + if (false === $recordInitialized) { + // skip initializing the record as long as no handler is going to handle it + if (!$handler->isHandling($record)) { + continue; + } + + try { + foreach ($this->processors as $processor) { + $record = $processor($record); + } + $recordInitialized = true; + } catch (Throwable $e) { + $this->handleException($e, $record); + + return true; + } + } + + // once the record is initialized, send it to all handlers as long as the bubbling chain is not interrupted + try { + $handled = true; + if (true === $handler->handle($record)) { + break; + } + } catch (Throwable $e) { + $this->handleException($e, $record); + + return true; + } + } + + return $handled; + } finally { + if ($this->detectCycles) { + if (isset($fiber)) { + $this->fiberLogDepth[$fiber]--; + } else { + $this->logDepth--; + } + } + } + } + + /** + * Ends a log cycle and frees all resources used by handlers. + * + * Closing a Handler means flushing all buffers and freeing any open resources/handles. + * Handlers that have been closed should be able to accept log records again and re-open + * themselves on demand, but this may not always be possible depending on implementation. + * + * This is useful at the end of a request and will be called automatically on every handler + * when they get destructed. + */ + public function close(): void + { + foreach ($this->handlers as $handler) { + $handler->close(); + } + } + + /** + * Ends a log cycle and resets all handlers and processors to their initial state. + * + * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal + * state, and getting it back to a state in which it can receive log records again. + * + * This is useful in case you want to avoid logs leaking between two requests or jobs when you + * have a long running process like a worker or an application server serving multiple requests + * in one process. + */ + public function reset(): void + { + foreach ($this->handlers as $handler) { + if ($handler instanceof ResettableInterface) { + $handler->reset(); + } + } + + foreach ($this->processors as $processor) { + if ($processor instanceof ResettableInterface) { + $processor->reset(); + } + } + } + + /** + * Gets the name of the logging level as a string. + * + * This still returns a string instead of a Level for BC, but new code should not rely on this method. + * + * @throws \Psr\Log\InvalidArgumentException If level is not defined + * + * @phpstan-param value-of|Level $level + * @phpstan-return value-of + * + * @deprecated Since 3.0, use {@see toMonologLevel} or {@see \Monolog\Level->getName()} instead + */ + public static function getLevelName(int|Level $level): string + { + return self::toMonologLevel($level)->getName(); + } + + /** + * Converts PSR-3 levels to Monolog ones if necessary + * + * @param int|string|Level|LogLevel::* $level Level number (monolog) or name (PSR-3) + * @throws \Psr\Log\InvalidArgumentException If level is not defined + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + public static function toMonologLevel(string|int|Level $level): Level + { + if ($level instanceof Level) { + return $level; + } + + if (\is_string($level)) { + if (\is_numeric($level)) { + $levelEnum = Level::tryFrom((int) $level); + if ($levelEnum === null) { + throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', Level::NAMES + Level::VALUES)); + } + + return $levelEnum; + } + + // Contains first char of all log levels and avoids using strtoupper() which may have + // strange results depending on locale (for example, "i" will become "İ" in Turkish locale) + $upper = strtr(substr($level, 0, 1), 'dinweca', 'DINWECA') . strtolower(substr($level, 1)); + if (defined(Level::class.'::'.$upper)) { + return constant(Level::class . '::' . $upper); + } + + throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', Level::NAMES + Level::VALUES)); + } + + $levelEnum = Level::tryFrom($level); + if ($levelEnum === null) { + throw new InvalidArgumentException('Level "'.var_export($level, true).'" is not defined, use one of: '.implode(', ', Level::NAMES + Level::VALUES)); + } + + return $levelEnum; + } + + /** + * Checks whether the Logger has a handler that listens on the given level + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + public function isHandling(int|string|Level $level): bool + { + $record = new LogRecord( + datetime: new DateTimeImmutable($this->microsecondTimestamps, $this->timezone), + channel: $this->name, + message: '', + level: self::toMonologLevel($level), + ); + + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return true; + } + } + + return false; + } + + /** + * Set a custom exception handler that will be called if adding a new record fails + * + * The Closure will receive an exception object and the record that failed to be logged + */ + public function setExceptionHandler(Closure|null $callback): self + { + $this->exceptionHandler = $callback; + + return $this; + } + + public function getExceptionHandler(): Closure|null + { + return $this->exceptionHandler; + } + + /** + * Adds a log record at an arbitrary level. + * + * This method allows for compatibility with common interfaces. + * + * @param mixed $level The log level (a Monolog, PSR-3 or RFC 5424 level) + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + * + * @phpstan-param Level|LogLevel::* $level + */ + public function log($level, string|\Stringable $message, array $context = []): void + { + if (!$level instanceof Level) { + if (!is_string($level) && !is_int($level)) { + throw new \InvalidArgumentException('$level is expected to be a string, int or '.Level::class.' instance'); + } + + if (isset(self::RFC_5424_LEVELS[$level])) { + $level = self::RFC_5424_LEVELS[$level]; + } + + $level = static::toMonologLevel($level); + } + + $this->addRecord($level, (string) $message, $context); + } + + /** + * Adds a log record at the DEBUG level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function debug(string|\Stringable $message, array $context = []): void + { + $this->addRecord(Level::Debug, (string) $message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function info(string|\Stringable $message, array $context = []): void + { + $this->addRecord(Level::Info, (string) $message, $context); + } + + /** + * Adds a log record at the NOTICE level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function notice(string|\Stringable $message, array $context = []): void + { + $this->addRecord(Level::Notice, (string) $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function warning(string|\Stringable $message, array $context = []): void + { + $this->addRecord(Level::Warning, (string) $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function error(string|\Stringable $message, array $context = []): void + { + $this->addRecord(Level::Error, (string) $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function critical(string|\Stringable $message, array $context = []): void + { + $this->addRecord(Level::Critical, (string) $message, $context); + } + + /** + * Adds a log record at the ALERT level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function alert(string|\Stringable $message, array $context = []): void + { + $this->addRecord(Level::Alert, (string) $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * This method allows for compatibility with common interfaces. + * + * @param string|Stringable $message The log message + * @param mixed[] $context The log context + */ + public function emergency(string|\Stringable $message, array $context = []): void + { + $this->addRecord(Level::Emergency, (string) $message, $context); + } + + /** + * Sets the timezone to be used for the timestamp of log records. + */ + public function setTimezone(DateTimeZone $tz): self + { + $this->timezone = $tz; + + return $this; + } + + /** + * Returns the timezone to be used for the timestamp of log records. + */ + public function getTimezone(): DateTimeZone + { + return $this->timezone; + } + + /** + * Delegates exception management to the custom exception handler, + * or throws the exception if no custom handler is set. + */ + protected function handleException(Throwable $e, LogRecord $record): void + { + if (null === $this->exceptionHandler) { + throw $e; + } + + ($this->exceptionHandler)($e, $record); + } + + /** + * @return array + */ + public function __serialize(): array + { + return [ + 'name' => $this->name, + 'handlers' => $this->handlers, + 'processors' => $this->processors, + 'microsecondTimestamps' => $this->microsecondTimestamps, + 'timezone' => $this->timezone, + 'exceptionHandler' => $this->exceptionHandler, + 'logDepth' => $this->logDepth, + 'detectCycles' => $this->detectCycles, + ]; + } + + /** + * @param array $data + */ + public function __unserialize(array $data): void + { + foreach (['name', 'handlers', 'processors', 'microsecondTimestamps', 'timezone', 'exceptionHandler', 'logDepth', 'detectCycles'] as $property) { + if (isset($data[$property])) { + $this->$property = $data[$property]; + } + } + + $this->fiberLogDepth = new \WeakMap(); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/ClosureContextProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/ClosureContextProcessor.php new file mode 100644 index 000000000000..514b354781ad --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/ClosureContextProcessor.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\LogRecord; + +/** + * Generates a context from a Closure if the Closure is the only value + * in the context + * + * It helps reduce the performance impact of debug logs if they do + * need to create lots of context information. If this processor is added + * on the correct handler the context data will only be generated + * when the logs are actually logged to that handler, which is useful when + * using FingersCrossedHandler or other filtering handlers to conditionally + * log records. + */ +class ClosureContextProcessor implements ProcessorInterface +{ + public function __invoke(LogRecord $record): LogRecord + { + $context = $record->context; + if (isset($context[0]) && 1 === \count($context) && $context[0] instanceof \Closure) { + try { + $context = $context[0](); + } catch (\Throwable $e) { + $context = [ + 'error_on_context_generation' => $e->getMessage(), + 'exception' => $e, + ]; + } + + if (!\is_array($context)) { + $context = [$context]; + } + + $record = $record->with(context: $context); + } + + return $record; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/GitProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/GitProcessor.php new file mode 100644 index 000000000000..5a70ac2e2e9a --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/GitProcessor.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Level; +use Monolog\Logger; +use Psr\Log\LogLevel; +use Monolog\LogRecord; + +/** + * Injects Git branch and Git commit SHA in all records + * + * @author Nick Otter + * @author Jordi Boggiano + */ +class GitProcessor implements ProcessorInterface +{ + private Level $level; + /** @var array{branch: string, commit: string}|array|null */ + private static $cache = null; + + /** + * @param int|string|Level|LogLevel::* $level The minimum logging level at which this Processor will be triggered + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + public function __construct(int|string|Level $level = Level::Debug) + { + $this->level = Logger::toMonologLevel($level); + } + + /** + * @inheritDoc + */ + public function __invoke(LogRecord $record): LogRecord + { + // return if the level is not high enough + if ($record->level->isLowerThan($this->level)) { + return $record; + } + + $record->extra['git'] = self::getGitInfo(); + + return $record; + } + + /** + * @return array{branch: string, commit: string}|array + */ + private static function getGitInfo(): array + { + if (self::$cache !== null) { + return self::$cache; + } + + $branches = shell_exec('git branch -v --no-abbrev'); + if (is_string($branches) && 1 === preg_match('{^\* (.+?)\s+([a-f0-9]{40})(?:\s|$)}m', $branches, $matches)) { + return self::$cache = [ + 'branch' => $matches[1], + 'commit' => $matches[2], + ]; + } + + return self::$cache = []; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php new file mode 100644 index 000000000000..cba6e0963637 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\LogRecord; + +/** + * Injects value of gethostname in all records + */ +class HostnameProcessor implements ProcessorInterface +{ + private static string $host; + + public function __construct() + { + self::$host = (string) gethostname(); + } + + /** + * @inheritDoc + */ + public function __invoke(LogRecord $record): LogRecord + { + $record->extra['hostname'] = self::$host; + + return $record; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php new file mode 100644 index 000000000000..3a6fbfbef8c3 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Level; +use Monolog\Logger; +use Psr\Log\LogLevel; +use Monolog\LogRecord; + +/** + * Injects line/file:class/function where the log message came from + * + * Warning: This only works if the handler processes the logs directly. + * If you put the processor on a handler that is behind a FingersCrossedHandler + * for example, the processor will only be called once the trigger level is reached, + * and all the log records will have the same file/line/.. data from the call that + * triggered the FingersCrossedHandler. + * + * @author Jordi Boggiano + */ +class IntrospectionProcessor implements ProcessorInterface +{ + private Level $level; + + /** @var string[] */ + private array $skipClassesPartials; + + private int $skipStackFramesCount; + + private const SKIP_FUNCTIONS = [ + 'call_user_func', + 'call_user_func_array', + ]; + + /** + * @param string|int|Level $level The minimum logging level at which this Processor will be triggered + * @param string[] $skipClassesPartials + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + public function __construct(int|string|Level $level = Level::Debug, array $skipClassesPartials = [], int $skipStackFramesCount = 0) + { + $this->level = Logger::toMonologLevel($level); + $this->skipClassesPartials = array_merge(['Monolog\\'], $skipClassesPartials); + $this->skipStackFramesCount = $skipStackFramesCount; + } + + /** + * @inheritDoc + */ + public function __invoke(LogRecord $record): LogRecord + { + // return if the level is not high enough + if ($record->level->isLowerThan($this->level)) { + return $record; + } + + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + + // skip first since it's always the current method + array_shift($trace); + // the call_user_func call is also skipped + array_shift($trace); + + $i = 0; + + while ($this->isTraceClassOrSkippedFunction($trace, $i)) { + if (isset($trace[$i]['class'])) { + foreach ($this->skipClassesPartials as $part) { + if (strpos($trace[$i]['class'], $part) !== false) { + $i++; + + continue 2; + } + } + } elseif (in_array($trace[$i]['function'], self::SKIP_FUNCTIONS, true)) { + $i++; + + continue; + } + + break; + } + + $i += $this->skipStackFramesCount; + + // we should have the call source now + $record->extra = array_merge( + $record->extra, + [ + 'file' => $trace[$i - 1]['file'] ?? null, + 'line' => $trace[$i - 1]['line'] ?? null, + 'class' => $trace[$i]['class'] ?? null, + 'callType' => $trace[$i]['type'] ?? null, + 'function' => $trace[$i]['function'] ?? null, + ] + ); + + return $record; + } + + /** + * @param array $trace + */ + private function isTraceClassOrSkippedFunction(array $trace, int $index): bool + { + if (!isset($trace[$index])) { + return false; + } + + return isset($trace[$index]['class']) || in_array($trace[$index]['function'], self::SKIP_FUNCTIONS, true); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/LoadAverageProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/LoadAverageProcessor.php new file mode 100644 index 000000000000..64e3c4747d26 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/LoadAverageProcessor.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\LogRecord; + +/** + * Injects sys_getloadavg in all records @see https://www.php.net/manual/en/function.sys-getloadavg.php + * + * @author Johan Vlaar + */ +class LoadAverageProcessor implements ProcessorInterface +{ + public const LOAD_1_MINUTE = 0; + public const LOAD_5_MINUTE = 1; + public const LOAD_15_MINUTE = 2; + + private const AVAILABLE_LOAD = [ + self::LOAD_1_MINUTE, + self::LOAD_5_MINUTE, + self::LOAD_15_MINUTE, + ]; + + /** + * @var int + */ + protected $avgSystemLoad; + + /** + * @param self::LOAD_* $avgSystemLoad + */ + public function __construct(int $avgSystemLoad = self::LOAD_1_MINUTE) + { + if (!in_array($avgSystemLoad, self::AVAILABLE_LOAD, true)) { + throw new \InvalidArgumentException(sprintf('Invalid average system load: `%s`', $avgSystemLoad)); + } + $this->avgSystemLoad = $avgSystemLoad; + } + + /** + * {@inheritDoc} + */ + public function __invoke(LogRecord $record): LogRecord + { + if (!function_exists('sys_getloadavg')) { + return $record; + } + $usage = sys_getloadavg(); + if (false === $usage) { + return $record; + } + + $record->extra['load_average'] = $usage[$this->avgSystemLoad]; + + return $record; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php new file mode 100644 index 000000000000..adc32c65d4ea --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\LogRecord; + +/** + * Injects memory_get_peak_usage in all records + * + * @see Monolog\Processor\MemoryProcessor::__construct() for options + * @author Rob Jensen + */ +class MemoryPeakUsageProcessor extends MemoryProcessor +{ + /** + * @inheritDoc + */ + public function __invoke(LogRecord $record): LogRecord + { + $usage = memory_get_peak_usage($this->realUsage); + + if ($this->useFormatting) { + $usage = $this->formatBytes($usage); + } + + $record->extra['memory_peak_usage'] = $usage; + + return $record; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php new file mode 100644 index 000000000000..f808e51b4a49 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Some methods that are common for all memory processors + * + * @author Rob Jensen + */ +abstract class MemoryProcessor implements ProcessorInterface +{ + /** + * @var bool If true, get the real size of memory allocated from system. Else, only the memory used by emalloc() is reported. + */ + protected bool $realUsage; + + /** + * @var bool If true, then format memory size to human readable string (MB, KB, B depending on size) + */ + protected bool $useFormatting; + + /** + * @param bool $realUsage Set this to true to get the real size of memory allocated from system. + * @param bool $useFormatting If true, then format memory size to human readable string (MB, KB, B depending on size) + */ + public function __construct(bool $realUsage = true, bool $useFormatting = true) + { + $this->realUsage = $realUsage; + $this->useFormatting = $useFormatting; + } + + /** + * Formats bytes into a human readable string if $this->useFormatting is true, otherwise return $bytes as is + * + * @return string|int Formatted string if $this->useFormatting is true, otherwise return $bytes as int + */ + protected function formatBytes(int $bytes) + { + if (!$this->useFormatting) { + return $bytes; + } + + if ($bytes > 1024 * 1024) { + return round($bytes / 1024 / 1024, 2).' MB'; + } elseif ($bytes > 1024) { + return round($bytes / 1024, 2).' KB'; + } + + return $bytes . ' B'; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php new file mode 100644 index 000000000000..a814b1df34b8 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\LogRecord; + +/** + * Injects memory_get_usage in all records + * + * @see Monolog\Processor\MemoryProcessor::__construct() for options + * @author Rob Jensen + */ +class MemoryUsageProcessor extends MemoryProcessor +{ + /** + * @inheritDoc + */ + public function __invoke(LogRecord $record): LogRecord + { + $usage = memory_get_usage($this->realUsage); + + if ($this->useFormatting) { + $usage = $this->formatBytes($usage); + } + + $record->extra['memory_usage'] = $usage; + + return $record; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php new file mode 100644 index 000000000000..47b1e64ff90e --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Level; +use Monolog\Logger; +use Psr\Log\LogLevel; +use Monolog\LogRecord; + +/** + * Injects Hg branch and Hg revision number in all records + * + * @author Jonathan A. Schweder + */ +class MercurialProcessor implements ProcessorInterface +{ + private Level $level; + /** @var array{branch: string, revision: string}|array|null */ + private static $cache = null; + + /** + * @param int|string|Level $level The minimum logging level at which this Processor will be triggered + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + public function __construct(int|string|Level $level = Level::Debug) + { + $this->level = Logger::toMonologLevel($level); + } + + /** + * @inheritDoc + */ + public function __invoke(LogRecord $record): LogRecord + { + // return if the level is not high enough + if ($record->level->isLowerThan($this->level)) { + return $record; + } + + $record->extra['hg'] = self::getMercurialInfo(); + + return $record; + } + + /** + * @return array{branch: string, revision: string}|array + */ + private static function getMercurialInfo(): array + { + if (self::$cache !== null) { + return self::$cache; + } + + $result = explode(' ', trim((string) shell_exec('hg id -nb'))); + + if (count($result) >= 3) { + return self::$cache = [ + 'branch' => $result[1], + 'revision' => $result[2], + ]; + } + + return self::$cache = []; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php new file mode 100644 index 000000000000..bb9a5224343d --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\LogRecord; + +/** + * Adds value of getmypid into records + * + * @author Andreas Hörnicke + */ +class ProcessIdProcessor implements ProcessorInterface +{ + /** + * @inheritDoc + */ + public function __invoke(LogRecord $record): LogRecord + { + $record->extra['process_id'] = getmypid(); + + return $record; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php new file mode 100644 index 000000000000..ebe41fc20604 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\LogRecord; + +/** + * An optional interface to allow labelling Monolog processors. + * + * @author Nicolas Grekas + */ +interface ProcessorInterface +{ + /** + * @return LogRecord The processed record + */ + public function __invoke(LogRecord $record); +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php new file mode 100644 index 000000000000..aad2aad2f32e --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Utils; +use Monolog\LogRecord; + +/** + * Processes a record's message according to PSR-3 rules + * + * It replaces {foo} with the value from $context['foo'] + * + * @author Jordi Boggiano + */ +class PsrLogMessageProcessor implements ProcessorInterface +{ + public const SIMPLE_DATE = "Y-m-d\TH:i:s.uP"; + + private ?string $dateFormat; + + private bool $removeUsedContextFields; + + /** + * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format + * @param bool $removeUsedContextFields If set to true the fields interpolated into message gets unset + */ + public function __construct(?string $dateFormat = null, bool $removeUsedContextFields = false) + { + $this->dateFormat = $dateFormat; + $this->removeUsedContextFields = $removeUsedContextFields; + } + + /** + * @inheritDoc + */ + public function __invoke(LogRecord $record): LogRecord + { + if (false === strpos($record->message, '{')) { + return $record; + } + + $replacements = []; + $context = $record->context; + + foreach ($context as $key => $val) { + $placeholder = '{' . $key . '}'; + if (strpos($record->message, $placeholder) === false) { + continue; + } + + if (null === $val || is_scalar($val) || (is_object($val) && method_exists($val, "__toString"))) { + $replacements[$placeholder] = $val; + } elseif ($val instanceof \DateTimeInterface) { + if (null === $this->dateFormat && $val instanceof \Monolog\DateTimeImmutable) { + // handle monolog dates using __toString if no specific dateFormat was asked for + // so that it follows the useMicroseconds flag + $replacements[$placeholder] = (string) $val; + } else { + $replacements[$placeholder] = $val->format($this->dateFormat ?? static::SIMPLE_DATE); + } + } elseif ($val instanceof \UnitEnum) { + $replacements[$placeholder] = $val instanceof \BackedEnum ? $val->value : $val->name; + } elseif (is_object($val)) { + $replacements[$placeholder] = '[object '.Utils::getClass($val).']'; + } elseif (is_array($val)) { + $replacements[$placeholder] = 'array'.Utils::jsonEncode($val, null, true); + } else { + $replacements[$placeholder] = '['.gettype($val).']'; + } + + if ($this->removeUsedContextFields) { + unset($context[$key]); + } + } + + return $record->with(message: strtr($record->message, $replacements), context: $context); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/TagProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/TagProcessor.php new file mode 100644 index 000000000000..4543ccf6112c --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/TagProcessor.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\LogRecord; + +/** + * Adds a tags array into record + * + * @author Martijn Riemers + */ +class TagProcessor implements ProcessorInterface +{ + /** @var string[] */ + private array $tags; + + /** + * @param string[] $tags + */ + public function __construct(array $tags = []) + { + $this->setTags($tags); + } + + /** + * @param string[] $tags + */ + public function addTags(array $tags = []): self + { + $this->tags = array_merge($this->tags, $tags); + + return $this; + } + + /** + * @param string[] $tags + */ + public function setTags(array $tags = []): self + { + $this->tags = $tags; + + return $this; + } + + /** + * @inheritDoc + */ + public function __invoke(LogRecord $record): LogRecord + { + $record->extra['tags'] = $this->tags; + + return $record; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/UidProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/UidProcessor.php new file mode 100644 index 000000000000..3a0c128c2b6a --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/UidProcessor.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\ResettableInterface; +use Monolog\LogRecord; + +/** + * Adds a unique identifier into records + * + * @author Simon Mönch + */ +class UidProcessor implements ProcessorInterface, ResettableInterface +{ + /** @var non-empty-string */ + private string $uid; + + /** + * @param int<1, 32> $length + */ + public function __construct(int $length = 7) + { + if ($length > 32 || $length < 1) { + throw new \InvalidArgumentException('The uid length must be an integer between 1 and 32'); + } + + $this->uid = $this->generateUid($length); + } + + /** + * @inheritDoc + */ + public function __invoke(LogRecord $record): LogRecord + { + $record->extra['uid'] = $this->uid; + + return $record; + } + + public function getUid(): string + { + return $this->uid; + } + + public function reset(): void + { + $this->uid = $this->generateUid(strlen($this->uid)); + } + + /** + * @param positive-int $length + * @return non-empty-string + */ + private function generateUid(int $length): string + { + return substr(bin2hex(random_bytes((int) ceil($length / 2))), 0, $length); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/WebProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/WebProcessor.php new file mode 100644 index 000000000000..2088b180b795 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/WebProcessor.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use ArrayAccess; +use Monolog\LogRecord; + +/** + * Injects url/method and remote IP of the current web request in all records + * + * @author Jordi Boggiano + */ +class WebProcessor implements ProcessorInterface +{ + /** + * @var array|ArrayAccess + */ + protected array|ArrayAccess $serverData; + + /** + * Default fields + * + * Array is structured as [key in record.extra => key in $serverData] + * + * @var array + */ + protected array $extraFields = [ + 'url' => 'REQUEST_URI', + 'ip' => 'REMOTE_ADDR', + 'http_method' => 'REQUEST_METHOD', + 'server' => 'SERVER_NAME', + 'referrer' => 'HTTP_REFERER', + 'user_agent' => 'HTTP_USER_AGENT', + ]; + + /** + * @param array|ArrayAccess|null $serverData Array or object w/ ArrayAccess that provides access to the $_SERVER data + * @param array|array|null $extraFields Field names and the related key inside $serverData to be added (or just a list of field names to use the default configured $serverData mapping). If not provided it defaults to: [url, ip, http_method, server, referrer] + unique_id if present in server data + */ + public function __construct(array|ArrayAccess|null $serverData = null, array|null $extraFields = null) + { + if (null === $serverData) { + $this->serverData = &$_SERVER; + } else { + $this->serverData = $serverData; + } + + $defaultEnabled = ['url', 'ip', 'http_method', 'server', 'referrer']; + if (isset($this->serverData['UNIQUE_ID'])) { + $this->extraFields['unique_id'] = 'UNIQUE_ID'; + $defaultEnabled[] = 'unique_id'; + } + + if (null === $extraFields) { + $extraFields = $defaultEnabled; + } + if (isset($extraFields[0])) { + foreach (array_keys($this->extraFields) as $fieldName) { + if (!in_array($fieldName, $extraFields, true)) { + unset($this->extraFields[$fieldName]); + } + } + } else { + $this->extraFields = $extraFields; + } + } + + /** + * @inheritDoc + */ + public function __invoke(LogRecord $record): LogRecord + { + // skip processing if for some reason request data + // is not present (CLI or wonky SAPIs) + if (!isset($this->serverData['REQUEST_URI'])) { + return $record; + } + + $record->extra = $this->appendExtraFields($record->extra); + + return $record; + } + + public function addExtraField(string $extraName, string $serverName): self + { + $this->extraFields[$extraName] = $serverName; + + return $this; + } + + /** + * @param mixed[] $extra + * @return mixed[] + */ + private function appendExtraFields(array $extra): array + { + foreach ($this->extraFields as $extraName => $serverName) { + $extra[$extraName] = $this->serverData[$serverName] ?? null; + } + + return $extra; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Registry.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Registry.php new file mode 100644 index 000000000000..2ef2edceb4d3 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Registry.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use InvalidArgumentException; + +/** + * Monolog log registry + * + * Allows to get `Logger` instances in the global scope + * via static method calls on this class. + * + * + * $application = new Monolog\Logger('application'); + * $api = new Monolog\Logger('api'); + * + * Monolog\Registry::addLogger($application); + * Monolog\Registry::addLogger($api); + * + * function testLogger() + * { + * Monolog\Registry::api()->error('Sent to $api Logger instance'); + * Monolog\Registry::application()->error('Sent to $application Logger instance'); + * } + * + * + * @author Tomas Tatarko + */ +class Registry +{ + /** + * List of all loggers in the registry (by named indexes) + * + * @var Logger[] + */ + private static array $loggers = []; + + /** + * Adds new logging channel to the registry + * + * @param Logger $logger Instance of the logging channel + * @param string|null $name Name of the logging channel ($logger->getName() by default) + * @param bool $overwrite Overwrite instance in the registry if the given name already exists? + * @throws \InvalidArgumentException If $overwrite set to false and named Logger instance already exists + */ + public static function addLogger(Logger $logger, ?string $name = null, bool $overwrite = false): void + { + $name = $name ?? $logger->getName(); + + if (isset(self::$loggers[$name]) && !$overwrite) { + throw new InvalidArgumentException('Logger with the given name already exists'); + } + + self::$loggers[$name] = $logger; + } + + /** + * Checks if such logging channel exists by name or instance + * + * @param string|Logger $logger Name or logger instance + */ + public static function hasLogger($logger): bool + { + if ($logger instanceof Logger) { + $index = array_search($logger, self::$loggers, true); + + return false !== $index; + } + + return isset(self::$loggers[$logger]); + } + + /** + * Removes instance from registry by name or instance + * + * @param string|Logger $logger Name or logger instance + */ + public static function removeLogger($logger): void + { + if ($logger instanceof Logger) { + if (false !== ($idx = array_search($logger, self::$loggers, true))) { + unset(self::$loggers[$idx]); + } + } else { + unset(self::$loggers[$logger]); + } + } + + /** + * Clears the registry + */ + public static function clear(): void + { + self::$loggers = []; + } + + /** + * Gets Logger instance from the registry + * + * @param string $name Name of the requested Logger instance + * @throws \InvalidArgumentException If named Logger instance is not in the registry + */ + public static function getInstance(string $name): Logger + { + if (!isset(self::$loggers[$name])) { + throw new InvalidArgumentException(sprintf('Requested "%s" logger instance is not in the registry', $name)); + } + + return self::$loggers[$name]; + } + + /** + * Gets Logger instance from the registry via static method call + * + * @param string $name Name of the requested Logger instance + * @param mixed[] $arguments Arguments passed to static method call + * @throws \InvalidArgumentException If named Logger instance is not in the registry + * @return Logger Requested instance of Logger + */ + public static function __callStatic(string $name, array $arguments): Logger + { + return self::getInstance($name); + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/ResettableInterface.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/ResettableInterface.php new file mode 100644 index 000000000000..4983a6b3553a --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/ResettableInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +/** + * Handler or Processor implementing this interface will be reset when Logger::reset() is called. + * + * Resetting ends a log cycle gets them back to their initial state. + * + * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal + * state, and getting it back to a state in which it can receive log records again. + * + * This is useful in case you want to avoid logs leaking between two requests or jobs when you + * have a long running process like a worker or an application server serving multiple requests + * in one process. + * + * @author Grégoire Pineau + */ +interface ResettableInterface +{ + public function reset(): void; +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/SignalHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/SignalHandler.php new file mode 100644 index 000000000000..b930ca4395a6 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/SignalHandler.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; +use ReflectionExtension; + +/** + * Monolog POSIX signal handler + * + * @author Robert Gust-Bardon + */ +class SignalHandler +{ + private LoggerInterface $logger; + + /** @var array SIG_DFL, SIG_IGN or previous callable */ + private array $previousSignalHandler = []; + /** @var array */ + private array $signalLevelMap = []; + /** @var array */ + private array $signalRestartSyscalls = []; + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * @param int|string|Level $level Level or level name + * @return $this + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + public function registerSignalHandler(int $signo, int|string|Level $level = LogLevel::CRITICAL, bool $callPrevious = true, bool $restartSyscalls = true, ?bool $async = true): self + { + if (!extension_loaded('pcntl') || !function_exists('pcntl_signal')) { + return $this; + } + + $level = Logger::toMonologLevel($level)->toPsrLogLevel(); + + if ($callPrevious) { + $handler = pcntl_signal_get_handler($signo); + $this->previousSignalHandler[$signo] = $handler; + } else { + unset($this->previousSignalHandler[$signo]); + } + $this->signalLevelMap[$signo] = $level; + $this->signalRestartSyscalls[$signo] = $restartSyscalls; + + if ($async !== null) { + pcntl_async_signals($async); + } + + pcntl_signal($signo, [$this, 'handleSignal'], $restartSyscalls); + + return $this; + } + + /** + * @param mixed $siginfo + */ + public function handleSignal(int $signo, $siginfo = null): void + { + /** @var array $signals */ + static $signals = []; + + if (\count($signals) === 0 && extension_loaded('pcntl')) { + $pcntl = new ReflectionExtension('pcntl'); + foreach ($pcntl->getConstants() as $name => $value) { + if (substr($name, 0, 3) === 'SIG' && $name[3] !== '_' && is_int($value)) { + $signals[$value] = $name; + } + } + } + + $level = $this->signalLevelMap[$signo] ?? LogLevel::CRITICAL; + $signal = $signals[$signo] ?? $signo; + $context = $siginfo ?? []; + $this->logger->log($level, sprintf('Program received signal %s', $signal), $context); + + if (!isset($this->previousSignalHandler[$signo])) { + return; + } + + if ($this->previousSignalHandler[$signo] === SIG_DFL) { + if (extension_loaded('pcntl') && function_exists('pcntl_signal') && function_exists('pcntl_sigprocmask') && function_exists('pcntl_signal_dispatch') + && extension_loaded('posix') && function_exists('posix_getpid') && function_exists('posix_kill') + ) { + $restartSyscalls = $this->signalRestartSyscalls[$signo] ?? true; + pcntl_signal($signo, SIG_DFL, $restartSyscalls); + pcntl_sigprocmask(SIG_UNBLOCK, [$signo], $oldset); + posix_kill(posix_getpid(), $signo); + pcntl_signal_dispatch(); + pcntl_sigprocmask(SIG_SETMASK, $oldset); + pcntl_signal($signo, [$this, 'handleSignal'], $restartSyscalls); + } + } elseif (is_callable($this->previousSignalHandler[$signo])) { + $this->previousSignalHandler[$signo]($signo, $siginfo); + } + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Test/TestCase.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Test/TestCase.php new file mode 100644 index 000000000000..98204a95ca5f --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Test/TestCase.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Test; + +use Monolog\Level; +use Monolog\Logger; +use Monolog\LogRecord; +use Monolog\DateTimeImmutable; +use Monolog\Formatter\FormatterInterface; +use Psr\Log\LogLevel; + +/** + * Lets you easily generate log records and a dummy formatter for testing purposes + * + * @author Jordi Boggiano + * + * @internal feel free to reuse this to test your own handlers, this is marked internal to avoid issues with PHPStorm https://github.com/Seldaek/monolog/issues/1677 + */ +class TestCase extends \PHPUnit\Framework\TestCase +{ + public function tearDown(): void + { + parent::tearDown(); + + if (isset($this->handler)) { + unset($this->handler); + } + } + + /** + * @param array $context + * @param array $extra + * + * @phpstan-param value-of|value-of|Level|LogLevel::* $level + */ + protected function getRecord(int|string|Level $level = Level::Warning, string|\Stringable $message = 'test', array $context = [], string $channel = 'test', \DateTimeImmutable $datetime = new DateTimeImmutable(true), array $extra = []): LogRecord + { + return new LogRecord( + message: (string) $message, + context: $context, + level: Logger::toMonologLevel($level), + channel: $channel, + datetime: $datetime, + extra: $extra, + ); + } + + /** + * @phpstan-return list + */ + protected function getMultipleRecords(): array + { + return [ + $this->getRecord(Level::Debug, 'debug message 1'), + $this->getRecord(Level::Debug, 'debug message 2'), + $this->getRecord(Level::Info, 'information'), + $this->getRecord(Level::Warning, 'warning'), + $this->getRecord(Level::Error, 'error'), + ]; + } + + protected function getIdentityFormatter(): FormatterInterface + { + $formatter = $this->createMock(FormatterInterface::class); + $formatter->expects(self::any()) + ->method('format') + ->will(self::returnCallback(function ($record) { + return $record->message; + })); + + return $formatter; + } +} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Utils.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Utils.php new file mode 100644 index 000000000000..7848f0ecdda1 --- /dev/null +++ b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Utils.php @@ -0,0 +1,274 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +final class Utils +{ + const DEFAULT_JSON_FLAGS = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION | JSON_INVALID_UTF8_SUBSTITUTE | JSON_PARTIAL_OUTPUT_ON_ERROR; + + public static function getClass(object $object): string + { + $class = \get_class($object); + + if (false === ($pos = \strpos($class, "@anonymous\0"))) { + return $class; + } + + if (false === ($parent = \get_parent_class($class))) { + return \substr($class, 0, $pos + 10); + } + + return $parent . '@anonymous'; + } + + public static function substr(string $string, int $start, ?int $length = null): string + { + if (extension_loaded('mbstring')) { + return mb_strcut($string, $start, $length); + } + + return substr($string, $start, (null === $length) ? strlen($string) : $length); + } + + /** + * Makes sure if a relative path is passed in it is turned into an absolute path + * + * @param string $streamUrl stream URL or path without protocol + */ + public static function canonicalizePath(string $streamUrl): string + { + $prefix = ''; + if ('file://' === substr($streamUrl, 0, 7)) { + $streamUrl = substr($streamUrl, 7); + $prefix = 'file://'; + } + + // other type of stream, not supported + if (false !== strpos($streamUrl, '://')) { + return $streamUrl; + } + + // already absolute + if (substr($streamUrl, 0, 1) === '/' || substr($streamUrl, 1, 1) === ':' || substr($streamUrl, 0, 2) === '\\\\') { + return $prefix.$streamUrl; + } + + $streamUrl = getcwd() . '/' . $streamUrl; + + return $prefix.$streamUrl; + } + + /** + * Return the JSON representation of a value + * + * @param mixed $data + * @param int $encodeFlags flags to pass to json encode, defaults to DEFAULT_JSON_FLAGS + * @param bool $ignoreErrors whether to ignore encoding errors or to throw on error, when ignored and the encoding fails, "null" is returned which is valid json for null + * @throws \RuntimeException if encoding fails and errors are not ignored + * @return string when errors are ignored and the encoding fails, "null" is returned which is valid json for null + */ + public static function jsonEncode($data, ?int $encodeFlags = null, bool $ignoreErrors = false): string + { + if (null === $encodeFlags) { + $encodeFlags = self::DEFAULT_JSON_FLAGS; + } + + if ($ignoreErrors) { + $json = @json_encode($data, $encodeFlags); + if (false === $json) { + return 'null'; + } + + return $json; + } + + $json = json_encode($data, $encodeFlags); + if (false === $json) { + $json = self::handleJsonError(json_last_error(), $data); + } + + return $json; + } + + /** + * Handle a json_encode failure. + * + * If the failure is due to invalid string encoding, try to clean the + * input and encode again. If the second encoding attempt fails, the + * initial error is not encoding related or the input can't be cleaned then + * raise a descriptive exception. + * + * @param int $code return code of json_last_error function + * @param mixed $data data that was meant to be encoded + * @param int $encodeFlags flags to pass to json encode, defaults to JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION + * @throws \RuntimeException if failure can't be corrected + * @return string JSON encoded data after error correction + */ + public static function handleJsonError(int $code, $data, ?int $encodeFlags = null): string + { + if ($code !== JSON_ERROR_UTF8) { + self::throwEncodeError($code, $data); + } + + if (is_string($data)) { + self::detectAndCleanUtf8($data); + } elseif (is_array($data)) { + array_walk_recursive($data, ['Monolog\Utils', 'detectAndCleanUtf8']); + } else { + self::throwEncodeError($code, $data); + } + + if (null === $encodeFlags) { + $encodeFlags = self::DEFAULT_JSON_FLAGS; + } + + $json = json_encode($data, $encodeFlags); + + if ($json === false) { + self::throwEncodeError(json_last_error(), $data); + } + + return $json; + } + + /** + * @internal + */ + public static function pcreLastErrorMessage(int $code): string + { + if (PHP_VERSION_ID >= 80000) { + return preg_last_error_msg(); + } + + $constants = (get_defined_constants(true))['pcre']; + $constants = array_filter($constants, function ($key) { + return substr($key, -6) == '_ERROR'; + }, ARRAY_FILTER_USE_KEY); + + $constants = array_flip($constants); + + return $constants[$code] ?? 'UNDEFINED_ERROR'; + } + + /** + * Throws an exception according to a given code with a customized message + * + * @param int $code return code of json_last_error function + * @param mixed $data data that was meant to be encoded + * @throws \RuntimeException + */ + private static function throwEncodeError(int $code, $data): never + { + $msg = match ($code) { + JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', + JSON_ERROR_STATE_MISMATCH => 'Underflow or the modes mismatch', + JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', + JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded', + default => 'Unknown error', + }; + + throw new \RuntimeException('JSON encoding failed: '.$msg.'. Encoding: '.var_export($data, true)); + } + + /** + * Detect invalid UTF-8 string characters and convert to valid UTF-8. + * + * Valid UTF-8 input will be left unmodified, but strings containing + * invalid UTF-8 codepoints will be reencoded as UTF-8 with an assumed + * original encoding of ISO-8859-15. This conversion may result in + * incorrect output if the actual encoding was not ISO-8859-15, but it + * will be clean UTF-8 output and will not rely on expensive and fragile + * detection algorithms. + * + * Function converts the input in place in the passed variable so that it + * can be used as a callback for array_walk_recursive. + * + * @param mixed $data Input to check and convert if needed, passed by ref + */ + private static function detectAndCleanUtf8(&$data): void + { + if (is_string($data) && preg_match('//u', $data) !== 1) { + $data = preg_replace_callback( + '/[\x80-\xFF]+/', + function (array $m): string { + return function_exists('mb_convert_encoding') ? mb_convert_encoding($m[0], 'UTF-8', 'ISO-8859-1') : utf8_encode($m[0]); + }, + $data + ); + if (!is_string($data)) { + $pcreErrorCode = preg_last_error(); + + throw new \RuntimeException('Failed to preg_replace_callback: ' . $pcreErrorCode . ' / ' . self::pcreLastErrorMessage($pcreErrorCode)); + } + $data = str_replace( + ['¤', '¦', '¨', '´', '¸', '¼', '½', '¾'], + ['€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'], + $data + ); + } + } + + /** + * Converts a string with a valid 'memory_limit' format, to bytes. + * + * @param string|false $val + * @return int|false Returns an integer representing bytes. Returns FALSE in case of error. + */ + public static function expandIniShorthandBytes($val) + { + if (!is_string($val)) { + return false; + } + + // support -1 + if ((int) $val < 0) { + return (int) $val; + } + + if (preg_match('/^\s*(?\d+)(?:\.\d+)?\s*(?[gmk]?)\s*$/i', $val, $match) !== 1) { + return false; + } + + $val = (int) $match['val']; + switch (strtolower($match['unit'] ?? '')) { + case 'g': + $val *= 1024; + // no break + case 'm': + $val *= 1024; + // no break + case 'k': + $val *= 1024; + } + + return $val; + } + + public static function getRecordMessageForException(LogRecord $record): string + { + $context = ''; + $extra = ''; + + try { + if (\count($record->context) > 0) { + $context = "\nContext: " . json_encode($record->context, JSON_THROW_ON_ERROR); + } + if (\count($record->extra) > 0) { + $extra = "\nExtra: " . json_encode($record->extra, JSON_THROW_ON_ERROR); + } + } catch (\Throwable $e) { + // noop + } + + return "\nThe exception occurred while attempting to log: " . $record->message . $context . $extra; + } +} diff --git a/apps/files_external/3rdparty/psr/cache/README.md b/apps/files_external/3rdparty/psr/cache/README.md new file mode 100644 index 000000000000..9855a318bd4b --- /dev/null +++ b/apps/files_external/3rdparty/psr/cache/README.md @@ -0,0 +1,12 @@ +Caching Interface +============== + +This repository holds all interfaces related to [PSR-6 (Caching Interface)][psr-url]. + +Note that this is not a Caching implementation of its own. It is merely interfaces that describe the components of a Caching mechanism. + +The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist. + +[psr-url]: https://www.php-fig.org/psr/psr-6/ +[package-url]: https://packagist.org/packages/psr/cache +[implementation-url]: https://packagist.org/providers/psr/cache-implementation diff --git a/apps/files_external/3rdparty/psr/cache/composer.json b/apps/files_external/3rdparty/psr/cache/composer.json new file mode 100644 index 000000000000..4b687971e4cb --- /dev/null +++ b/apps/files_external/3rdparty/psr/cache/composer.json @@ -0,0 +1,25 @@ +{ + "name": "psr/cache", + "description": "Common interface for caching libraries", + "keywords": ["psr", "psr-6", "cache"], + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "require": { + "php": ">=8.0.0" + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/apps/files_external/3rdparty/psr/cache/src/CacheException.php b/apps/files_external/3rdparty/psr/cache/src/CacheException.php new file mode 100644 index 000000000000..bb785f46cebc --- /dev/null +++ b/apps/files_external/3rdparty/psr/cache/src/CacheException.php @@ -0,0 +1,10 @@ +=8.0.0" + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + } +} diff --git a/apps/files_external/3rdparty/psr/log/src/AbstractLogger.php b/apps/files_external/3rdparty/psr/log/src/AbstractLogger.php new file mode 100644 index 000000000000..d60a091affae --- /dev/null +++ b/apps/files_external/3rdparty/psr/log/src/AbstractLogger.php @@ -0,0 +1,15 @@ +logger = $logger; + } +} diff --git a/apps/files_external/3rdparty/psr/log/src/LoggerInterface.php b/apps/files_external/3rdparty/psr/log/src/LoggerInterface.php new file mode 100644 index 000000000000..b3a24b5f7e9d --- /dev/null +++ b/apps/files_external/3rdparty/psr/log/src/LoggerInterface.php @@ -0,0 +1,125 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string|\Stringable $message + * @param array $context + * + * @return void + */ + public function alert(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string|\Stringable $message + * @param array $context + * + * @return void + */ + public function critical(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string|\Stringable $message + * @param array $context + * + * @return void + */ + public function error(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string|\Stringable $message + * @param array $context + * + * @return void + */ + public function warning(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string|\Stringable $message + * @param array $context + * + * @return void + */ + public function notice(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string|\Stringable $message + * @param array $context + * + * @return void + */ + public function info(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string|\Stringable $message + * @param array $context + * + * @return void + */ + public function debug(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::DEBUG, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string|\Stringable $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + abstract public function log($level, string|\Stringable $message, array $context = []): void; +} diff --git a/apps/files_external/3rdparty/psr/log/src/NullLogger.php b/apps/files_external/3rdparty/psr/log/src/NullLogger.php new file mode 100644 index 000000000000..c1cc3c0692a2 --- /dev/null +++ b/apps/files_external/3rdparty/psr/log/src/NullLogger.php @@ -0,0 +1,30 @@ +logger) { }` + * blocks. + */ +class NullLogger extends AbstractLogger +{ + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string|\Stringable $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + public function log($level, string|\Stringable $message, array $context = []): void + { + // noop + } +} diff --git a/apps/files_external/appinfo/Migrations/Version20220329110116.php b/apps/files_external/appinfo/Migrations/Version20220329110116.php index 62ef9ad00166..d0f322c94941 100644 --- a/apps/files_external/appinfo/Migrations/Version20220329110116.php +++ b/apps/files_external/appinfo/Migrations/Version20220329110116.php @@ -12,6 +12,7 @@ use OCP\ILogger; use OCP\IConfig; use phpseclib3\Crypt\RSA as RSACrypt; +use phpseclib3\Crypt\RSA\PrivateKey; class Version20220329110116 implements ISimpleMigration { /** @var IGlobalStoragesService */ diff --git a/core/Command/Integrity/SignApp.php b/core/Command/Integrity/SignApp.php index a68c47cf649c..b9bd469df82c 100644 --- a/core/Command/Integrity/SignApp.php +++ b/core/Command/Integrity/SignApp.php @@ -26,6 +26,7 @@ use OC\IntegrityCheck\Helpers\FileAccessHelper; use OCP\IURLGenerator; use phpseclib3\Crypt\RSA; +use phpseclib3\Crypt\RSA\PrivateKey; use phpseclib3\File\X509; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; diff --git a/core/Command/Integrity/SignCore.php b/core/Command/Integrity/SignCore.php index a998adfcc29b..116785160a2f 100644 --- a/core/Command/Integrity/SignCore.php +++ b/core/Command/Integrity/SignCore.php @@ -25,6 +25,7 @@ use OC\IntegrityCheck\Checker; use OC\IntegrityCheck\Helpers\FileAccessHelper; use phpseclib3\Crypt\RSA; +use phpseclib3\Crypt\RSA\PrivateKey; use phpseclib3\File\X509; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; diff --git a/lib/private/Files/Cache/CacheEntry.php b/lib/private/Files/Cache/CacheEntry.php index f5e647848d03..871492e1eb26 100644 --- a/lib/private/Files/Cache/CacheEntry.php +++ b/lib/private/Files/Cache/CacheEntry.php @@ -36,24 +36,20 @@ public function __construct(array $data) { $this->data = $data; } - public function offsetSet($offset, $value) { + public function offsetSet($offset, $value): void { $this->data[$offset] = $value; } - public function offsetExists($offset) { + public function offsetExists($offset): bool { return isset($this->data[$offset]); } - public function offsetUnset($offset) { + public function offsetUnset($offset): void { unset($this->data[$offset]); } - public function offsetGet($offset) { - if (isset($this->data[$offset])) { - return $this->data[$offset]; - } else { - return null; - } + public function offsetGet($offset): mixed { + return $this->data[$offset] ?? null; } public function getId() { diff --git a/lib/private/Files/FileInfo.php b/lib/private/Files/FileInfo.php index 1a3d927e566d..374c159c94e6 100644 --- a/lib/private/Files/FileInfo.php +++ b/lib/private/Files/FileInfo.php @@ -85,30 +85,32 @@ public function __construct($path, $storage, $internalPath, $data, $mount, $owne $this->owner = $owner; } - public function offsetSet($offset, $value) { + public function offsetSet($offset, $value): void { $this->data[$offset] = $value; } - public function offsetExists($offset) { + public function offsetExists($offset): bool { return isset($this->data[$offset]); } - public function offsetUnset($offset) { + public function offsetUnset($offset): void { unset($this->data[$offset]); } - public function offsetGet($offset) { + public function offsetGet($offset): mixed { if ($offset === 'type') { return $this->getType(); - } elseif ($offset === 'etag') { + } + + if ($offset === 'etag') { return $this->getEtag(); - } elseif ($offset === 'permissions') { + } + + if ($offset === 'permissions') { return $this->getPermissions(); - } elseif (isset($this->data[$offset])) { - return $this->data[$offset]; - } else { - return null; } + + return $this->data[$offset] ?? null; } /** diff --git a/lib/private/Files/Stream/Encryption.php b/lib/private/Files/Stream/Encryption.php index 7ec3b2d6cccd..fe1650e04582 100644 --- a/lib/private/Files/Stream/Encryption.php +++ b/lib/private/Files/Stream/Encryption.php @@ -199,11 +199,8 @@ public static function wrap( protected static function wrapSource($source, $context = [], $protocol = null, $class = null, $mode = 'r+') { try { \stream_wrapper_register($protocol, $class); - if (@\rewinddir($source) === false) { - $wrapped = \fopen($protocol . '://', $mode, false, $context); - } else { - $wrapped = \opendir($protocol . '://', $context); - } + \rewinddir($source); + $wrapped = \fopen($protocol . '://', $mode, false, $context); } catch (\BadMethodCallException $e) { \stream_wrapper_unregister($protocol); throw $e; diff --git a/lib/private/Image/BmpToResource.php b/lib/private/Image/BmpToResource.php index 44713af3d78b..b62ab6edf47c 100644 --- a/lib/private/Image/BmpToResource.php +++ b/lib/private/Image/BmpToResource.php @@ -68,10 +68,9 @@ public function __construct($fileName) { } /** - * @return resource * @throws \Exception */ - public function toResource() { + public function toResource(): \GdImage { try { $this->header = $this->readBitmapHeader(); $this->header += $this->readDibHeader(); diff --git a/lib/private/IntegrityCheck/Checker.php b/lib/private/IntegrityCheck/Checker.php index a27191387c1b..fff3d441fbfa 100644 --- a/lib/private/IntegrityCheck/Checker.php +++ b/lib/private/IntegrityCheck/Checker.php @@ -38,6 +38,7 @@ use OCP\IConfig; use OCP\ITempManager; use phpseclib3\Crypt\RSA; +use phpseclib3\Crypt\RSA\PrivateKey; use phpseclib3\File\X509; /** diff --git a/lib/private/IntegrityCheck/Iterator/ExcludeFileByNameFilterIterator.php b/lib/private/IntegrityCheck/Iterator/ExcludeFileByNameFilterIterator.php index 1d6e78bdcec5..651355a7134f 100644 --- a/lib/private/IntegrityCheck/Iterator/ExcludeFileByNameFilterIterator.php +++ b/lib/private/IntegrityCheck/Iterator/ExcludeFileByNameFilterIterator.php @@ -65,10 +65,7 @@ class ExcludeFileByNameFilterIterator extends \RecursiveFilterIterator { '|/core/js/mimetypelist.js$|', // this file can be regenerated with additional entries with occ maintenance:mimetype:update-js ]; - /** - * @return bool - */ - public function accept() { + public function accept(): bool { /** @var \SplFileInfo $current */ $current = $this->current(); diff --git a/lib/private/IntegrityCheck/Iterator/ExcludeFoldersByPathFilterIterator.php b/lib/private/IntegrityCheck/Iterator/ExcludeFoldersByPathFilterIterator.php index b3030f6b74f8..58437fcae528 100644 --- a/lib/private/IntegrityCheck/Iterator/ExcludeFoldersByPathFilterIterator.php +++ b/lib/private/IntegrityCheck/Iterator/ExcludeFoldersByPathFilterIterator.php @@ -49,10 +49,7 @@ public function __construct(\RecursiveIterator $iterator, $root = '') { $this->excludedFolders = \array_merge($excludedFolders, $appFolders); } - /** - * @return bool - */ - public function accept() { + public function accept(): bool { return !\in_array( $this->current()->getPathName(), $this->excludedFolders, diff --git a/lib/private/Memcache/ArrayCache.php b/lib/private/Memcache/ArrayCache.php index be54e9c311e4..41d164c4758f 100644 --- a/lib/private/Memcache/ArrayCache.php +++ b/lib/private/Memcache/ArrayCache.php @@ -94,9 +94,9 @@ public function add($key, $value, $ttl = 0) { // since this cache is not shared race conditions aren't an issue if ($this->hasKey($key)) { return false; - } else { - return $this->set($key, $value, $ttl); } + + return $this->set($key, $value, $ttl); } /** @@ -111,10 +111,10 @@ public function inc($key, $step = 1) { if (\is_int($oldValue)) { $this->set($key, $oldValue + $step); return $oldValue + $step; - } else { - $success = $this->add($key, $step); - return ($success) ? $step : false; } + + $success = $this->add($key, $step); + return ($success) ? $step : false; } /** @@ -129,9 +129,9 @@ public function dec($key, $step = 1) { if (\is_int($oldValue)) { $this->set($key, $oldValue - $step); return $oldValue - $step; - } else { - return false; } + + return false; } /** @@ -145,12 +145,12 @@ public function dec($key, $step = 1) { public function cas($key, $old, $new) { if ($this->get($key) === $old) { return $this->set($key, $new); - } else { - return false; } + + return false; } - public static function isAvailable(): true { + public static function isAvailable(): bool { return true; } } diff --git a/lib/private/Session/CryptoSessionData.php b/lib/private/Session/CryptoSessionData.php index 233ba5f025c4..0cc99a000e78 100644 --- a/lib/private/Session/CryptoSessionData.php +++ b/lib/private/Session/CryptoSessionData.php @@ -33,16 +33,11 @@ * @package OC\Session */ class CryptoSessionData implements \ArrayAccess, ISession { - /** @var ISession */ - protected $session; - /** @var \OCP\Security\ICrypto */ - protected $crypto; - /** @var string */ - protected $passphrase; - /** @var array */ - protected $sessionValues; - /** @var bool */ - protected $isModified = false; + protected ISession $session; + protected ICrypto $crypto; + protected string $passphrase; + protected array $sessionValues; + protected bool $isModified = false; public const encryptedSessionName = 'encrypted_session_data'; /** @@ -67,20 +62,20 @@ public function __construct( public function __destruct() { try { $this->close(); - } catch (SessionNotAvailableException $e) { + } catch (SessionNotAvailableException) { // This exception can occur if session is already closed // So it is safe to ignore it and let the garbage collector to proceed } } - protected function initializeSession() { + protected function initializeSession(): void { $encryptedSessionData = $this->session->get(self::encryptedSessionName); try { $this->sessionValues = \json_decode( $this->crypto->decrypt($encryptedSessionData, $this->passphrase), true ); - } catch (\Exception $e) { + } catch (\Exception) { $this->sessionValues = []; } } @@ -91,7 +86,7 @@ protected function initializeSession() { * @param string $key * @param mixed $value */ - public function set($key, $value) { + public function set($key, $value): void { $this->sessionValues[$key] = $value; $this->isModified = true; } @@ -102,12 +97,8 @@ public function set($key, $value) { * @param string $key * @return string|null Either the value or null */ - public function get($key) { - if (isset($this->sessionValues[$key])) { - return $this->sessionValues[$key]; - } - - return null; + public function get($key): ?string { + return $this->sessionValues[$key] ?? null; } /** @@ -116,7 +107,7 @@ public function get($key) { * @param string $key * @return bool */ - public function exists($key) { + public function exists($key): bool { return isset($this->sessionValues[$key]); } @@ -125,7 +116,7 @@ public function exists($key) { * * @param string $key */ - public function remove($key) { + public function remove($key): void { $this->isModified = true; unset($this->sessionValues[$key]); $this->session->remove(self::encryptedSessionName); @@ -134,7 +125,7 @@ public function remove($key) { /** * Reset and recreate the session */ - public function clear() { + public function clear(): void { $this->sessionValues = []; $this->isModified = true; $this->session->clear(); @@ -146,7 +137,7 @@ public function clear() { * @param bool $deleteOldSession Whether to delete the old associated session file or not. * @return void */ - public function regenerateId($deleteOldSession = true) { + public function regenerateId($deleteOldSession = true): void { $this->session->regenerateId($deleteOldSession); } @@ -157,14 +148,14 @@ public function regenerateId($deleteOldSession = true) { * @throws SessionNotAvailableException * @since 9.1.0 */ - public function getId() { + public function getId(): string { return $this->session->getId(); } /** * Close the session and release the lock, also writes all changed data in batch */ - public function close() { + public function close(): void { if ($this->isModified) { $encryptedValue = $this->crypto->encrypt(\json_encode($this->sessionValues), $this->passphrase); $this->session->set(self::encryptedSessionName, $encryptedValue); @@ -177,15 +168,15 @@ public function close() { * @param mixed $offset * @return bool */ - public function offsetExists($offset) { + public function offsetExists($offset): bool { return $this->exists($offset); } /** * @param mixed $offset - * @return mixed + * @return string|null */ - public function offsetGet($offset) { + public function offsetGet($offset): ?string { return $this->get($offset); } @@ -193,14 +184,14 @@ public function offsetGet($offset) { * @param mixed $offset * @param mixed $value */ - public function offsetSet($offset, $value) { + public function offsetSet($offset, mixed $value): void { $this->set($offset, $value); } /** * @param mixed $offset */ - public function offsetUnset($offset) { + public function offsetUnset($offset): void { $this->remove($offset); } } diff --git a/lib/private/User/Sync/BackendUsersIterator.php b/lib/private/User/Sync/BackendUsersIterator.php index 25b5d748e523..062374bfaefe 100644 --- a/lib/private/User/Sync/BackendUsersIterator.php +++ b/lib/private/User/Sync/BackendUsersIterator.php @@ -23,33 +23,30 @@ use OCP\UserInterface; class BackendUsersIterator extends UsersIterator { - /** - * @var UserInterface - */ - private $backend; + private UserInterface $backend; /** * @var int the current data position, * we need to track it independently of parent::$position to handle data sets larger thin LIMIT properly */ - private $dataPos = 0; + private int $dataPos = 0; /** * @var int to cache the count($this->data) calculations */ - private $endPos = 0; + private int $endPos = 0; /** @var bool false if the backend returned less than LIMIT results */ - private $hasMoreData = false; + private bool $hasMoreData = false; /** @var string search for the uid string in backend */ - private $search; + private string $search; - public function __construct(UserInterface $backend, $filterUID = '') { + public function __construct(UserInterface $backend, string $filterUID = '') { $this->backend = $backend; $this->search = $filterUID; } - public function rewind() { + public function rewind(): void { parent::rewind(); $this->data = $this->backend->getUsers($this->search, self::LIMIT, 0); $this->dataPos = 0; @@ -57,7 +54,7 @@ public function rewind() { $this->hasMoreData = $this->endPos >= self::LIMIT; } - public function next() { + public function next(): void { $this->position++; $this->dataPos++; if ($this->hasMoreData && $this->dataPos >= $this->endPos) { @@ -70,7 +67,7 @@ public function next() { } } - protected function currentDataPos() { + protected function currentDataPos(): int { return $this->dataPos; } } diff --git a/lib/private/User/Sync/SeenUsersIterator.php b/lib/private/User/Sync/SeenUsersIterator.php index f4af918c0a98..53e331c2c018 100644 --- a/lib/private/User/Sync/SeenUsersIterator.php +++ b/lib/private/User/Sync/SeenUsersIterator.php @@ -23,26 +23,20 @@ use OC\User\AccountMapper; class SeenUsersIterator extends UsersIterator { - /** - * @var AccountMapper - */ - private $mapper; - /** - * @var string class name - */ - private $backend; + private AccountMapper $mapper; + private string $backend; - public function __construct(AccountMapper $mapper, $backend) { + public function __construct(AccountMapper $mapper, string $backend) { $this->mapper = $mapper; $this->backend = $backend; } - public function rewind() { + public function rewind(): void { parent::rewind(); $this->data = $this->mapper->findUserIds($this->backend, true, self::LIMIT, 0); } - public function next() { + public function next(): void { $this->position++; if ($this->currentDataPos() === 0) { $this->page++; diff --git a/lib/private/User/Sync/UsersIterator.php b/lib/private/User/Sync/UsersIterator.php index 9a97aae64abe..486c552b7fb9 100644 --- a/lib/private/User/Sync/UsersIterator.php +++ b/lib/private/User/Sync/UsersIterator.php @@ -21,32 +21,32 @@ namespace OC\User\Sync; abstract class UsersIterator implements \Iterator { - protected $position = 0; + protected int $position = 0; protected $page; protected $data; public const LIMIT = 500; - public function rewind() { + public function rewind(): void { $this->position = 0; $this->page = 0; } - public function current() { + public function current(): mixed { return $this->data[$this->currentDataPos()]; } - public function key() { + public function key(): mixed { return $this->position; } - abstract public function next(); + abstract public function next(): void; - public function valid() { + public function valid(): bool { return isset($this->data[$this->currentDataPos()]); } - protected function currentDataPos() { + protected function currentDataPos(): int { return $this->position % self::LIMIT; } } diff --git a/lib/private/legacy/image.php b/lib/private/legacy/image.php index a6d5658c6eac..625fb1df45b6 100644 --- a/lib/private/legacy/image.php +++ b/lib/private/legacy/image.php @@ -769,13 +769,7 @@ private function loadFromBase64($str) { return false; } - /** - * Create a new image from file or URL - * @param string $fileName

- * Path to the BMP image. - * @return bool|resource an image resource identifier on success, FALSE on errors. - */ - private function imagecreatefrombmp($fileName) { + private function imagecreatefrombmp(string $fileName): GdImage|bool { try { $bmp = new BmpToResource($fileName); $imageHandle = $bmp->toResource(); @@ -830,9 +824,8 @@ public function preciseResize($width, $height) { $heightOrig = \imagesy($this->resource); $process = \imagecreatetruecolor($width, $height); - if ($process == false) { + if ($process === false) { $this->logger->error(__METHOD__ . '(): Error creating true color image', ['app' => 'core']); - \imagedestroy($process); return false; } @@ -843,8 +836,8 @@ public function preciseResize($width, $height) { \imagesavealpha($process, true); } - \imagecopyresampled($process, $this->resource, 0, 0, 0, 0, $width, $height, $widthOrig, $heightOrig); - if ($process == false) { + $result = \imagecopyresampled($process, $this->resource, 0, 0, 0, 0, $width, $height, $widthOrig, $heightOrig); + if ($result === false) { $this->logger->error(__METHOD__ . '(): Error re-sampling process image', ['app' => 'core']); \imagedestroy($process); return false; @@ -890,7 +883,6 @@ public function centerCrop($size = 0) { $process = \imagecreatetruecolor($targetWidth, $targetHeight); if ($process == false) { $this->logger->error('OC_Image->centerCrop, Error creating true color image', ['app' => 'core']); - \imagedestroy($process); return false; } @@ -901,8 +893,8 @@ public function centerCrop($size = 0) { \imagesavealpha($process, true); } - \imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $targetWidth, $targetHeight, $width, $height); - if ($process == false) { + $result = \imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $targetWidth, $targetHeight, $width, $height); + if ($result === false) { $this->logger->error('OC_Image->centerCrop, Error re-sampling process image ' . $width . 'x' . $height, ['app' => 'core']); \imagedestroy($process); return false; @@ -927,9 +919,8 @@ public function crop($x, $y, $w, $h) { return false; } $process = \imagecreatetruecolor($w, $h); - if ($process == false) { + if ($process === false) { $this->logger->error(__METHOD__ . '(): Error creating true color image', ['app' => 'core']); - \imagedestroy($process); return false; } @@ -940,8 +931,8 @@ public function crop($x, $y, $w, $h) { \imagesavealpha($process, true); } - \imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $w, $h, $w, $h); - if ($process == false) { + $result = \imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $w, $h, $w, $h); + if ($result === false) { $this->logger->error(__METHOD__ . '(): Error re-sampling process image ' . $w . 'x' . $h, ['app' => 'core']); \imagedestroy($process); return false; @@ -1001,7 +992,7 @@ public function scaleDownToFit($maxWidth, $maxHeight) { /** * Destroys the current image and resets the object */ - public function destroy() { + public function destroy(): void { if ($this->valid()) { \imagedestroy($this->resource); } From 010c2b213298b337eceb2078d386500b8a89d38e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Mon, 25 Sep 2023 23:45:06 +0200 Subject: [PATCH 06/42] fix: style & phan --- lib/private/legacy/image.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/private/legacy/image.php b/lib/private/legacy/image.php index 625fb1df45b6..c2fb40810bc8 100644 --- a/lib/private/legacy/image.php +++ b/lib/private/legacy/image.php @@ -1067,6 +1067,7 @@ function imagebmp($im, $fileName = '', $bit = 24, $compression = 0) { $index = \imagecolorat($im, $i, $j); if ($index !== $lastIndex || $sameNum > 255) { if ($sameNum != 0) { + /** @phan-suppress-next-line PhanTypeMismatchArgumentInternalReal */ $bmpData .= \chr($sameNum) . \chr($lastIndex); } $lastIndex = $index; From 494578be93a17461339f818d31c68d2bf24dc6ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Tue, 26 Sep 2023 00:09:45 +0200 Subject: [PATCH 07/42] fix: add oc version back to DefinitionParameter::jsonSerialize --- lib/public/Files/External/DefinitionParameter.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/public/Files/External/DefinitionParameter.php b/lib/public/Files/External/DefinitionParameter.php index 8c947ed97f05..7ccf7ae8fbd2 100644 --- a/lib/public/Files/External/DefinitionParameter.php +++ b/lib/public/Files/External/DefinitionParameter.php @@ -156,6 +156,8 @@ public function isFlagSet($flag) { /** * Serialize into JSON for client-side JS + * + * @since 10.0 */ public function jsonSerialize(): array { return [ From 39580e1927b24da07d4a5e8e6ec041ceba52c93b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Tue, 26 Sep 2023 00:36:00 +0200 Subject: [PATCH 08/42] fix: more php8.x deprecations --- lib/private/Archive/ZIP.php | 2 +- .../DB/QueryBuilder/CompositeExpression.php | 2 +- lib/private/Files/Stream/Close.php | 1 + lib/private/Files/Stream/Quota.php | 7 ++--- lib/private/IntegrityCheck/Checker.php | 6 ++++- lib/private/Memcache/Memcached.php | 10 +++---- tests/lib/AppFramework/Http/RequestStream.php | 1 + tests/lib/DB/MySqlToolsTest.php | 5 +--- tests/lib/Encryption/Keys/StorageTest.php | 4 +++ tests/lib/Files/FilesystemTest.php | 4 +-- tests/lib/IntegrityCheck/CheckerTest.php | 26 +++++++++---------- tests/lib/LegacyHelperTest.php | 13 ---------- tests/lib/TestCase.php | 2 +- 13 files changed, 39 insertions(+), 44 deletions(-) diff --git a/lib/private/Archive/ZIP.php b/lib/private/Archive/ZIP.php index ba7f6eda226f..456aa14a2c38 100644 --- a/lib/private/Archive/ZIP.php +++ b/lib/private/Archive/ZIP.php @@ -45,7 +45,7 @@ class ZIP extends Archive { public function __construct($source) { $this->path=$source; $this->zip=new ZipArchive(); - if ($this->zip->open($source, ZipArchive::CREATE)) { + if ($this->zip->open($source, ZipArchive::OVERWRITE)) { } else { \OCP\Util::writeLog('files_archive', 'Error while opening archive '.$source, \OCP\Util::WARN); } diff --git a/lib/private/DB/QueryBuilder/CompositeExpression.php b/lib/private/DB/QueryBuilder/CompositeExpression.php index 22af55359f52..ff0b372947e8 100644 --- a/lib/private/DB/QueryBuilder/CompositeExpression.php +++ b/lib/private/DB/QueryBuilder/CompositeExpression.php @@ -68,7 +68,7 @@ public function add($part) { * * @return integer */ - public function count() { + public function count(): int { return $this->compositeExpression->count(); } diff --git a/lib/private/Files/Stream/Close.php b/lib/private/Files/Stream/Close.php index 4810d01150a8..421173d2bc2e 100644 --- a/lib/private/Files/Stream/Close.php +++ b/lib/private/Files/Stream/Close.php @@ -28,6 +28,7 @@ * stream wrapper that provides a callback on stream close */ class Close { + public $context; private static $callBacks = []; private $path = ''; private $source; diff --git a/lib/private/Files/Stream/Quota.php b/lib/private/Files/Stream/Quota.php index 1cb7c4c77370..2964da6a7d1c 100644 --- a/lib/private/Files/Stream/Quota.php +++ b/lib/private/Files/Stream/Quota.php @@ -32,6 +32,7 @@ * or: resource \OC\Files\Stream\Quota::wrap($stream, $limit) */ class Quota { + public $context; private static $streams = []; /** @@ -66,7 +67,7 @@ public static function clear() { * @return resource */ public static function wrap($stream, $limit) { - $id = \uniqid(); + $id = \uniqid('', true); self::register($id, $stream, $limit); $meta = \stream_get_meta_data($stream); return \fopen('quota://' . $id, $meta['mode']); @@ -77,9 +78,9 @@ public function stream_open($path, $mode, $options, &$opened_path) { if (isset(self::$streams[$id])) { list($this->source, $this->limit) = self::$streams[$id]; return true; - } else { - return false; } + + return false; } public function stream_seek($offset, $whence = SEEK_SET) { diff --git a/lib/private/IntegrityCheck/Checker.php b/lib/private/IntegrityCheck/Checker.php index fff3d441fbfa..610e99e22595 100644 --- a/lib/private/IntegrityCheck/Checker.php +++ b/lib/private/IntegrityCheck/Checker.php @@ -343,7 +343,11 @@ private function verify($signaturePath, $basePath, $certificateCN, $force = fals return []; } - $signatureData = \json_decode($this->fileAccessHelper->file_get_contents($signaturePath), true); + $content = $this->fileAccessHelper->file_get_contents($signaturePath); + if (!$content) { + throw new MissingSignatureException('Signature data not found.'); + } + $signatureData = \json_decode($content, true); if (!\is_array($signatureData)) { throw new MissingSignatureException('Signature data not found.'); } diff --git a/lib/private/Memcache/Memcached.php b/lib/private/Memcache/Memcached.php index 5d80de578af6..337938ecc013 100644 --- a/lib/private/Memcache/Memcached.php +++ b/lib/private/Memcache/Memcached.php @@ -96,16 +96,16 @@ protected function getNamespace() { return $this->prefix; } - public function get($key) { + public function get($key): mixed { $result = self::$cache->get($this->getNamespace() . $key); - if ($result === false and self::$cache->getResultCode() == \Memcached::RES_NOTFOUND) { + if ($result === false && self::$cache->getResultCode() == \Memcached::RES_NOTFOUND) { return null; - } else { - return $result; } + + return $result; } - public function set($key, $value, $ttl = 0) { + public function set($key, $value, $ttl = 0): mixed { if ($ttl > 0) { $result = self::$cache->set($this->getNamespace() . $key, $value, $ttl); } else { diff --git a/tests/lib/AppFramework/Http/RequestStream.php b/tests/lib/AppFramework/Http/RequestStream.php index d23aee181d62..b70990123ea3 100644 --- a/tests/lib/AppFramework/Http/RequestStream.php +++ b/tests/lib/AppFramework/Http/RequestStream.php @@ -7,6 +7,7 @@ * Used to simulate php://input for Request tests */ class RequestStream { + public $context; protected $position; protected $varname; diff --git a/tests/lib/DB/MySqlToolsTest.php b/tests/lib/DB/MySqlToolsTest.php index d7f6d7d12565..42f97c624f23 100644 --- a/tests/lib/DB/MySqlToolsTest.php +++ b/tests/lib/DB/MySqlToolsTest.php @@ -39,10 +39,7 @@ class MySqlToolsTest extends TestCase { * @var IDBConnection */ private $db; - - /** - * @var MySqlTools - */ + private MySqlTools $tools; public function setUp(): void { parent::setUp(); diff --git a/tests/lib/Encryption/Keys/StorageTest.php b/tests/lib/Encryption/Keys/StorageTest.php index d24cee79b749..c982080bad6d 100644 --- a/tests/lib/Encryption/Keys/StorageTest.php +++ b/tests/lib/Encryption/Keys/StorageTest.php @@ -53,6 +53,10 @@ class StorageTest extends TestCase { /** @var \PHPUnit\Framework\MockObject\MockObject */ protected $config; + /** + * @var array|string[] + */ + private array $mkdirStack; public function setUp(): void { parent::setUp(); diff --git a/tests/lib/Files/FilesystemTest.php b/tests/lib/Files/FilesystemTest.php index 7d33710473c5..faafad461b28 100644 --- a/tests/lib/Files/FilesystemTest.php +++ b/tests/lib/Files/FilesystemTest.php @@ -95,13 +95,13 @@ protected function tearDown(): void { } self::logout(); - self::invokePrivate('\OC\Files\Filesystem', 'normalizedPathCache', [null]); + # self::invokePrivate('\OC\Files\Filesystem', 'normalizedPathCache', [null]); \OC_User::clearBackends(); parent::tearDown(); } public function testMount() { - Filesystem::mount('\OC\Files\Storage\Local', self::getStorageData(), '/'); + Filesystem::mount('\OC\Files\Storage\Local', $this->getStorageData(), '/'); $this->assertEquals('/', Filesystem::getMountPoint('/')); $this->assertEquals('/', Filesystem::getMountPoint('/some/folder')); list(, $internalPath) = Filesystem::resolvePath('/'); diff --git a/tests/lib/IntegrityCheck/CheckerTest.php b/tests/lib/IntegrityCheck/CheckerTest.php index 1a26fe82ec8a..c8a385434861 100644 --- a/tests/lib/IntegrityCheck/CheckerTest.php +++ b/tests/lib/IntegrityCheck/CheckerTest.php @@ -72,7 +72,7 @@ public function setUp(): void { ->expects($this->any()) ->method('create') ->with('oc.integritycheck.checker') - ->will($this->returnValue(new NullCache())); + ->willReturn(new NullCache()); $this->checker = new Checker( $this->environmentHelper, @@ -154,7 +154,7 @@ public function testIgnoredAppSignatureWithoutSignatureData() { $this->environmentHelper ->expects($this->once()) ->method('getChannel') - ->will($this->returnValue('stable')); + ->willReturn('stable'); $configMap = [ ['integrity.check.disabled', false, false], ['integrity.ignore.missing.app.signature', [], ['SomeApp']] @@ -162,7 +162,7 @@ public function testIgnoredAppSignatureWithoutSignatureData() { $this->config ->expects($this->any()) ->method('getSystemValue') - ->will($this->returnValueMap($configMap)); + ->willReturnMap($configMap); $expected = []; $this->assertSame($expected, $this->checker->verifyAppSignature('SomeApp')); @@ -172,7 +172,7 @@ public function testVerifyAppSignatureWithoutSignatureData() { $this->environmentHelper ->expects($this->once()) ->method('getChannel') - ->will($this->returnValue('stable')); + ->willReturn('stable'); $configMap = [ ['integrity.check.disabled', false, false], ['integrity.ignore.missing.app.signature', [], []] @@ -180,7 +180,7 @@ public function testVerifyAppSignatureWithoutSignatureData() { $this->config ->expects($this->any()) ->method('getSystemValue') - ->will($this->returnValueMap($configMap)); + ->willReturnMap($configMap); $expected = [ 'EXCEPTION' => [ @@ -199,16 +199,16 @@ public function testVerifyAppSignatureWithValidSignatureData() { $this->environmentHelper ->expects($this->once()) ->method('getChannel') - ->will($this->returnValue('stable')); + ->willReturn('stable'); $this->config ->method('getSystemValue') - ->will($this->returnValueMap($map)); + ->willReturnMap($map); $this->appLocator ->expects($this->once()) ->method('getAppPath') ->with('SomeApp') - ->will($this->returnValue(\OC::$SERVERROOT . '/tests/data/integritycheck/app/')); + ->willReturn(\OC::$SERVERROOT . '/tests/data/integritycheck/app/'); $signatureDataFile = '{ "hashes": { "AnotherFile.txt": "1570ca9420e37629de4328f48c51da29840ddeaa03ae733da4bf1d854b8364f594aac560601270f9e1797ed4cd57c1aea87bf44cf4245295c94f2e935a2f0112", @@ -235,18 +235,18 @@ public function testVerifyAppSignatureWithTamperedSignatureData() { $this->environmentHelper ->expects($this->once()) ->method('getChannel') - ->will($this->returnValue('stable')); + ->willReturn('stable'); $this->config ->expects($this->any()) ->method('getSystemValue') ->with('integrity.check.disabled', false) - ->will($this->returnValue(false)); + ->willReturn(false); $this->appLocator ->expects($this->once()) ->method('getAppPath') ->with('SomeApp') - ->will($this->returnValue(\OC::$SERVERROOT . '/tests/data/integritycheck/app/')); + ->willReturn(\OC::$SERVERROOT . '/tests/data/integritycheck/app/'); $signatureDataFile = '{ "hashes": { "AnotherFile.txt": "tampered", @@ -283,7 +283,7 @@ public function testVerifyAppSignatureWithTamperedFiles() { $this->environmentHelper ->expects($this->once()) ->method('getChannel') - ->will($this->returnValue('stable')); + ->willReturn('stable'); $this->config ->method('getSystemValue') ->will($this->returnValueMap($map)); @@ -292,7 +292,7 @@ public function testVerifyAppSignatureWithTamperedFiles() { ->expects($this->once()) ->method('getAppPath') ->with('SomeApp') - ->will($this->returnValue(\OC::$SERVERROOT . '/tests/data/integritycheck/appWithInvalidData/')); + ->willReturn(\OC::$SERVERROOT . '/tests/data/integritycheck/appWithInvalidData/'); $signatureDataFile = '{ "hashes": { "AnotherFile.txt": "1570ca9420e37629de4328f48c51da29840ddeaa03ae733da4bf1d854b8364f594aac560601270f9e1797ed4cd57c1aea87bf44cf4245295c94f2e935a2f0112", diff --git a/tests/lib/LegacyHelperTest.php b/tests/lib/LegacyHelperTest.php index 0f4e092ac6e3..c7da8b2cfb14 100644 --- a/tests/lib/LegacyHelperTest.php +++ b/tests/lib/LegacyHelperTest.php @@ -262,17 +262,4 @@ public function testRecursiveFolderDeletion() { \OC_Helper::rmdirr($baseDir); $this->assertFileDoesNotExist($baseDir); } - - /** - * Allows us to test private methods/properties - * - * @param $object - * @param $methodName - * @param array $parameters - * @return mixed - * @deprecated Please extend \Test\TestCase and use self::invokePrivate() then - */ - public static function invokePrivate($object, $methodName, array $parameters = []) { - return parent::invokePrivate($object, $methodName, $parameters); - } } diff --git a/tests/lib/TestCase.php b/tests/lib/TestCase.php index 17be523b5658..12e66990efda 100644 --- a/tests/lib/TestCase.php +++ b/tests/lib/TestCase.php @@ -173,7 +173,7 @@ protected function tearDown(): void { * @param array $parameters * @return mixed */ - protected static function invokePrivate($object, $methodName, array $parameters = []) { + protected static function invokePrivate($object, $methodName, array $parameters = []): mixed { if (\is_string($object)) { $className = $object; } else { From cdd98a7b799d5393e433e8c22a1a8ad46114a31c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Tue, 26 Sep 2023 00:36:00 +0200 Subject: [PATCH 09/42] fix: more php8.x deprecations --- apps/dav/lib/Server.php | 1 - apps/dav/lib/Upload/AssemblyStream.php | 7 ++- .../tests/unit/Connector/Sabre/FileTest.php | 11 ++-- .../unit/Connector/Sabre/FilesPluginTest.php | 4 ++ .../Connector/Sabre/FilesReportPluginTest.php | 21 ++++--- .../tests/Controller/OcmControllerTest.php | 2 + apps/federatedfilesharing/tests/TestCase.php | 14 +---- apps/files/lib/Command/TransferOwnership.php | 2 +- apps/files/tests/Command/ScanTest.php | 7 ++- apps/files_sharing/tests/TestCase.php | 6 -- apps/files_versions/tests/VersioningTest.php | 6 -- core/Controller/AvatarController.php | 2 +- core/Controller/LostController.php | 6 +- lib/private/Archive/ZIP.php | 2 +- lib/private/Files/FileInfo.php | 2 +- .../Files/Storage/Wrapper/Encryption.php | 4 +- lib/private/Files/Stream/Encryption.php | 2 +- lib/private/Files/View.php | 2 +- lib/private/L10N/L10N.php | 5 +- lib/private/Preview/Image.php | 2 +- lib/private/Search/Result/File.php | 10 +++ lib/private/Security/Crypto.php | 3 +- lib/private/Session/CryptoSessionData.php | 4 +- lib/private/Share20/Manager.php | 2 +- lib/private/User/Database.php | 2 +- lib/private/legacy/image.php | 4 +- lib/public/IL10N.php | 6 +- lib/public/Security/ICrypto.php | 3 +- settings/ChangePassword/Controller.php | 2 +- settings/Panels/Admin/SecurityWarning.php | 2 +- .../Core/Controller/AvatarControllerTest.php | 4 +- tests/Core/Controller/LostControllerTest.php | 33 +++++----- tests/lib/AppConfigTest.php | 2 +- tests/lib/Archive/ZIPTest.php | 4 -- tests/php-out.txt | 61 +++++++++++++++++++ 35 files changed, 155 insertions(+), 95 deletions(-) create mode 100644 tests/php-out.txt diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index 338be6e7816c..c4e5315a00df 100644 --- a/apps/dav/lib/Server.php +++ b/apps/dav/lib/Server.php @@ -179,7 +179,6 @@ public function __construct(IRequest $request, $baseUri) { $acl->principalCollectionSet = [ 'principals/users', 'principals/groups' ]; - $acl->defaultUsernamePath = 'principals/users'; $this->server->addPlugin($acl); } diff --git a/apps/dav/lib/Upload/AssemblyStream.php b/apps/dav/lib/Upload/AssemblyStream.php index 2f408f5b876a..da32e15b113d 100644 --- a/apps/dav/lib/Upload/AssemblyStream.php +++ b/apps/dav/lib/Upload/AssemblyStream.php @@ -248,9 +248,12 @@ public static function wrap(array $nodes) { 'assembly' => [ 'nodes' => $nodes] ]); - \stream_wrapper_register('assembly', '\OCA\DAV\Upload\AssemblyStream'); + $existed = \in_array("assembly", stream_get_wrappers()); + if (!$existed) { + \stream_wrapper_register('assembly', __CLASS__); + } try { - $wrapped = \fopen('assembly://', 'r', null, $context); + $wrapped = \fopen('assembly://', 'r', false, $context); } catch (\BadMethodCallException $e) { \stream_wrapper_unregister('assembly'); throw $e; diff --git a/apps/dav/tests/unit/Connector/Sabre/FileTest.php b/apps/dav/tests/unit/Connector/Sabre/FileTest.php index 20c7ec8cb72a..ef36896dd5cd 100644 --- a/apps/dav/tests/unit/Connector/Sabre/FileTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/FileTest.php @@ -262,13 +262,12 @@ public function testFileContentNotAllowedConvertedToForbidden() { ->getMock(); $view->expects($this->atLeastOnce()) ->method('resolvePath') - ->will( - $this->returnCallback( - function ($path) use ($storage) { - return [$storage, $path]; - } - ) + ->willReturnCallback( + function ($path) use ($storage) { + return [$storage, $path]; + } ); + $view->method('getRelativePath')->willReturn('test.txt'); $storage->expects($this->once()) ->method('fopen') diff --git a/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php index 61b35a748307..e8bcbc200498 100644 --- a/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php @@ -612,6 +612,10 @@ public function testAdditionalHeaders() { ->expects($this->once()) ->method('getName') ->willReturn('somefile.xml'); + $node + ->expects($this->once()) + ->method('getContentDispositionFileName') + ->willReturn('somefile.xml'); $this->tree ->expects($this->once()) diff --git a/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php index 2dda24113852..9eacbcb2e158 100644 --- a/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php @@ -81,7 +81,7 @@ public function setUp(): void { $this->server->expects($this->any()) ->method('getBaseUri') - ->will($this->returnValue('http://example.com/owncloud/remote.php/dav')); + ->willReturn('http://example.com/owncloud/remote.php/dav'); $this->groupManager = $this->getMockBuilder('\OCP\IGroupManager') ->disableOriginalConstructor() @@ -99,15 +99,15 @@ public function setUp(): void { $privateTagManager->expects($this->any()) ->method('load') ->with('files') - ->will($this->returnValue($this->privateTags)); + ->willReturn($this->privateTags); $user = $this->createMock('\OCP\IUser'); $user->expects($this->any()) ->method('getUID') - ->will($this->returnValue('testuser')); + ->willReturn('testuser'); $this->userSession->expects($this->any()) ->method('getUser') - ->will($this->returnValue($user)); + ->willReturn($user); // add FilesPlugin to test more properties $this->server->addPlugin( @@ -179,7 +179,7 @@ public function testOnReport() { $this->groupManager->expects($this->any()) ->method('isAdmin') - ->will($this->returnValue(true)); + ->willReturn(true); $this->tagMapper ->expects($this->exactly(2)) @@ -194,6 +194,7 @@ public function testOnReport() { ); $reportTargetNode = $this->createMock(\OCA\DAV\Connector\Sabre\Directory::class); + $reportTargetNode->method('getPath')->willReturn(''); $response = $this->createMock(\Sabre\HTTP\ResponseInterface::class); $response->expects($this->once()) ->method('setHeader') @@ -209,7 +210,7 @@ public function testOnReport() { $this->tree->expects($this->any()) ->method('getNodeForPath') ->with('/' . $path) - ->will($this->returnValue($reportTargetNode)); + ->willReturn($reportTargetNode); $filesNode1 = $this->createMock(\OCP\Files\Folder::class); $filesNode1->method('getId')->willReturn(111); @@ -236,17 +237,17 @@ public function testOnReport() { $this->server->expects($this->any()) ->method('getRequestUri') - ->will($this->returnValue($path)); + ->willReturn($path); $this->server->httpResponse = $response; $this->plugin->initialize($this->server); $responses = null; $this->server->expects($this->once()) ->method('generateMultiStatus') - ->will( - $this->returnCallback(function ($responsesArg) use (&$responses) { + ->willReturnCallback( + function ($responsesArg) use (&$responses) { $responses = $responsesArg; - }) + } ); $this->assertFalse($this->plugin->onReport(FilesReportPluginImplementation::REPORT_NAME, $parameters, '/' . $path)); diff --git a/apps/federatedfilesharing/tests/Controller/OcmControllerTest.php b/apps/federatedfilesharing/tests/Controller/OcmControllerTest.php index 6532d544a783..fff53ca61b20 100644 --- a/apps/federatedfilesharing/tests/Controller/OcmControllerTest.php +++ b/apps/federatedfilesharing/tests/Controller/OcmControllerTest.php @@ -115,6 +115,8 @@ protected function setUp(): void { $this->logger = $this->createMock(ILogger::class); $this->config = $this->createMock(IConfig::class); + $this->urlGenerator->method('linkToRouteAbsolute')->willReturn(''); + $this->ocmController = new OcmController( 'federatedfilesharing', $this->request, diff --git a/apps/federatedfilesharing/tests/TestCase.php b/apps/federatedfilesharing/tests/TestCase.php index 5d7ca19fd4c4..aeda7c85f262 100644 --- a/apps/federatedfilesharing/tests/TestCase.php +++ b/apps/federatedfilesharing/tests/TestCase.php @@ -25,6 +25,7 @@ use OC\Files\Filesystem; use OCA\Files\Share; use Test\Traits\UserTrait; +use OCA\Files_Sharing\SharedStorage; /** * Class Test_Files_Sharing_Base @@ -85,8 +86,6 @@ public static function tearDownAfterClass(): void { * @param string $user */ protected static function loginHelper($user) { - self::resetStorage(); - \OC_Util::tearDownFS(); \OC::$server->getUserSession()->setUser(null); \OC\Files\Filesystem::tearDown(); @@ -95,15 +94,4 @@ protected static function loginHelper($user) { \OC_Util::setupFS($user); } - - /** - * reset init status for the share storage - */ - protected static function resetStorage() { - $storage = new \ReflectionClass('\OCA\Files_Sharing\SharedStorage'); - $isInitialized = $storage->getProperty('initialized'); - $isInitialized->setAccessible(true); - $isInitialized->setValue($storage, false); - $isInitialized->setAccessible(false); - } } diff --git a/apps/files/lib/Command/TransferOwnership.php b/apps/files/lib/Command/TransferOwnership.php index a9ff6c54a9ad..ec7e26c459a9 100644 --- a/apps/files/lib/Command/TransferOwnership.php +++ b/apps/files/lib/Command/TransferOwnership.php @@ -153,7 +153,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->sourceUser = $sourceUserObject->getUID(); $this->destinationUser = $destinationUserObject->getUID(); - $this->inputPath = $input->getOption('path'); + $this->inputPath = $input->getOption('path') ?? ''; $this->inputPath = \ltrim($this->inputPath, '/'); // target user has to be ready diff --git a/apps/files/tests/Command/ScanTest.php b/apps/files/tests/Command/ScanTest.php index 9cc270d0333f..4e1fc8d310bc 100644 --- a/apps/files/tests/Command/ScanTest.php +++ b/apps/files/tests/Command/ScanTest.php @@ -91,9 +91,10 @@ class ScanTest extends TestCase { private $commandTester; /** - * @var string[] + * @var array */ private $groupsCreated = []; + private mixed $dataDir; protected function setUp(): void { if ($this->runsWithPrimaryObjectstorage()) { @@ -118,8 +119,8 @@ protected function setUp(): void { ); $this->commandTester = new CommandTester($command); - $this->scanUser1 = $this->createUser('scanuser1' . \uniqid()); - $this->scanUser2 = $this->createUser('scanuser2' . \uniqid()); + $this->scanUser1 = $this->createUser('scanuser1' . \uniqid('', true)); + $this->scanUser2 = $this->createUser('scanuser2' . \uniqid('', true)); $user1 = $this->createUser('user1'); $this->createUser('user2'); diff --git a/apps/files_sharing/tests/TestCase.php b/apps/files_sharing/tests/TestCase.php index 78e342401b2a..29bacbe45974 100644 --- a/apps/files_sharing/tests/TestCase.php +++ b/apps/files_sharing/tests/TestCase.php @@ -179,12 +179,6 @@ protected static function loginHelper($user, $create = false, $password = false) * reset init status for the share storage */ protected static function resetStorage() { - $storage = new \ReflectionClass(SharedStorage::class); - $isInitialized = $storage->getProperty('initialized'); - $isInitialized->setAccessible(true); - $isInitialized->setValue($storage, false); - $isInitialized->setAccessible(false); - $storage = new \ReflectionClass(Storage::class); $property = $storage->getProperty('localCache'); $property->setAccessible(true); diff --git a/apps/files_versions/tests/VersioningTest.php b/apps/files_versions/tests/VersioningTest.php index 3e6623e93a41..eb4b0ea67524 100644 --- a/apps/files_versions/tests/VersioningTest.php +++ b/apps/files_versions/tests/VersioningTest.php @@ -1412,12 +1412,6 @@ public static function loginHelper($user, $create = false): void { OC::$server->getUserManager()->createUser($user, $user); } - $storage = new ReflectionClass('\OCA\Files_Sharing\SharedStorage'); - $isInitialized = $storage->getProperty('initialized'); - $isInitialized->setAccessible(true); - $isInitialized->setValue($storage, false); - $isInitialized->setAccessible(false); - OC_Util::tearDownFS(); OC_User::setUserId(''); Filesystem::tearDown(); diff --git a/core/Controller/AvatarController.php b/core/Controller/AvatarController.php index 0c70e5992e77..cd72a19f27ad 100644 --- a/core/Controller/AvatarController.php +++ b/core/Controller/AvatarController.php @@ -213,7 +213,7 @@ public function postAvatar($path) { try { $image = new \OC_Image(); - $image->load($handle); + $image->loadFromFileHandle($handle); $image->fixOrientation(); // don't accept images with too big dimensions // 4k - 4096×2160 diff --git a/core/Controller/LostController.php b/core/Controller/LostController.php index 3155711e6749..ccbe084d87e4 100644 --- a/core/Controller/LostController.php +++ b/core/Controller/LostController.php @@ -166,7 +166,7 @@ public function resetform($token, $userId) { private function checkPasswordResetToken($token, $userId) { $user = $this->userManager->get($userId); - $splittedToken = \explode(':', $this->config->getUserValue($userId, 'owncloud', 'lostpassword', null)); + $splittedToken = \explode(':', $this->config->getUserValue($userId, 'owncloud', 'lostpassword', null) ?? ''); if (\count($splittedToken) !== 2) { $this->config->deleteUserValue($userId, 'owncloud', 'lostpassword'); throw new \Exception($this->l10n->t('Could not reset password because the token is invalid')); @@ -354,7 +354,7 @@ public function sendEmail($user, $token, $link) { } } - $getToken = $this->config->getUserValue($user, 'owncloud', 'lostpassword'); + $getToken = $this->config->getUserValue($user, 'owncloud', 'lostpassword') ?? ''; if ($getToken !== '') { $splittedToken = \explode(':', $getToken); if ((\count($splittedToken)) === 2 && $splittedToken[0] > ($this->timeFactory->getTime() - 60 * 5)) { @@ -383,7 +383,7 @@ public function sendEmail($user, $token, $link) { $this->mailer->send($message); } catch (\Exception $e) { throw new \Exception($this->l10n->t( - 'Couldn\'t send reset email. Please contact your administrator.' + "Couldn't send reset email. Please contact your administrator." )); } diff --git a/lib/private/Archive/ZIP.php b/lib/private/Archive/ZIP.php index 456aa14a2c38..ba7f6eda226f 100644 --- a/lib/private/Archive/ZIP.php +++ b/lib/private/Archive/ZIP.php @@ -45,7 +45,7 @@ class ZIP extends Archive { public function __construct($source) { $this->path=$source; $this->zip=new ZipArchive(); - if ($this->zip->open($source, ZipArchive::OVERWRITE)) { + if ($this->zip->open($source, ZipArchive::CREATE)) { } else { \OCP\Util::writeLog('files_archive', 'Error while opening archive '.$source, \OCP\Util::WARN); } diff --git a/lib/private/Files/FileInfo.php b/lib/private/Files/FileInfo.php index 374c159c94e6..a2ff1f7f16f9 100644 --- a/lib/private/Files/FileInfo.php +++ b/lib/private/Files/FileInfo.php @@ -343,6 +343,6 @@ public function addSubEntry($data, $entryPath) { * @inheritdoc */ public function getChecksum() { - return $this->data['checksum']; + return $this->data['checksum'] ?? ''; } } diff --git a/lib/private/Files/Storage/Wrapper/Encryption.php b/lib/private/Files/Storage/Wrapper/Encryption.php index 906568d088da..b859608dd70f 100644 --- a/lib/private/Files/Storage/Wrapper/Encryption.php +++ b/lib/private/Files/Storage/Wrapper/Encryption.php @@ -988,9 +988,9 @@ protected function getHeaderSize($path) { $path = $realFile; } } - $firstBlock = $this->readFirstBlock($path); + $firstBlock = $this->readFirstBlock($path) ?? ''; - if (\substr($firstBlock, 0, \strlen(Util::HEADER_START)) === Util::HEADER_START) { + if (str_starts_with($firstBlock, Util::HEADER_START)) { $headerSize = $this->util->getHeaderSize(); } diff --git a/lib/private/Files/Stream/Encryption.php b/lib/private/Files/Stream/Encryption.php index fe1650e04582..02895f11cb63 100644 --- a/lib/private/Files/Stream/Encryption.php +++ b/lib/private/Files/Stream/Encryption.php @@ -199,7 +199,7 @@ public static function wrap( protected static function wrapSource($source, $context = [], $protocol = null, $class = null, $mode = 'r+') { try { \stream_wrapper_register($protocol, $class); - \rewinddir($source); + \rewind($source); $wrapped = \fopen($protocol . '://', $mode, false, $context); } catch (\BadMethodCallException $e) { \stream_wrapper_unregister($protocol); diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php index 9bd55a420c3b..4aa5581c3939 100644 --- a/lib/private/Files/View.php +++ b/lib/private/Files/View.php @@ -1972,7 +1972,7 @@ public function verifyPath($path, $fileName) { * @return string[] */ private function getParents($path) { - $path = \trim($path, '/'); + $path = \trim($path ?? '', '/'); if (!$path) { return []; } diff --git a/lib/private/L10N/L10N.php b/lib/private/L10N/L10N.php index b4ec20eaa331..cb88988d823f 100644 --- a/lib/private/L10N/L10N.php +++ b/lib/private/L10N/L10N.php @@ -72,13 +72,14 @@ public function getLanguageCode() { * Translating * * @param string $text The text we need a translation for - * @param array $parameters default:array() Parameters for sprintf + * @param array|mixed $parameters default:array() Parameters for sprintf * @return string Translation or the same text * * Returns the translation. If no translation is found, $text will be * returned. */ - public function t(string $text, $parameters = []) { + public function t(string $text, $parameters = []): string { + $parameters = \is_array($parameters) ? $parameters : [$parameters]; return (string) new \OC_L10N_String($this, $text, $parameters); } diff --git a/lib/private/Preview/Image.php b/lib/private/Preview/Image.php index 302ff2ff6a7a..a1c0b1cffa4e 100644 --- a/lib/private/Preview/Image.php +++ b/lib/private/Preview/Image.php @@ -41,7 +41,7 @@ public function getThumbnail(File $file, $maxX, $maxY, $scalingUp) { } $image = new \OC_Image(); $handle = $file->fopen('r'); - $image->load($handle); + $image->loadFromFileHandle($handle); $image->fixOrientation(); if (!$this->validateImageDimensions($image)) { return false; diff --git a/lib/private/Search/Result/File.php b/lib/private/Search/Result/File.php index 371e3f117617..17df1bd8efd6 100644 --- a/lib/private/Search/Result/File.php +++ b/lib/private/Search/Result/File.php @@ -67,6 +67,16 @@ class File extends \OCP\Search\Result { */ public $permissions; + /** + * @var float + */ + public $score; + + /** + * @var string[] + */ + public $highlights; + /** * Create a new file search result * @param FileInfo $data file data given by provider diff --git a/lib/private/Security/Crypto.php b/lib/private/Security/Crypto.php index 9e0ac67e7a4d..8ecac526bc43 100644 --- a/lib/private/Security/Crypto.php +++ b/lib/private/Security/Crypto.php @@ -112,12 +112,13 @@ public function encrypt($plaintext, $password = '') { /** * Decrypts a value and verifies the HMAC (Encrypt-Then-Mac) + * * @param string $authenticatedCiphertext * @param string $password Password to encrypt, if not specified the secret from config.php will be taken * @return string plaintext * @throws \Exception If the HMAC does not match */ - public function decrypt($authenticatedCiphertext, $password = '') { + public function decrypt(string $authenticatedCiphertext, string $password = ''): string { if ($password === '') { $password = $this->config->getSystemValue('secret'); } diff --git a/lib/private/Session/CryptoSessionData.php b/lib/private/Session/CryptoSessionData.php index 0cc99a000e78..0b6cbe95a255 100644 --- a/lib/private/Session/CryptoSessionData.php +++ b/lib/private/Session/CryptoSessionData.php @@ -69,12 +69,12 @@ public function __destruct() { } protected function initializeSession(): void { - $encryptedSessionData = $this->session->get(self::encryptedSessionName); + $encryptedSessionData = $this->session->get(self::encryptedSessionName) ?? ''; try { $this->sessionValues = \json_decode( $this->crypto->decrypt($encryptedSessionData, $this->passphrase), true - ); + ) ?? []; } catch (\Exception) { $this->sessionValues = []; } diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index 22283779bc9b..4918a7259c99 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -1199,7 +1199,7 @@ public function deleteShare(\OCP\Share\IShare $share) { $deletedShares[] = $share; //Format hook info - $formattedDeletedShares = \array_map('self::formatUnshareHookParams', $deletedShares); + $formattedDeletedShares = \array_map(self::formatUnshareHookParams(...), $deletedShares); $hookParams['deletedShares'] = $formattedDeletedShares; diff --git a/lib/private/User/Database.php b/lib/private/User/Database.php index 2e413ba37d7e..d5313a53b5a5 100644 --- a/lib/private/User/Database.php +++ b/lib/private/User/Database.php @@ -167,7 +167,7 @@ public function setDisplayName($uid, $displayName) { */ public function getDisplayName($uid) { $this->loadUser($uid); - if (\strlen($this->cache[$uid]['displayname']) === 0) { + if (($this->cache[$uid]['displayname'] ?? '') === '') { return $uid; } diff --git a/lib/private/legacy/image.php b/lib/private/legacy/image.php index c2fb40810bc8..43ab84f9a17e 100644 --- a/lib/private/legacy/image.php +++ b/lib/private/legacy/image.php @@ -509,10 +509,10 @@ public function fixOrientation() { /** * Loads an image from a local file, a base64 encoded string or a resource created by an imagecreate* function. * - * @param resource|string $imageRef The path to a local file, a base64 encoded string or a resource created by an imagecreate* function or a file resource (file handle ). + * @param GdImage|resource|string $imageRef The path to a local file, a base64 encoded string or a resource created by an imagecreate* function or a file resource (file handle ). * @return GdImage|false An image resource or false on error */ - public function load(\GdImage|string $imageRef) { + public function load($imageRef) { if ($imageRef instanceof \GdImage) { $this->resource = $imageRef; return $this->resource; diff --git a/lib/public/IL10N.php b/lib/public/IL10N.php index f871ee57f592..a8cc30f5a23c 100644 --- a/lib/public/IL10N.php +++ b/lib/public/IL10N.php @@ -46,14 +46,14 @@ interface IL10N { * Translating * * @param string $text The text we need a translation for - * @param array $parameters default:array() Parameters for sprintf - * @return \OC_L10N_String Translation or the same text + * @param array|mixed $parameters default:array() Parameters for sprintf + * @return string Translation or the same text * * Returns the translation. If no translation is found, $text will be * returned. * @since 6.0.0 */ - public function t(string $text, $parameters = []); + public function t(string $text, $parameters = []): string; /** * Translating diff --git a/lib/public/Security/ICrypto.php b/lib/public/Security/ICrypto.php index 4a905dc292b5..405c9e349d45 100644 --- a/lib/public/Security/ICrypto.php +++ b/lib/public/Security/ICrypto.php @@ -53,11 +53,12 @@ public function encrypt($plaintext, $password = ''); /** * Decrypts a value and verifies the HMAC (Encrypt-Then-Mac) + * * @param string $authenticatedCiphertext * @param string $password Password to encrypt, if not specified the secret from config.php will be taken * @return string plaintext * @throws \Exception If the HMAC does not match * @since 8.0.0 */ - public function decrypt($authenticatedCiphertext, $password = ''); + public function decrypt(string $authenticatedCiphertext, string $password = ''): string; } diff --git a/settings/ChangePassword/Controller.php b/settings/ChangePassword/Controller.php index 487777b718c3..ea367c476e72 100644 --- a/settings/ChangePassword/Controller.php +++ b/settings/ChangePassword/Controller.php @@ -213,7 +213,7 @@ private static function sendNotificationMail($username) { $mailer->send($message); } catch (\Exception $e) { throw new \Exception($l10n->t( - 'Couldn\'t send reset email. Please contact your administrator.' + "Couldn't send reset email. Please contact your administrator." )); } } diff --git a/settings/Panels/Admin/SecurityWarning.php b/settings/Panels/Admin/SecurityWarning.php index 9eb7f76c9ef9..6cc43cc00029 100644 --- a/settings/Panels/Admin/SecurityWarning.php +++ b/settings/Panels/Admin/SecurityWarning.php @@ -96,7 +96,7 @@ public function getPanel() { } $template->assign('OutdatedCacheWarning', $outdatedCaches); $template->assign('has_fileinfo', $this->helper->fileInfoLoaded()); - $databaseOverload = (\strpos($this->config->getSystemValue('dbtype'), 'sqlite') !== false); + $databaseOverload = (\strpos($this->config->getSystemValue('dbtype') ?? '', 'sqlite') !== false); $template->assign('databaseOverload', $databaseOverload); if ($this->lockingProvider instanceof NoopLockingProvider) { $template->assign('fileLockingType', 'none'); diff --git a/tests/Core/Controller/AvatarControllerTest.php b/tests/Core/Controller/AvatarControllerTest.php index 64da05211eb1..db365b08b08f 100644 --- a/tests/Core/Controller/AvatarControllerTest.php +++ b/tests/Core/Controller/AvatarControllerTest.php @@ -276,7 +276,7 @@ public function testPostAvatarNoPathOrImage() { */ public function testPostAvatarFile() { //Create temp file - $fileName = \tempnam(null, "avatarTest"); + $fileName = \tempnam('', "avatarTest"); $copyRes = \copy(\OC::$SERVERROOT.'/tests/data/testimage.jpg', $fileName); $this->assertTrue($copyRes); @@ -329,7 +329,7 @@ public function testPostAvatarInvalidFile() { */ public function testPostAvatarFileGif() { //Create temp file - $fileName = \tempnam(null, "avatarTest"); + $fileName = \tempnam('', "avatarTest"); $copyRes = \copy(\OC::$SERVERROOT.'/tests/data/testimage.gif', $fileName); $this->assertTrue($copyRes); diff --git a/tests/Core/Controller/LostControllerTest.php b/tests/Core/Controller/LostControllerTest.php index f4711ca8d692..79e130906b45 100644 --- a/tests/Core/Controller/LostControllerTest.php +++ b/tests/Core/Controller/LostControllerTest.php @@ -21,8 +21,10 @@ namespace Tests\Core\Controller; +use Exception; use OC\Core\Controller\LostController; use OC\User\Session; +use OC_Defaults; use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Utility\ITimeFactory; use OCP\IConfig; @@ -34,7 +36,9 @@ use OCP\IUserManager; use OCP\Mail\IMailer; use OCP\Security\ISecureRandom; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use function vsprintf; /** * Class LostControllerTest @@ -46,25 +50,25 @@ class LostControllerTest extends TestCase { private $lostController; /** @var IUser */ private $existingUser; - /** @var IURLGenerator | PHPUnit\Framework\MockObject\MockObject */ + /** @var IURLGenerator | MockObject */ private $urlGenerator; /** @var IL10N */ private $l10n; - /** @var IUserManager | PHPUnit\Framework\MockObject\MockObject */ + /** @var IUserManager | MockObject */ private $userManager; - /** @var \OC_Defaults */ + /** @var OC_Defaults */ private $defaults; - /** @var IConfig | PHPUnit\Framework\MockObject\MockObject */ + /** @var IConfig | MockObject */ private $config; - /** @var IMailer | PHPUnit\Framework\MockObject\MockObject */ + /** @var IMailer | MockObject */ private $mailer; - /** @var ISecureRandom | PHPUnit\Framework\MockObject\MockObject */ + /** @var ISecureRandom | MockObject */ private $secureRandom; - /** @var ITimeFactory | PHPUnit\Framework\MockObject\MockObject */ + /** @var ITimeFactory | MockObject */ private $timeFactory; - /** @var IRequest | PHPUnit\Framework\MockObject\MockObject */ + /** @var IRequest | MockObject */ private $request; - /** @var ILogger | PHPUnit\Framework\MockObject\MockObject*/ + /** @var ILogger | MockObject */ private $logger; /** @var Session */ private $userSession; @@ -96,7 +100,7 @@ protected function setUp(): void { ->expects($this->any()) ->method('t') ->will($this->returnCallback(function ($text, $parameters = []) { - return \vsprintf($text, $parameters); + return vsprintf($text, $parameters); })); $this->defaults = $this->getMockBuilder('\OC_Defaults') ->disableOriginalConstructor()->getMock(); @@ -337,7 +341,7 @@ public function testExceptionDuringUserLookup() { ->expects($this->once()) ->method('get') ->with($user) - ->willThrowException(new \Exception("User not found")); + ->willThrowException(new Exception("User not found")); $expectedResponse = [ 'status' => 'success' ]; @@ -580,15 +584,16 @@ public function testEmailCantSendException() { $this->mailer ->expects($this->once()) ->method('createMessage') - ->will($this->returnValue($message)); + ->willReturn($message); $this->mailer ->expects($this->once()) ->method('send') ->with($message) - ->will($this->throwException(new \Exception())); + ->will($this->throwException(new Exception())); $response = $this->lostController->email('ExistingUser'); - $expectedResponse = ['status' => 'error', 'msg' => 'Couldn\'t send reset email. Please contact your administrator.']; + # TODO: double check behavior change in frontends + $expectedResponse = ['status' => 'error', 'msg' => 'Couldn't send reset email. Please contact your administrator.']; $this->assertSame($expectedResponse, $response); } diff --git a/tests/lib/AppConfigTest.php b/tests/lib/AppConfigTest.php index ef533d348405..19424fe7605a 100644 --- a/tests/lib/AppConfigTest.php +++ b/tests/lib/AppConfigTest.php @@ -154,10 +154,10 @@ public function testGetApps(): void { $config = new \OC\AppConfig(\OC::$server->getDatabaseConnection()); $this->assertEquals([ + '123456', 'anotherapp', 'someapp', 'testapp', - '123456', ], $config->getApps()); }); } diff --git a/tests/lib/Archive/ZIPTest.php b/tests/lib/Archive/ZIPTest.php index 93909cbca67b..b628289e5b7c 100644 --- a/tests/lib/Archive/ZIPTest.php +++ b/tests/lib/Archive/ZIPTest.php @@ -11,10 +11,6 @@ use OC\Archive\ZIP; class ZIPTest extends TestBase { - protected function setUp(): void { - parent::setUp(); - } - protected function getExisting() { return new ZIP($this->getArchiveTestDataDir() . '/data.zip'); } diff --git a/tests/php-out.txt b/tests/php-out.txt new file mode 100644 index 000000000000..25be606b7e97 --- /dev/null +++ b/tests/php-out.txt @@ -0,0 +1,61 @@ +PHPUnit 9.6.13 by Sebastian Bergmann and contributors. + +Runtime: PHP 8.2.10 +Configuration: phpunit-autotest.xml + +........................................................... 59 / 10771 ( 0%) +...........................S............................... 118 / 10771 ( 1%) +........................................................... 177 / 10771 ( 1%) +........................................................... 236 / 10771 ( 2%) +....................F...................................... 295 / 10771 ( 2%) +........................................................... 354 / 10771 ( 3%) +........................................................... 413 / 10771 ( 3%) +........................................................... 472 / 10771 ( 4%) +........................................................... 531 / 10771 ( 4%) +........................................................... 590 / 10771 ( 5%) +........................................................... 649 / 10771 ( 6%) +........................................................... 708 / 10771 ( 6%) +........................................................... 767 / 10771 ( 7%) +.....................F..F.F..F............................. 826 / 10771 ( 7%) +........................................................... 885 / 10771 ( 8%) +........................................................... 944 / 10771 ( 8%) +........................................................... 1003 / 10771 ( 9%) +........................................................... 1062 / 10771 ( 9%) +.......................................S........SS......... 1121 / 10771 ( 10%) +........................................................... 1180 / 10771 ( 10%) +........................................................... 1239 / 10771 ( 11%) +........................................................... 1298 / 10771 ( 12%) +........................................................... 1357 / 10771 ( 12%) +........................................................... 1416 / 10771 ( 13%) +........................................................... 1475 / 10771 ( 13%) +........................................................... 1534 / 10771 ( 14%) +.....................S..................................... 1593 / 10771 ( 14%) +S.......................................................... 1652 / 10771 ( 15%) +......................................S.................... 1711 / 10771 ( 15%) +..........................S................................ 1770 / 10771 ( 16%) +........................................................... 1829 / 10771 ( 16%) +........................................................... 1888 / 10771 ( 17%) +........................................................... 1947 / 10771 ( 18%) +........................................................... 2006 / 10771 ( 18%) +....................................S...................... 2065 / 10771 ( 19%) +........................................................... 2124 / 10771 ( 19%) +........................................................... 2183 / 10771 ( 20%) +........................................................... 2242 / 10771 ( 20%) +........................................................... 2301 / 10771 ( 21%) +............S.............................................. 2360 / 10771 ( 21%) +........................................................... 2419 / 10771 ( 22%) +........................................................... 2478 / 10771 ( 23%) +........................................................... 2537 / 10771 ( 23%) +........................................................... 2596 / 10771 ( 24%) +........................................................... 2655 / 10771 ( 24%) +........................................................... 2714 / 10771 ( 25%) +........................................................... 2773 / 10771 ( 25%) +........................................................... 2832 / 10771 ( 26%) +........................................................... 2891 / 10771 ( 26%) +........................................................... 2950 / 10771 ( 27%) +........................................................... 3009 / 10771 ( 27%) +...................................S....................... 3068 / 10771 ( 28%) +........................................................... 3127 / 10771 ( 29%) +........................................................... 3186 / 10771 ( 29%) +..........S................................................ 3245 / 10771 ( 30%) +..................................... \ No newline at end of file From 9ae76fc26f5e2b1f766b6c1ffaa43d09415d7dce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Tue, 26 Sep 2023 11:44:13 +0200 Subject: [PATCH 10/42] fix: preview generation --- lib/private/Preview.php | 2 +- lib/private/Preview/Bitmap.php | 2 +- tests/php-out.txt | 61 ---------------------------------- 3 files changed, 2 insertions(+), 63 deletions(-) delete mode 100644 tests/php-out.txt diff --git a/lib/private/Preview.php b/lib/private/Preview.php index f409addfdbf3..e8d6fefae247 100644 --- a/lib/private/Preview.php +++ b/lib/private/Preview.php @@ -1116,7 +1116,7 @@ private function generatePreview() { $previewProviders = \OC::$server->getPreviewManager() ->getProviders(); foreach ($previewProviders as $supportedMimeType => $providers) { - if (!\preg_match($supportedMimeType, $this->mimeType)) { + if (!\preg_match($supportedMimeType, $this->mimeType ?? '')) { continue; } diff --git a/lib/private/Preview/Bitmap.php b/lib/private/Preview/Bitmap.php index e004f93c2cd1..b7f783c5cb38 100644 --- a/lib/private/Preview/Bitmap.php +++ b/lib/private/Preview/Bitmap.php @@ -58,7 +58,7 @@ public function getThumbnail(File $file, $maxX, $maxY, $scalingUp) { //new bitmap image object $image = new \OC_Image(); - $image->loadFromData($bp); + $image->loadFromData((string)$bp); //check if image object is valid return $image->valid() ? $image : false; } diff --git a/tests/php-out.txt b/tests/php-out.txt deleted file mode 100644 index 25be606b7e97..000000000000 --- a/tests/php-out.txt +++ /dev/null @@ -1,61 +0,0 @@ -PHPUnit 9.6.13 by Sebastian Bergmann and contributors. - -Runtime: PHP 8.2.10 -Configuration: phpunit-autotest.xml - -........................................................... 59 / 10771 ( 0%) -...........................S............................... 118 / 10771 ( 1%) -........................................................... 177 / 10771 ( 1%) -........................................................... 236 / 10771 ( 2%) -....................F...................................... 295 / 10771 ( 2%) -........................................................... 354 / 10771 ( 3%) -........................................................... 413 / 10771 ( 3%) -........................................................... 472 / 10771 ( 4%) -........................................................... 531 / 10771 ( 4%) -........................................................... 590 / 10771 ( 5%) -........................................................... 649 / 10771 ( 6%) -........................................................... 708 / 10771 ( 6%) -........................................................... 767 / 10771 ( 7%) -.....................F..F.F..F............................. 826 / 10771 ( 7%) -........................................................... 885 / 10771 ( 8%) -........................................................... 944 / 10771 ( 8%) -........................................................... 1003 / 10771 ( 9%) -........................................................... 1062 / 10771 ( 9%) -.......................................S........SS......... 1121 / 10771 ( 10%) -........................................................... 1180 / 10771 ( 10%) -........................................................... 1239 / 10771 ( 11%) -........................................................... 1298 / 10771 ( 12%) -........................................................... 1357 / 10771 ( 12%) -........................................................... 1416 / 10771 ( 13%) -........................................................... 1475 / 10771 ( 13%) -........................................................... 1534 / 10771 ( 14%) -.....................S..................................... 1593 / 10771 ( 14%) -S.......................................................... 1652 / 10771 ( 15%) -......................................S.................... 1711 / 10771 ( 15%) -..........................S................................ 1770 / 10771 ( 16%) -........................................................... 1829 / 10771 ( 16%) -........................................................... 1888 / 10771 ( 17%) -........................................................... 1947 / 10771 ( 18%) -........................................................... 2006 / 10771 ( 18%) -....................................S...................... 2065 / 10771 ( 19%) -........................................................... 2124 / 10771 ( 19%) -........................................................... 2183 / 10771 ( 20%) -........................................................... 2242 / 10771 ( 20%) -........................................................... 2301 / 10771 ( 21%) -............S.............................................. 2360 / 10771 ( 21%) -........................................................... 2419 / 10771 ( 22%) -........................................................... 2478 / 10771 ( 23%) -........................................................... 2537 / 10771 ( 23%) -........................................................... 2596 / 10771 ( 24%) -........................................................... 2655 / 10771 ( 24%) -........................................................... 2714 / 10771 ( 25%) -........................................................... 2773 / 10771 ( 25%) -........................................................... 2832 / 10771 ( 26%) -........................................................... 2891 / 10771 ( 26%) -........................................................... 2950 / 10771 ( 27%) -........................................................... 3009 / 10771 ( 27%) -...................................S....................... 3068 / 10771 ( 28%) -........................................................... 3127 / 10771 ( 29%) -........................................................... 3186 / 10771 ( 29%) -..........S................................................ 3245 / 10771 ( 30%) -..................................... \ No newline at end of file From 694476afd7666053742a2e15cbbe11ec5da3d4e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Tue, 26 Sep 2023 12:59:02 +0200 Subject: [PATCH 11/42] fix: more fixes .... --- apps/dav/tests/unit/CalDAV/CalendarTest.php | 4 ++-- .../lib/Controller/RequestHandlerController.php | 2 +- .../lib/FederatedShareProvider.php | 2 +- .../tests/Panels/GeneralPersonalPanelTest.php | 4 ++++ .../Controller/Share20OcsControllerTest.php | 1 + lib/private/AppFramework/Http/Request.php | 16 ++++++++-------- lib/private/Files/Stream/Encryption.php | 3 +++ lib/private/Server.php | 4 +--- lib/public/IRequest.php | 8 ++++---- tests/lib/Repair/RepairMimeTypesTest.php | 15 +++++++++------ 10 files changed, 34 insertions(+), 25 deletions(-) diff --git a/apps/dav/tests/unit/CalDAV/CalendarTest.php b/apps/dav/tests/unit/CalDAV/CalendarTest.php index 4cdbfe8a517f..a5042f271840 100644 --- a/apps/dav/tests/unit/CalDAV/CalendarTest.php +++ b/apps/dav/tests/unit/CalDAV/CalendarTest.php @@ -42,9 +42,9 @@ public function setUp(): void { $this->l10n ->expects($this->any()) ->method('t') - ->will($this->returnCallback(function ($text, $parameters = []) { + ->willReturnCallback(function ($text, $parameters = []) { return \vsprintf($text, $parameters); - })); + }); } public function testDelete() { diff --git a/apps/federatedfilesharing/lib/Controller/RequestHandlerController.php b/apps/federatedfilesharing/lib/Controller/RequestHandlerController.php index a67094929978..df84d9868ac8 100644 --- a/apps/federatedfilesharing/lib/Controller/RequestHandlerController.php +++ b/apps/federatedfilesharing/lib/Controller/RequestHandlerController.php @@ -111,7 +111,7 @@ public function createShare() { null ); - if (\strlen($token) > 128) { + if ($token === null || \strlen($token) > 128) { throw new BadRequestException('Token too long'); } diff --git a/apps/federatedfilesharing/lib/FederatedShareProvider.php b/apps/federatedfilesharing/lib/FederatedShareProvider.php index 20cf0d4d9e6f..0519cd7ffc79 100644 --- a/apps/federatedfilesharing/lib/FederatedShareProvider.php +++ b/apps/federatedfilesharing/lib/FederatedShareProvider.php @@ -728,7 +728,7 @@ public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offs * @inheritdoc */ public function getShareById($id, $recipientId = null) { - if (!ctype_digit($id)) { + if (!ctype_digit((string)$id)) { // share id is defined as a field of type integer // if someone calls the API asking for a share id like "abc" or "42.1" // then there is no point trying to query the database, diff --git a/apps/federatedfilesharing/tests/Panels/GeneralPersonalPanelTest.php b/apps/federatedfilesharing/tests/Panels/GeneralPersonalPanelTest.php index 602ef9c4f0b2..d5e6e5c92671 100644 --- a/apps/federatedfilesharing/tests/Panels/GeneralPersonalPanelTest.php +++ b/apps/federatedfilesharing/tests/Panels/GeneralPersonalPanelTest.php @@ -49,6 +49,10 @@ class GeneralPersonalPanelTest extends \Test\TestCase { public function setUp(): void { parent::setUp(); $this->l = $this->getMockBuilder(IL10N::class)->getMock(); + $this->l->method('t')->willReturnCallback(function ($text, $parameters = []) { + return \vsprintf($text, $parameters); + }); + $this->urlGenerator = $this->getMockBuilder(IURLGenerator::class)->getMock(); $this->userSession = $this->getMockBuilder(IUserSession::class)->getMock(); $this->request = $this->getMockBuilder(IRequest::class)->getMock(); diff --git a/apps/files_sharing/tests/Controller/Share20OcsControllerTest.php b/apps/files_sharing/tests/Controller/Share20OcsControllerTest.php index 9a671357c788..91ff881663de 100644 --- a/apps/files_sharing/tests/Controller/Share20OcsControllerTest.php +++ b/apps/files_sharing/tests/Controller/Share20OcsControllerTest.php @@ -3843,6 +3843,7 @@ public function testPermissionsViaAttributes($expectedPermission, $attributes) { ['attributes', null, $attributes], ['shareType', $this->any(), Share::SHARE_TYPE_USER], ['shareWith', null, 'validUser'], + ['expireDate', '', ''] ]); $userFolder = $this->createMock('\OCP\Files\Folder'); diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php index 72e2848fe3c2..74f7ca082609 100644 --- a/lib/private/AppFramework/Http/Request.php +++ b/lib/private/AppFramework/Http/Request.php @@ -324,10 +324,10 @@ public function getHeader(string $name): ?string { * 1. URL parameters * 2. POST parameters * 3. GET parameters - * @param mixed $default If the key is not found, this value will be returned + * @param mixed|null $default If the key is not found, this value will be returned * @return mixed the content of the array */ - public function getParam($key, $default = null) { + public function getParam(string $key, mixed $default = null): mixed { return $this->parameters[$key] ?? $default; } @@ -336,7 +336,7 @@ public function getParam($key, $default = null) { * (as GET or POST) or through the URL by the route * @return array the array with all parameters */ - public function getParams() { + public function getParams(): array { return $this->parameters; } @@ -344,7 +344,7 @@ public function getParams() { * Returns the method of the request * @return string the method of the request (POST, GET, etc) */ - public function getMethod() { + public function getMethod(): string { return $this->method; } @@ -391,8 +391,8 @@ protected function getContent() { if ($this->method === 'PUT' && $this->getHeader('Content-Length') !== 0 && $this->getHeader('Content-Length') !== null - && \strpos($this->getHeader('Content-Type') ?? '', 'application/x-www-form-urlencoded') === false - && \strpos($this->getHeader('Content-Type') ?? '', 'application/json') === false + && !str_contains($this->getHeader('Content-Type') ?? '', 'application/x-www-form-urlencoded') + && !str_contains($this->getHeader('Content-Type') ?? '', 'application/json') ) { if ($this->content === false) { throw new \LogicException( @@ -431,7 +431,7 @@ protected function decodeContent() { // or post correctly } elseif ($this->method !== 'GET' && $this->method !== 'POST' - && \strpos($this->getHeader('Content-Type') ?? '', 'application/x-www-form-urlencoded') !== false) { + && str_contains($this->getHeader('Content-Type') ?? '', 'application/x-www-form-urlencoded')) { \parse_str(\file_get_contents($this->inputStream), $params); if (\is_array($params)) { $this->items['params'] = $params; @@ -709,7 +709,7 @@ public function getPathInfo() { */ public function getScriptName() { $name = $this->server['SCRIPT_NAME']; - $overwriteWebRoot = $this->config->getSystemValue('overwritewebroot'); + $overwriteWebRoot = $this->config->getSystemValue('overwritewebroot') ?? ''; if ($overwriteWebRoot !== '' && $this->isOverwriteCondition()) { // FIXME: This code is untestable due to __DIR__, also that hardcoded path is really dangerous $serverRoot = \str_replace('\\', '/', \substr(__DIR__, 0, -\strlen('lib/private/appframework/http/'))); diff --git a/lib/private/Files/Stream/Encryption.php b/lib/private/Files/Stream/Encryption.php index 02895f11cb63..9c3244c2bd91 100644 --- a/lib/private/Files/Stream/Encryption.php +++ b/lib/private/Files/Stream/Encryption.php @@ -99,6 +99,9 @@ class Encryption extends Wrapper { /** @var array */ protected $expectedContextProperties; + /** @var string - keep this for property access in L224*/ + protected $sourceFileOfRename; + public function __construct() { $this->expectedContextProperties = [ 'source', diff --git a/lib/private/Server.php b/lib/private/Server.php index 46172e9f0d97..3cc8bfa8cd78 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -730,9 +730,7 @@ public function __construct($webRoot, \OC\Config $config) { 'server' => $_SERVER, 'env' => $_ENV, 'cookies' => $_COOKIE, - 'method' => (isset($_SERVER, $_SERVER['REQUEST_METHOD'])) - ? $_SERVER['REQUEST_METHOD'] - : null, + 'method' => $_SERVER['REQUEST_METHOD'] ?? 'GET', 'urlParams' => $urlParams, ], $this->getSecureRandom(), diff --git a/lib/public/IRequest.php b/lib/public/IRequest.php index 31987599591e..27b28a297a49 100644 --- a/lib/public/IRequest.php +++ b/lib/public/IRequest.php @@ -80,11 +80,11 @@ public function getHeader(string $name): ?string; * 1. URL parameters * 2. POST parameters * 3. GET parameters - * @param mixed $default If the key is not found, this value will be returned + * @param mixed|null $default If the key is not found, this value will be returned * @return mixed the content of the array * @since 6.0.0 */ - public function getParam($key, $default = null); + public function getParam(string $key, mixed $default = null): mixed; /** * Returns all params that were received, be it from the request @@ -94,7 +94,7 @@ public function getParam($key, $default = null); * @return array the array with all parameters * @since 6.0.0 */ - public function getParams(); + public function getParams(): array; /** * Returns the method of the request @@ -102,7 +102,7 @@ public function getParams(); * @return string the method of the request (POST, GET, etc) * @since 6.0.0 */ - public function getMethod(); + public function getMethod(): string; /** * Shortcut for accessing an uploaded file through the $_FILES array diff --git a/tests/lib/Repair/RepairMimeTypesTest.php b/tests/lib/Repair/RepairMimeTypesTest.php index 4e92440a7806..e922543b727d 100644 --- a/tests/lib/Repair/RepairMimeTypesTest.php +++ b/tests/lib/Repair/RepairMimeTypesTest.php @@ -8,11 +8,14 @@ */ namespace Test\Repair; +use OC; use OC\Files\Storage\Temporary; +use OC\Repair\RepairMimeTypes; use OCP\Files\IMimeTypeLoader; use OCP\IConfig; use OCP\Migration\IOutput; use OCP\Migration\IRepairStep; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; /** @@ -35,10 +38,10 @@ class RepairMimeTypesTest extends TestCase { protected function setUp(): void { parent::setUp(); - $this->mimetypeLoader = \OC::$server->getMimeTypeLoader(); + $this->mimetypeLoader = OC::$server->getMimeTypeLoader(); - /** @var IConfig | \PHPUnit\Framework\MockObject\MockObject $config */ - $config = $this->getMockBuilder('OCP\IConfig') + /** @var IConfig | MockObject $config */ + $config = $this->getMockBuilder(IConfig::class) ->disableOriginalConstructor() ->getMock(); $config->expects($this->any()) @@ -46,9 +49,9 @@ protected function setUp(): void { ->with('version') ->willReturn('8.0.0.0'); - $this->storage = new \OC\Files\Storage\Temporary([]); + $this->storage = new Temporary([]); - $this->repair = new \OC\Repair\RepairMimeTypes($config); + $this->repair = new RepairMimeTypes($config); } protected function tearDown(): void { @@ -107,7 +110,7 @@ private function getMimeTypeIdFromDB($mimeType) { private function renameMimeTypes($currentMimeTypes, $fixedMimeTypes) { $this->addEntries($currentMimeTypes); - /** @var IOutput | \PHPUnit\Framework\MockObject\MockObject $outputMock */ + /** @var IOutput | MockObject $outputMock */ $outputMock = $this->getMockBuilder('\OCP\Migration\IOutput') ->disableOriginalConstructor() ->getMock(); From 3f52ece4d690bf324e612f9a7e73c74d00521c5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Wed, 27 Sep 2023 12:49:25 +0200 Subject: [PATCH 12/42] fix: ShareTest and MemcacheLockingProvider --- lib/private/Lock/MemcacheLockingProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/private/Lock/MemcacheLockingProvider.php b/lib/private/Lock/MemcacheLockingProvider.php index 27bd5b66f372..8682930bc0da 100644 --- a/lib/private/Lock/MemcacheLockingProvider.php +++ b/lib/private/Lock/MemcacheLockingProvider.php @@ -55,7 +55,7 @@ private function setTTL($path) { public function isLocked($path, $type) { $lockValue = $this->memcache->get($path); if ($type === self::LOCK_SHARED) { - return $lockValue > 0; + return (int)($lockValue) > 0; } if ($type === self::LOCK_EXCLUSIVE) { From 743c8aaeb9c6a40fbc9bc855246481b7173d8ef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Wed, 27 Sep 2023 13:02:44 +0200 Subject: [PATCH 13/42] fix: drop ext-apc - this is unused for decades --- lib/private/Memcache/APCu.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/private/Memcache/APCu.php b/lib/private/Memcache/APCu.php index a3e045b007f0..b14c53763767 100644 --- a/lib/private/Memcache/APCu.php +++ b/lib/private/Memcache/APCu.php @@ -58,7 +58,8 @@ public function remove($key) { public function clear($prefix = '') { $ns = $this->getPrefix() . $prefix; $ns = \preg_quote($ns, '/'); - $iter = new \APCuIterator('/^' . $ns . '/', APC_ITER_KEY); + $iter = new \APCUIterator('/^' . $ns . '/', APC_ITER_KEY); + return \apcu_delete($iter); } From 4af82eb3558fe0c3f3bde92bafe10d207bcabec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Thu, 28 Sep 2023 12:34:21 +0200 Subject: [PATCH 14/42] fix: cache tests + usage of TestCase::getUniqueID() --- lib/private/Memcache/Redis.php | 12 ++- .../Core/Command/Config/App/SetConfigTest.php | 4 +- .../Command/Config/System/SetConfigTest.php | 4 +- tests/lib/Memcache/APCuTest.php | 7 +- tests/lib/Memcache/ArrayCacheTest.php | 4 +- tests/lib/Memcache/Cache.php | 43 ++++++----- tests/lib/Memcache/CasTraitTest.php | 26 ++++--- tests/lib/Memcache/FactoryTest.php | 43 ++++++----- tests/lib/Memcache/MemcachedTest.php | 8 +- tests/lib/Memcache/RedisTest.php | 8 +- tests/phpunit-autotest.xml | 77 ++++++++++--------- 11 files changed, 131 insertions(+), 105 deletions(-) diff --git a/lib/private/Memcache/Redis.php b/lib/private/Memcache/Redis.php index 5717005ee372..67615b8655d3 100644 --- a/lib/private/Memcache/Redis.php +++ b/lib/private/Memcache/Redis.php @@ -62,16 +62,20 @@ public function set($key, $value, $ttl = 0): mixed { return self::$cache->set($this->getNameSpace() . $key, \json_encode($value)); } - public function hasKey($key) { - return self::$cache->exists($this->getNameSpace() . $key); + public function hasKey($key): bool { + $val = self::$cache->exists($this->getNameSpace() . $key); + if (\is_bool($val)) { + return $val; + } + return (int)$val > 0; } public function remove($key) { if (self::$cache->del($this->getNameSpace() . $key)) { return true; - } else { - return false; } + + return false; } public function clear($prefix = '') { diff --git a/tests/Core/Command/Config/App/SetConfigTest.php b/tests/Core/Command/Config/App/SetConfigTest.php index db87c11d1fcb..8a31868ce001 100644 --- a/tests/Core/Command/Config/App/SetConfigTest.php +++ b/tests/Core/Command/Config/App/SetConfigTest.php @@ -49,7 +49,7 @@ protected function setUp(): void { $this->command = new SetConfig($config); } - public function setData() { + public function providesData() { return [ [ 'name', @@ -71,7 +71,7 @@ public function setData() { } /** - * @dataProvider setData + * @dataProvider providesData * * @param string $configName * @param mixed $newValue diff --git a/tests/Core/Command/Config/System/SetConfigTest.php b/tests/Core/Command/Config/System/SetConfigTest.php index 5c53b4ca202e..8c903d571915 100644 --- a/tests/Core/Command/Config/System/SetConfigTest.php +++ b/tests/Core/Command/Config/System/SetConfigTest.php @@ -49,7 +49,7 @@ protected function setUp(): void { $this->command = new SetConfig($systemConfig); } - public function setData() { + public function providesData() { return [ [['name'], 'newvalue', null, 'newvalue'], [['a', 'b', 'c'], 'foobar', null, ['b' => ['c' => 'foobar']]], @@ -58,7 +58,7 @@ public function setData() { } /** - * @dataProvider setData + * @dataProvider providesData * * @param array $configNames * @param string $newValue diff --git a/tests/lib/Memcache/APCuTest.php b/tests/lib/Memcache/APCuTest.php index ddfeb1f44e42..25cf4c8ea69d 100644 --- a/tests/lib/Memcache/APCuTest.php +++ b/tests/lib/Memcache/APCuTest.php @@ -9,14 +9,15 @@ namespace Test\Memcache; +use OC\Memcache\APCu; + class APCuTest extends Cache { protected function setUp(): void { parent::setUp(); - if (!\OC\Memcache\APCu::isAvailable()) { + if (!APCu::isAvailable()) { $this->markTestSkipped('The APCu extension is not available.'); - return; } - $this->instance=new \OC\Memcache\APCu(self::getUniqueID()); + $this->instance=new APCu(self::getUniqueID()); } } diff --git a/tests/lib/Memcache/ArrayCacheTest.php b/tests/lib/Memcache/ArrayCacheTest.php index 4e3623d344d0..58eb085b16b2 100644 --- a/tests/lib/Memcache/ArrayCacheTest.php +++ b/tests/lib/Memcache/ArrayCacheTest.php @@ -9,9 +9,11 @@ namespace Test\Memcache; +use OC\Memcache\ArrayCache; + class ArrayCacheTest extends Cache { protected function setUp(): void { parent::setUp(); - $this->instance = new \OC\Memcache\ArrayCache(''); + $this->instance = new ArrayCache(''); } } diff --git a/tests/lib/Memcache/Cache.php b/tests/lib/Memcache/Cache.php index 6b1263f429c1..67fbe6ecff1f 100644 --- a/tests/lib/Memcache/Cache.php +++ b/tests/lib/Memcache/Cache.php @@ -9,71 +9,74 @@ namespace Test\Memcache; -abstract class Cache extends \Test\Cache\TestCache { +use OCP\IMemcache; +use Test\Cache\TestCache; + +abstract class Cache extends TestCache { /** - * @var \OCP\IMemcache cache; + * @var IMemcache cache; */ protected $instance; - public function testExistsAfterSet() { + public function testExistsAfterSet(): void { $this->assertFalse($this->instance->hasKey('foo')); $this->instance->set('foo', 'bar'); $this->assertTrue($this->instance->hasKey('foo')); } - public function testGetAfterSet() { + public function testGetAfterSet(): void { $this->assertNull($this->instance->get('foo')); $this->instance->set('foo', 'bar'); $this->assertEquals('bar', $this->instance->get('foo')); } - public function testGetArrayAfterSet() { + public function testGetArrayAfterSet(): void { $this->assertNull($this->instance->get('foo')); $this->instance->set('foo', ['bar']); $this->assertEquals(['bar'], $this->instance->get('foo')); } - public function testDoesNotExistAfterRemove() { + public function testDoesNotExistAfterRemove(): void { $this->instance->set('foo', 'bar'); $this->instance->remove('foo'); $this->assertFalse($this->instance->hasKey('foo')); } - public function testRemoveNonExisting() { + public function testRemoveNonExisting(): void { $this->instance->remove('foo'); $this->assertFalse($this->instance->hasKey('foo')); } - public function testArrayAccessSet() { + public function testArrayAccessSet(): void { $this->instance['foo'] = 'bar'; $this->assertEquals('bar', $this->instance->get('foo')); } - public function testArrayAccessGet() { + public function testArrayAccessGet(): void { $this->instance->set('foo', 'bar'); $this->assertEquals('bar', $this->instance['foo']); } - public function testArrayAccessExists() { + public function testArrayAccessExists(): void { $this->assertArrayNotHasKey('foo', $this->instance); $this->instance->set('foo', 'bar'); $this->assertArrayHasKey('foo', $this->instance); } - public function testArrayAccessUnset() { + public function testArrayAccessUnset(): void { $this->instance->set('foo', 'bar'); unset($this->instance['foo']); $this->assertFalse($this->instance->hasKey('foo')); } - public function testAdd() { + public function testAdd(): void { $this->assertTrue($this->instance->add('foo', 'bar')); $this->assertEquals('bar', $this->instance->get('foo')); $this->assertFalse($this->instance->add('foo', 'asd')); $this->assertEquals('bar', $this->instance->get('foo')); } - public function testInc() { + public function testInc(): void { $this->assertEquals(1, $this->instance->inc('foo')); $this->assertEquals(1, $this->instance->get('foo')); $this->assertEquals(2, $this->instance->inc('foo')); @@ -86,7 +89,7 @@ public function testInc() { $this->assertEquals('bar', $this->instance->get('foo')); } - public function testDec() { + public function testDec(): void { $this->assertFalse($this->instance->dec('foo')); $this->instance->set('foo', 20); $this->assertEquals(19, $this->instance->dec('foo')); @@ -98,34 +101,32 @@ public function testDec() { $this->assertEquals('bar', $this->instance->get('foo')); } - public function testCasNotChanged() { + public function testCasNotChanged(): void { $this->instance->set('foo', 'bar'); $this->assertTrue($this->instance->cas('foo', 'bar', 'asd')); $this->assertEquals('asd', $this->instance->get('foo')); } - public function testCasChanged() { + public function testCasChanged(): void { $this->instance->set('foo', 'bar1'); $this->assertFalse($this->instance->cas('foo', 'bar', 'asd')); $this->assertEquals('bar1', $this->instance->get('foo')); } - public function testCadNotChanged() { + public function testCadNotChanged(): void { $this->instance->set('foo', 'bar'); $this->assertTrue($this->instance->cad('foo', 'bar')); $this->assertFalse($this->instance->hasKey('foo')); } - public function testCadChanged() { + public function testCadChanged(): void { $this->instance->set('foo', 'bar1'); $this->assertFalse($this->instance->cad('foo', 'bar')); $this->assertTrue($this->instance->hasKey('foo')); } protected function tearDown(): void { - if ($this->instance) { - $this->instance->clear(); - } + $this->instance?->clear(); parent::tearDown(); } diff --git a/tests/lib/Memcache/CasTraitTest.php b/tests/lib/Memcache/CasTraitTest.php index 943daf88e603..d93b6c2378fe 100644 --- a/tests/lib/Memcache/CasTraitTest.php +++ b/tests/lib/Memcache/CasTraitTest.php @@ -21,50 +21,52 @@ namespace Test\Memcache; +use OC\Memcache\ArrayCache; +use OC\Memcache\CasTrait; use Test\TestCase; class CasTraitTest extends TestCase { /** - * @return \OC\Memcache\CasTrait + * @return CasTrait */ private function getCache() { - $sourceCache = new \OC\Memcache\ArrayCache(); + $sourceCache = new ArrayCache(); $mock = $this->getMockForTrait('\OC\Memcache\CasTrait'); $mock->expects($this->any()) ->method('set') - ->will($this->returnCallback(function ($key, $value, $ttl) use ($sourceCache) { + ->willReturnCallback(function ($key, $value, $ttl) use ($sourceCache) { return $sourceCache->set($key, $value, $ttl); - })); + }); $mock->expects($this->any()) ->method('get') - ->will($this->returnCallback(function ($key) use ($sourceCache) { + ->willReturnCallback(function ($key) use ($sourceCache) { return $sourceCache->get($key); - })); + }); $mock->expects($this->any()) ->method('add') - ->will($this->returnCallback(function ($key, $value, $ttl) use ($sourceCache) { + ->willReturnCallback(function ($key, $value, $ttl) use ($sourceCache) { return $sourceCache->add($key, $value, $ttl); - })); + }); $mock->expects($this->any()) ->method('remove') - ->will($this->returnCallback(function ($key) use ($sourceCache) { + ->willReturnCallback(function ($key) use ($sourceCache) { return $sourceCache->remove($key); - })); + }); return $mock; } - public function testCasNotChanged() { + public function testCasNotChanged(): void { $cache = $this->getCache(); $cache->set('foo', 'bar'); $this->assertTrue($cache->cas('foo', 'bar', 'asd')); $this->assertEquals('asd', $cache->get('foo')); } - public function testCasChanged() { + public function testCasChanged(): void { $cache = $this->getCache(); $cache->set('foo', 'bar1'); $this->assertFalse($cache->cas('foo', 'bar', 'asd')); diff --git a/tests/lib/Memcache/FactoryTest.php b/tests/lib/Memcache/FactoryTest.php index 31b232a69d2c..d275fb5ef988 100644 --- a/tests/lib/Memcache/FactoryTest.php +++ b/tests/lib/Memcache/FactoryTest.php @@ -20,11 +20,16 @@ */ namespace Test\Memcache; +use OC\HintException; +use OC\Memcache\Factory; +use Test\TestCase; +use OCP\ILogger; + class Test_Factory_Available_Cache1 { public function __construct($prefix = '') { } - public static function isAvailable() { + public static function isAvailable(): true { return true; } } @@ -33,7 +38,7 @@ class Test_Factory_Available_Cache2 { public function __construct($prefix = '') { } - public static function isAvailable() { + public static function isAvailable(): true { return true; } } @@ -42,7 +47,7 @@ class Test_Factory_Unavailable_Cache1 { public function __construct($prefix = '') { } - public static function isAvailable() { + public static function isAvailable(): false { return false; } } @@ -51,48 +56,48 @@ class Test_Factory_Unavailable_Cache2 { public function __construct($prefix = '') { } - public static function isAvailable() { + public static function isAvailable(): false { return false; } } -class FactoryTest extends \Test\TestCase { +class FactoryTest extends TestCase { public const AVAILABLE1 = '\\Test\\Memcache\\Test_Factory_Available_Cache1'; public const AVAILABLE2 = '\\Test\\Memcache\\Test_Factory_Available_Cache2'; public const UNAVAILABLE1 = '\\Test\\Memcache\\Test_Factory_Unavailable_Cache1'; public const UNAVAILABLE2 = '\\Test\\Memcache\\Test_Factory_Unavailable_Cache2'; - public function cacheAvailabilityProvider() { + public function cacheAvailabilityProvider(): array { return [ [ // local and distributed available self::AVAILABLE1, self::AVAILABLE2, null, - self::AVAILABLE1, self::AVAILABLE2, \OC\Memcache\Factory::NULL_CACHE + self::AVAILABLE1, self::AVAILABLE2, Factory::NULL_CACHE ], [ // local and distributed null null, null, null, - \OC\Memcache\Factory::NULL_CACHE, \OC\Memcache\Factory::NULL_CACHE, \OC\Memcache\Factory::NULL_CACHE + Factory::NULL_CACHE, Factory::NULL_CACHE, Factory::NULL_CACHE ], [ // local available, distributed null (most common scenario) self::AVAILABLE1, null, null, - self::AVAILABLE1, self::AVAILABLE1, \OC\Memcache\Factory::NULL_CACHE + self::AVAILABLE1, self::AVAILABLE1, Factory::NULL_CACHE ], [ // locking cache available null, null, self::AVAILABLE1, - \OC\Memcache\Factory::NULL_CACHE, \OC\Memcache\Factory::NULL_CACHE, self::AVAILABLE1 + Factory::NULL_CACHE, Factory::NULL_CACHE, self::AVAILABLE1 ], [ // locking cache unavailable: no exception here in the factory null, null, self::UNAVAILABLE1, - \OC\Memcache\Factory::NULL_CACHE, \OC\Memcache\Factory::NULL_CACHE, \OC\Memcache\Factory::NULL_CACHE + Factory::NULL_CACHE, Factory::NULL_CACHE, Factory::NULL_CACHE ] ]; } - public function cacheUnavailableProvider() { + public function cacheUnavailableProvider(): array { return [ [ // local available, distributed unavailable @@ -119,9 +124,9 @@ public function testCacheAvailability( $expectedLocalCache, $expectedDistributedCache, $expectedLockingCache - ) { - $logger = $this->getMockBuilder('\OCP\ILogger')->getMock(); - $factory = new \OC\Memcache\Factory('abc', $logger, $localCache, $distributedCache, $lockingCache); + ): void { + $logger = $this->getMockBuilder(ILogger::class)->getMock(); + $factory = new Factory('abc', $logger, $localCache, $distributedCache, $lockingCache); $this->assertTrue(\is_a($factory->createLocal(), $expectedLocalCache)); $this->assertTrue(\is_a($factory->createDistributed(), $expectedDistributedCache)); $this->assertTrue(\is_a($factory->createLocking(), $expectedLockingCache)); @@ -130,10 +135,10 @@ public function testCacheAvailability( /** * @dataProvider cacheUnavailableProvider */ - public function testCacheNotAvailableException($localCache, $distributedCache) { - $this->expectException(\OC\HintException::class); + public function testCacheNotAvailableException($localCache, $distributedCache): void { + $this->expectException(HintException::class); - $logger = $this->getMockBuilder('\OCP\ILogger')->getMock(); - new \OC\Memcache\Factory('abc', $logger, $localCache, $distributedCache); + $logger = $this->getMockBuilder(ILogger::class)->getMock(); + new Factory('abc', $logger, $localCache, $distributedCache); } } diff --git a/tests/lib/Memcache/MemcachedTest.php b/tests/lib/Memcache/MemcachedTest.php index 1b1b894fa0d2..4eb64e6fa55a 100644 --- a/tests/lib/Memcache/MemcachedTest.php +++ b/tests/lib/Memcache/MemcachedTest.php @@ -9,14 +9,16 @@ namespace Test\Memcache; +use OC\Memcache\Memcached; + class MemcachedTest extends Cache { public static function setUpBeforeClass(): void { parent::setUpBeforeClass(); - if (!\OC\Memcache\Memcached::isAvailable()) { + if (!Memcached::isAvailable()) { self::markTestSkipped('The memcached extension is not available.'); } - $instance = new \OC\Memcache\Memcached(self::getUniqueID()); + $instance = new Memcached(self::getUniqueID()); try { $instance->set(self::getUniqueID(), self::getUniqueID()); } catch (\Exception $ex) { @@ -26,7 +28,7 @@ public static function setUpBeforeClass(): void { protected function setUp(): void { parent::setUp(); - $this->instance = new \OC\Memcache\Memcached(self::getUniqueID()); + $this->instance = new Memcached(self::getUniqueID()); } public function testClear() { diff --git a/tests/lib/Memcache/RedisTest.php b/tests/lib/Memcache/RedisTest.php index 8b7c36f88da2..3ac8e443cae6 100644 --- a/tests/lib/Memcache/RedisTest.php +++ b/tests/lib/Memcache/RedisTest.php @@ -9,16 +9,18 @@ namespace Test\Memcache; +use OC\Memcache\Redis; + class RedisTest extends Cache { public static function setUpBeforeClass(): void { parent::setUpBeforeClass(); - if (!\OC\Memcache\Redis::isAvailable()) { + if (!Redis::isAvailable()) { self::markTestSkipped('The redis extension is not available.'); } try { - $instance = new \OC\Memcache\Redis(self::getUniqueID()); + $instance = new Redis(self::getUniqueID()); $instance->set(self::getUniqueID(), self::getUniqueID()); } catch (\RedisException $ex) { self::markTestSkipped('redis server seems to be down.'); @@ -27,6 +29,6 @@ public static function setUpBeforeClass(): void { protected function setUp(): void { parent::setUp(); - $this->instance = new \OC\Memcache\Redis(self::getUniqueID()); + $this->instance = new Redis(self::getUniqueID()); } } diff --git a/tests/phpunit-autotest.xml b/tests/phpunit-autotest.xml index 470385d1972d..040a265a3d94 100644 --- a/tests/phpunit-autotest.xml +++ b/tests/phpunit-autotest.xml @@ -1,37 +1,44 @@ - - - - .. - - - ../apps/dav/tests - ../apps/encryption/tests - ../apps/federatedfilesharing/tests - ../apps/federation/tests - ../apps/files/tests - ../apps/files_external - ../apps/files_sharing/tests - ../apps/files_trashbin/tests - ../apps/files_versions/tests - ../apps/provisioning_api/tests - ../apps/updatenotification/tests - ../tests - ../build - ../lib/composer - ../config/config.apps.sample.php - ../config/config.sample.php - - - - - lib/ - Settings/ - Core/ - apps.php - - - - - + + + + .. + + + ../apps/dav/tests + ../apps/encryption/tests + ../apps/federatedfilesharing/tests + + ../apps/federation/tests + ../apps/files/tests + ../apps/files_external + ../apps/files_sharing/tests + ../apps/files_trashbin/tests + ../apps/files_versions/tests + ../apps/provisioning_api/tests + ../apps/updatenotification/tests + + ../tests + ../build + ../lib/composer + ../config/config.apps.sample.php + ../config/config.sample.php + + + + + lib/ + Settings/ + Core/ + apps.php + + + + + From 182da903171f2da877fcb471ea62b7773ee20e73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Thu, 28 Sep 2023 13:10:10 +0200 Subject: [PATCH 15/42] fix: dynamic properties --- apps/files/tests/Command/ScanTest.php | 1 - lib/private/Files/Stream/Dir.php | 1 + tests/lib/DB/MySqlToolsTest.php | 3 +- tests/lib/Encryption/Keys/StorageTest.php | 1 - tests/lib/Files/ViewTest.php | 1 - tests/lib/Share20/ShareTest.php | 4 +- tests/lib/User/BasicAuthModuleTest.php | 161 ++++++++++++---------- tests/lib/User/TokenAuthModuleTest.php | 48 ++++--- 8 files changed, 117 insertions(+), 103 deletions(-) diff --git a/apps/files/tests/Command/ScanTest.php b/apps/files/tests/Command/ScanTest.php index 4e1fc8d310bc..601f4727fe27 100644 --- a/apps/files/tests/Command/ScanTest.php +++ b/apps/files/tests/Command/ScanTest.php @@ -94,7 +94,6 @@ class ScanTest extends TestCase { * @var array */ private $groupsCreated = []; - private mixed $dataDir; protected function setUp(): void { if ($this->runsWithPrimaryObjectstorage()) { diff --git a/lib/private/Files/Stream/Dir.php b/lib/private/Files/Stream/Dir.php index 8b0034e0fb81..38735745bc72 100644 --- a/lib/private/Files/Stream/Dir.php +++ b/lib/private/Files/Stream/Dir.php @@ -28,6 +28,7 @@ class Dir { private static $dirs = []; private $name; private $index; + public $context; public function dir_opendir($path, $options) { $this->name = \substr($path, \strlen('fakedir://')); diff --git a/tests/lib/DB/MySqlToolsTest.php b/tests/lib/DB/MySqlToolsTest.php index 42f97c624f23..067e29f2934a 100644 --- a/tests/lib/DB/MySqlToolsTest.php +++ b/tests/lib/DB/MySqlToolsTest.php @@ -34,12 +34,11 @@ * Class MySqlToolsTest */ class MySqlToolsTest extends TestCase { - public MySqlTools $tools; + private MySqlTools $tools; /** * @var IDBConnection */ private $db; - private MySqlTools $tools; public function setUp(): void { parent::setUp(); diff --git a/tests/lib/Encryption/Keys/StorageTest.php b/tests/lib/Encryption/Keys/StorageTest.php index c982080bad6d..3f0c3fae1f6b 100644 --- a/tests/lib/Encryption/Keys/StorageTest.php +++ b/tests/lib/Encryption/Keys/StorageTest.php @@ -39,7 +39,6 @@ * @package Test\Encryption\Keys */ class StorageTest extends TestCase { - public $mkdirStack; use UserTrait; /** @var Storage */ diff --git a/tests/lib/Files/ViewTest.php b/tests/lib/Files/ViewTest.php index d2c897cdbb13..bf526d0c0218 100644 --- a/tests/lib/Files/ViewTest.php +++ b/tests/lib/Files/ViewTest.php @@ -59,7 +59,6 @@ public function instanceOfStorage($className) { * @package Test\Files */ class ViewTest extends TestCase { - public $shallThrow; /** * @var Storage[] $storages */ diff --git a/tests/lib/Share20/ShareTest.php b/tests/lib/Share20/ShareTest.php index 57be79d79d09..7513072859b4 100644 --- a/tests/lib/Share20/ShareTest.php +++ b/tests/lib/Share20/ShareTest.php @@ -43,8 +43,8 @@ class ShareTest extends TestCase { protected $share; public function setUp(): void { - $this->rootFolder = $this->createMock('\OCP\Files\IRootFolder'); - $this->userManager = $this->createMock('OCP\IUserManager'); + $this->rootFolder = $this->createMock(IRootFolder::class); + $this->userManager = $this->createMock(IUserManager::class); $this->share = new Share($this->rootFolder, $this->userManager); } diff --git a/tests/lib/User/BasicAuthModuleTest.php b/tests/lib/User/BasicAuthModuleTest.php index 0602d6cb6502..c8bd4c6a4f8e 100644 --- a/tests/lib/User/BasicAuthModuleTest.php +++ b/tests/lib/User/BasicAuthModuleTest.php @@ -22,30 +22,33 @@ namespace Test\User; +use Exception; +use OC\AppFramework\Http\Request; use OC\User\BasicAuthModule; use OCP\AppFramework\Utility\ITimeFactory; use OCP\IConfig; use OCP\ILogger; -use OCP\IRequest; use OCP\ISession; use OCP\IUser; use OCP\IUserManager; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; +use UnexpectedValueException; +use function get_class; +use function time; class BasicAuthModuleTest extends TestCase { - /** @var IConfig | \PHPUnit\Framework\MockObject\MockObject */ + /** @var IConfig | MockObject */ private $config; - /** @var ILogger | \PHPUnit\Framework\MockObject\MockObject */ + /** @var ILogger | MockObject */ private $logger; - /** @var IUserManager | \PHPUnit\Framework\MockObject\MockObject */ + /** @var IUserManager | MockObject */ private $manager; - /** @var IRequest | \PHPUnit\Framework\MockObject\MockObject */ - private $request; - /** @var ITimeFactory | \PHPUnit\Framework\MockObject\MockObject */ + /** @var ITimeFactory | MockObject */ private $timeFactory; - /** @var IUser | \PHPUnit\Framework\MockObject\MockObject */ + /** @var IUser | MockObject */ private $user; - /** @var ISession | \PHPUnit\Framework\MockObject\MockObject */ + /** @var ISession | MockObject */ private $session; public function setUp(): void { @@ -53,14 +56,13 @@ public function setUp(): void { $this->config = $this->createMock(IConfig::class); $this->logger = $this->createMock(ILogger::class); $this->manager = $this->createMock(IUserManager::class); - $this->request = $this->createMock(IRequest::class); $this->session = $this->createMock(ISession::class); $this->timeFactory = $this->createMock(ITimeFactory::class); $this->user = $this->createMock(IUser::class); - $this->user->expects($this->any())->method('getUID')->willReturn('user1'); + $this->user->method('getUID')->willReturn('user1'); - $this->manager->expects($this->any())->method('checkPassword') + $this->manager->method('checkPassword') ->willReturnMap([ ['user1', '123456', $this->user], ['user@example.com', '123456', $this->user], @@ -69,7 +71,7 @@ public function setUp(): void { ['unique@example.com', '123456', null], ]); - $this->manager->expects($this->any())->method('getByEmail') + $this->manager->method('getByEmail') ->willReturnMap([ ['not-unique@example.com', [$this->user, $this->user]], ['unique@example.com', [$this->user]], @@ -82,47 +84,48 @@ public function setUp(): void { /** * @dataProvider providesCredentials - * @param mixed $expectedResult - * @param string $userId */ - public function testAuth($expectedResult, $userId) { - $this->session->method('exists')->will($this->returnValueMap([ + public function testAuth(mixed $expectedResult, string $userId): void { + $this->session->method('exists')->willReturnMap([ ['app_password', false], ['last_check_timeout', true] - ])); + ]); // check auth - $time = \time(); - $this->session->method('get')->will($this->returnValueMap([ + $time = time(); + $this->session->method('get')->willReturnMap([ ['user_id', $userId], ['last_check_timeout', $time - 60 * 5] - ])); + ]); $this->timeFactory->method('getTime')->willReturn($time); $module = new BasicAuthModule($this->config, $this->logger, $this->manager, $this->session, $this->timeFactory); - $this->request->server = [ - 'PHP_AUTH_USER' => $userId, - 'PHP_AUTH_PW' => '123456', - ]; - if ($expectedResult instanceof \Exception) { + $request = new Request([ + 'server' => [ + 'PHP_AUTH_USER' => $userId, + 'PHP_AUTH_PW' => '123456', + ] + ]); + + if ($expectedResult instanceof Exception) { $this->expectException(\get_class($expectedResult)); $this->expectExceptionMessage($expectedResult->getMessage()); } - $this->assertEquals($expectedResult ? $this->user : null, $module->auth($this->request)); + $this->assertEquals($expectedResult ? $this->user : null, $module->auth($request)); } - public function testAppPassword() { - $this->session->method('exists')->will($this->returnValueMap([ + public function testAppPassword(): void { + $this->session->method('exists')->willReturnMap([ ['app_password', true], ['last_check_timeout', true] - ])); + ]); // check auth - $time = \time(); - $this->session->method('get')->will($this->returnValueMap([ + $time = time(); + $this->session->method('get')->willReturnMap([ ['user_id', 'user'], ['last_check_timeout', $time - 60 * 5] - ])); + ]); $this->timeFactory->method('getTime')->willReturn($time); $this->manager @@ -130,47 +133,53 @@ public function testAppPassword() { ->method('checkPassword'); $module = new BasicAuthModule($this->config, $this->logger, $this->manager, $this->session, $this->timeFactory); - $this->request->server = [ - 'PHP_AUTH_USER' => 'user', - 'PHP_AUTH_PW' => 'app-pass-word', - ]; - $this->assertEquals(null, $module->auth($this->request)); + $request = new Request([ + 'server' => [ + 'PHP_AUTH_USER' => 'user', + 'PHP_AUTH_PW' => 'app-pass-word', + ] + ]); + $this->assertEquals(null, $module->auth($request)); } - public function testGetUserPassword() { + public function testGetUserPassword(): void { $module = new BasicAuthModule($this->config, $this->logger, $this->manager, $this->session, $this->timeFactory); - $this->request->server = [ - 'PHP_AUTH_USER' => 'user1', - 'PHP_AUTH_PW' => '123456', - ]; - $this->assertEquals('123456', $module->getUserPassword($this->request)); - - $this->request->server = []; - $this->assertEquals('', $module->getUserPassword($this->request)); + $request = new Request([ + 'server' => [ + 'PHP_AUTH_USER' => 'user1', + 'PHP_AUTH_PW' => '123456', + ] + ]); + $this->assertEquals('123456', $module->getUserPassword($request)); + + $request = new Request([ + 'server' => [] + ]); + $this->assertEquals('', $module->getUserPassword($request)); } - public function providesCredentials() { + public function providesCredentials(): array { return [ 'no user is' => [false, ''], 'user1 can login' => [true, 'user1'], 'user1 can login with email' => [true, 'user@example.com'], 'unique email can login' => [true, 'unique@example.com'], - 'not unique email can not login' => [new \Exception('Invalid credentials'), 'not-unique@example.com'], - 'user2 is not known' => [new \Exception('Invalid credentials'), 'user2'], + 'not unique email can not login' => [new Exception('Invalid credentials'), 'not-unique@example.com'], + 'user2 is not known' => [new Exception('Invalid credentials'), 'user2'], ]; } - public function testTimeout() { - $this->session->method('exists')->will($this->returnValueMap([ + public function testTimeout(): void { + $this->session->method('exists')->willReturnMap([ ['app_password', false], ['last_check_timeout', true] - ])); + ]); - $time = \time(); - $this->session->method('get')->will($this->returnValueMap([ + $time = time(); + $this->session->method('get')->willReturnMap([ ['last_check_timeout', $time - 60 * 4], ['user_id', 'user1'] - ])); + ]); $this->timeFactory->method('getTime')->willReturn($time); @@ -183,15 +192,17 @@ public function testTimeout() { $module = new BasicAuthModule($this->config, $this->logger, $this->manager, $this->session, $this->timeFactory); - $this->request->server = [ - 'PHP_AUTH_USER' => 'user1', - 'PHP_AUTH_PW' => '123456', - ]; + $request = new Request([ + 'server' => [ + 'PHP_AUTH_USER' => 'user1', + 'PHP_AUTH_PW' => '123456', + ] + ]); - $this->assertEquals($this->user, $module->auth($this->request)); + $this->assertEquals($this->user, $module->auth($request)); } - public function invalidUserIdProvider() { + public function invalidUserIdProvider(): array { return [ [''], [null], ]; @@ -200,19 +211,19 @@ public function invalidUserIdProvider() { /** * @dataProvider invalidUserIdProvider */ - public function testInvalidUserId($userId) { - $this->expectException(\UnexpectedValueException::class); + public function testInvalidUserId($userId): void { + $this->expectException(UnexpectedValueException::class); - $this->session->method('exists')->will($this->returnValueMap([ + $this->session->method('exists')->willReturnMap([ ['app_password', false], ['last_check_timeout', true] - ])); + ]); - $time = \time(); - $this->session->method('get')->will($this->returnValueMap([ + $time = time(); + $this->session->method('get')->willReturnMap([ ['last_check_timeout', $time - 60 * 4], ['user_id', $userId] - ])); + ]); $this->timeFactory->method('getTime')->willReturn($time); @@ -226,11 +237,13 @@ public function testInvalidUserId($userId) { $module = new BasicAuthModule($this->config, $this->logger, $this->manager, $this->session, $this->timeFactory); - $this->request->server = [ - 'PHP_AUTH_USER' => 'user1', - 'PHP_AUTH_PW' => '123456', - ]; + $request = new Request([ + 'server' => [ + 'PHP_AUTH_USER' => 'user1', + 'PHP_AUTH_PW' => '123456', + ] + ]); - $module->auth($this->request); + $module->auth($request); } } diff --git a/tests/lib/User/TokenAuthModuleTest.php b/tests/lib/User/TokenAuthModuleTest.php index 380b56d68cb5..5388dde98f67 100644 --- a/tests/lib/User/TokenAuthModuleTest.php +++ b/tests/lib/User/TokenAuthModuleTest.php @@ -21,27 +21,28 @@ namespace Test\User; +use Exception; +use OC\AppFramework\Http\Request; use OC\Authentication\Exceptions\InvalidTokenException; use OC\Authentication\Token\IProvider; use OC\Authentication\Token\IToken; use OC\User\TokenAuthModule; -use OCP\IRequest; use OCP\ISession; use OCP\IUser; use OCP\IUserManager; use OCP\Session\Exceptions\SessionNotAvailableException; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; +use function get_class; class TokenAuthModuleTest extends TestCase { - /** @var IUserManager | \PHPUnit\Framework\MockObject\MockObject */ + /** @var IUserManager | MockObject */ private $manager; - /** @var IRequest | \PHPUnit\Framework\MockObject\MockObject */ - private $request; - /** @var IUser | \PHPUnit\Framework\MockObject\MockObject */ + /** @var IUser | MockObject */ private $user; - /** @var ISession | \PHPUnit\Framework\MockObject\MockObject */ + /** @var ISession | MockObject */ private $session; - /** @var IProvider | \PHPUnit\Framework\MockObject\MockObject */ + /** @var IProvider | MockObject */ private $tokenProvider; public function setUp(): void { @@ -49,7 +50,6 @@ public function setUp(): void { $this->manager = $this->createMock(IUserManager::class); $this->session = $this->createMock(ISession::class); $this->tokenProvider = $this->createMock(IProvider::class); - $this->request = $this->createMock(IRequest::class); $this->user = $this->createMock(IUser::class); $this->session->expects($this->any())->method('getId')->willThrowException(new SessionNotAvailableException()); @@ -88,29 +88,33 @@ public function setUp(): void { public function testModule($expectedResult, $authHeader, $password = '') { $module = new TokenAuthModule($this->session, $this->tokenProvider, $this->manager); if ($authHeader === 'basic') { - $this->request->server = [ - 'PHP_AUTH_USER' => 'user1', - 'PHP_AUTH_PW' => 'valid-token', - ]; + $request = new Request([ + 'server' => [ + 'PHP_AUTH_USER' => 'user1', + 'PHP_AUTH_PW' => 'valid-token', + ] + ]); } else { - $this->request->server = [ - 'PHP_AUTH_USER' => '', - 'PHP_AUTH_PW' => '', - ]; - $this->request->expects($this->any())->method('getHeader')->willReturn($authHeader); + $request = new Request([ + 'server' => [ + 'PHP_AUTH_USER' => '', + 'PHP_AUTH_PW' => '', + 'HTTP_AUTHORIZATION' => $authHeader + ] + ]); } - if ($expectedResult instanceof \Exception) { + if ($expectedResult instanceof Exception) { $this->expectException(\get_class($expectedResult)); $this->expectExceptionMessage($expectedResult->getMessage()); - $module->auth($this->request); + $module->auth($request); } else { - $this->assertEquals($expectedResult ? $this->user : null, $module->auth($this->request)); - $this->assertEquals($password, $module->getUserPassword($this->request)); + $this->assertEquals($expectedResult ? $this->user : null, $module->auth($request)); + $this->assertEquals($password, $module->getUserPassword($request)); } } - public function providesCredentials() { + public function providesCredentials(): array { return [ 'no auth header' => [false, ''], 'not valid token' => [false, 'token whateverbutnothingvalid'], From 6c78a73f097fb034b5bb52df9781c2e9eafda438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Fri, 29 Sep 2023 11:36:43 +0200 Subject: [PATCH 16/42] feat: move php version check to base.php --- lib/base.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/base.php b/lib/base.php index 43269235bf7f..f7ec7c0a9654 100644 --- a/lib/base.php +++ b/lib/base.php @@ -5,14 +5,14 @@ if (\defined('OC_CONSOLE')) { $eol = PHP_EOL; } -if (PHP_VERSION_ID < 70400) { - echo 'This version of ownCloud requires at least PHP 7.4.0'.$eol; +if (PHP_VERSION_ID < 80200) { + echo 'This version of ownCloud requires at least PHP 8.2.0'.$eol; echo 'You are currently running PHP ' . PHP_VERSION . '. Please update your PHP version.'.$eol; exit(1); } -if (PHP_VERSION_ID >= 80000) { - echo 'This version of ownCloud is not compatible with PHP 8.0' . $eol; +if (PHP_VERSION_ID >= 80300) { + echo 'This version of ownCloud is not compatible with PHP 8.3' . $eol; echo 'You are currently running PHP ' . PHP_VERSION . '.' . $eol; exit(1); } From 6619f0e7295180f691e6212af5b84a1e67bb2b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Fri, 29 Sep 2023 12:20:30 +0200 Subject: [PATCH 17/42] fix: installation on MySql --- lib/private/DB/MDB2SchemaManager.php | 10 +++++++--- lib/private/DB/Migrator.php | 13 ++++++++----- lib/private/Session/CryptoSessionData.php | 5 +---- lib/private/Session/Internal.php | 4 ++-- lib/private/Session/Memory.php | 2 +- lib/private/Setup/MySQL.php | 8 ++++---- lib/public/ISession.php | 2 +- 7 files changed, 24 insertions(+), 20 deletions(-) diff --git a/lib/private/DB/MDB2SchemaManager.php b/lib/private/DB/MDB2SchemaManager.php index 5213209834a9..27c7d3d2a6f7 100644 --- a/lib/private/DB/MDB2SchemaManager.php +++ b/lib/private/DB/MDB2SchemaManager.php @@ -136,11 +136,15 @@ public function removeDBStructure($file) { * @return bool */ private function executeSchemaChange($schema) { - $this->conn->beginTransaction(); + if (!$this->conn->getDatabasePlatform() instanceof MySqlPlatform) { + $this->conn->beginTransaction(); + } foreach ($schema->toSql($this->conn->getDatabasePlatform()) as $sql) { - $this->conn->query($sql); + $this->conn->executeQuery($sql); + } + if (!$this->conn->getDatabasePlatform() instanceof MySQLPlatform) { + $this->conn->commit(); } - $this->conn->commit(); if ($this->conn->getDatabasePlatform() instanceof SqlitePlatform) { $this->conn->close(); diff --git a/lib/private/DB/Migrator.php b/lib/private/DB/Migrator.php index 94bc3c8a9a2b..001b89237a7f 100644 --- a/lib/private/DB/Migrator.php +++ b/lib/private/DB/Migrator.php @@ -28,8 +28,7 @@ namespace OC\DB; use Doctrine\DBAL\Schema\AbstractAsset; -use \Doctrine\DBAL\Schema\Index; -use \Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Platforms\MySQLPlatform; use \Doctrine\DBAL\Schema\Schema; use \Doctrine\DBAL\Schema\Comparator; use Doctrine\DBAL\Types\StringType; @@ -175,14 +174,18 @@ protected function applySchema(Schema $targetSchema, \Doctrine\DBAL\Connection $ $schemaDiff = $this->getDiff($targetSchema, $connection); - $connection->beginTransaction(); + if (!$connection->getDatabasePlatform() instanceof MySqlPlatform) { + $connection->beginTransaction(); + } $sqls = $schemaDiff->toSql($connection->getDatabasePlatform()); $step = 0; foreach ($sqls as $sql) { $this->emit($sql, $step++, \count($sqls)); - $connection->query($sql); + $connection->executeQuery($sql); + } + if (!$connection->getDatabasePlatform() instanceof MySQLPlatform) { + $connection->commit(); } - $connection->commit(); } /** diff --git a/lib/private/Session/CryptoSessionData.php b/lib/private/Session/CryptoSessionData.php index 0b6cbe95a255..e989b1638014 100644 --- a/lib/private/Session/CryptoSessionData.php +++ b/lib/private/Session/CryptoSessionData.php @@ -93,11 +93,8 @@ public function set($key, $value): void { /** * Get a value from the session - * - * @param string $key - * @return string|null Either the value or null */ - public function get($key): ?string { + public function get(string $key): mixed { return $this->sessionValues[$key] ?? null; } diff --git a/lib/private/Session/Internal.php b/lib/private/Session/Internal.php index 560410b576d7..2d4f91b83f0c 100644 --- a/lib/private/Session/Internal.php +++ b/lib/private/Session/Internal.php @@ -70,7 +70,7 @@ public function set($key, $value) { * @param string $key * @return mixed */ - public function get($key) { + public function get(string $key): mixed { if (!$this->exists($key)) { return null; } @@ -102,7 +102,7 @@ public function clear() { $_SESSION = []; } - public function close() { + public function close(): void { \session_write_close(); parent::close(); } diff --git a/lib/private/Session/Memory.php b/lib/private/Session/Memory.php index c187591685cb..307f0ad889c8 100644 --- a/lib/private/Session/Memory.php +++ b/lib/private/Session/Memory.php @@ -57,7 +57,7 @@ public function set($key, $value) { * @param string $key * @return mixed */ - public function get($key) { + public function get(string $key): mixed { if (!$this->exists($key)) { return null; } diff --git a/lib/private/Setup/MySQL.php b/lib/private/Setup/MySQL.php index 0cb4ce11fd45..3ccda3e8f0e4 100644 --- a/lib/private/Setup/MySQL.php +++ b/lib/private/Setup/MySQL.php @@ -69,7 +69,7 @@ private function createDatabase($connection) { //we can't use OC_DB functions here because we need to connect as the administrative user. $characterSet = $this->config->getSystemValue('mysql.utf8mb4', false) ? 'utf8mb4' : 'utf8'; $query = "CREATE DATABASE IF NOT EXISTS `$name` CHARACTER SET $characterSet COLLATE {$characterSet}_bin;"; - $connection->executeUpdate($query); + $connection->executeStatement($query); } catch (\Exception $ex) { $this->logger->error('Database creation failed: {error}', [ 'app' => 'mysql.setup', @@ -81,7 +81,7 @@ private function createDatabase($connection) { try { //this query will fail if there aren't the right permissions, ignore the error $query="GRANT ALL PRIVILEGES ON `$name` . * TO '$user'"; - $connection->executeUpdate($query); + $connection->executeStatement($query); } catch (\Exception $ex) { $this->logger->debug('Could not automatically grant privileges, this can be ignored if database user already had privileges: {error}', [ 'app' => 'mysql.setup', @@ -100,9 +100,9 @@ private function createDBUser($connection) { // we need to create 2 accounts, one for global use and one for local user. if we don't specify the local one, // the anonymous user would take precedence when there is one. $query = "CREATE USER '$name'@'localhost' IDENTIFIED BY '$password'"; - $connection->executeUpdate($query); + $connection->executeStatement($query); $query = "CREATE USER '$name'@'%' IDENTIFIED BY '$password'"; - $connection->executeUpdate($query); + $connection->executeStatement($query); } /** diff --git a/lib/public/ISession.php b/lib/public/ISession.php index 66f477c9fd1a..2f732f67cafa 100644 --- a/lib/public/ISession.php +++ b/lib/public/ISession.php @@ -58,7 +58,7 @@ public function set($key, $value); * @return mixed should return null if $key does not exist * @since 6.0.0 */ - public function get($key); + public function get(string $key): mixed; /** * Check if a named key exists in the session From 5e6d07c6f90518223d285f6b2663e86a72b4b4a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Fri, 29 Sep 2023 14:57:30 +0200 Subject: [PATCH 18/42] fix: MigratorTest for MySQL --- tests/lib/DB/MigratorTest.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/lib/DB/MigratorTest.php b/tests/lib/DB/MigratorTest.php index d406e0abcfc0..38c708d928d6 100644 --- a/tests/lib/DB/MigratorTest.php +++ b/tests/lib/DB/MigratorTest.php @@ -10,6 +10,7 @@ namespace Test\DB; use Doctrine\DBAL\Exception as DBALException; +use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\SchemaConfig; @@ -136,7 +137,9 @@ public function testDuplicateKeyUpgrade() { } catch (\Doctrine\DBAL\Exception\UniqueConstraintViolationException $e) { $caught = true; // makes PostgreSQL happier after an exception (and we don't have rollback yet on the public API...) - $this->connection->commit(); + if (!$this->connection->getDatabasePlatform() instanceof MySQLPlatform) { + $this->connection->commit(); + } } $this->assertTrue($caught); } From d99d100c16b7851f7c0582e3e2afcce2bb7f807f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Sun, 1 Oct 2023 10:42:53 +0200 Subject: [PATCH 19/42] chore: write log messagen an exception to error_log in case of exception during Log::writeExtra --- lib/private/Log/Owncloud.php | 127 ++++++++++++++++++----------------- 1 file changed, 66 insertions(+), 61 deletions(-) diff --git a/lib/private/Log/Owncloud.php b/lib/private/Log/Owncloud.php index 54f21dadde46..cecf7f21612b 100644 --- a/lib/private/Log/Owncloud.php +++ b/lib/private/Log/Owncloud.php @@ -68,71 +68,76 @@ public static function write($app, $message, $level, $conditionalLogFile = null) } public static function writeExtra($app, $message, $level, $conditionalLogFile, $extraFields = []) { - $config = \OC::$server->getSystemConfig(); - - // default to ISO8601 - $format = $config->getValue('logdateformat', 'c'); - $logTimeZone = $config->getValue("logtimezone", 'UTC'); try { - $timezone = new \DateTimeZone($logTimeZone); - } catch (\Exception $e) { - $timezone = new \DateTimeZone('UTC'); - } - $time = \DateTime::createFromFormat("U.u", \number_format(\microtime(true), 4, ".", "")); - if ($time === false) { - $time = new \DateTime(null, $timezone); - } else { - // apply timezone if $time is created from UNIX timestamp - $time->setTimezone($timezone); - } - $request = \OC::$server->getRequest(); - $reqId = $request->getId(); - $remoteAddr = $request->getRemoteAddress(); - // remove username/passwords from URLs before writing the to the log file - $time = $time->format($format); - $url = ($request->getRequestUri() !== '') ? $request->getRequestUri() : '--'; - $method = \is_string($request->getMethod()) ? $request->getMethod() : '--'; - if (\OC::$server->getConfig()->getSystemValue('installed', false)) { - $user = (\OC_User::getUser()) ? \OC_User::getUser() : '--'; - } else { - $user = '--'; - } - $entry = \compact( - 'reqId', - 'level', - 'time', - 'remoteAddr', - 'user', - 'app', - 'method', - 'url', - 'message' - ); + $config = \OC::$server->getSystemConfig(); - if (!empty($extraFields)) { - // augment with additional fields - $entry = \array_merge($entry, $extraFields); - } + // default to ISO8601 + $format = $config->getValue('logdateformat', 'c'); + $logTimeZone = $config->getValue("logtimezone", 'UTC'); + try { + $timezone = new \DateTimeZone($logTimeZone); + } catch (\Exception $e) { + $timezone = new \DateTimeZone('UTC'); + } + $time = \DateTime::createFromFormat("U.u", \number_format(\microtime(true), 4, ".", "")); + if ($time === false) { + $time = new \DateTime(null, $timezone); + } else { + // apply timezone if $time is created from UNIX timestamp + $time->setTimezone($timezone); + } + $request = \OC::$server->getRequest(); + $reqId = $request->getId(); + $remoteAddr = $request->getRemoteAddress(); + // remove username/passwords from URLs before writing the to the log file + $time = $time->format($format); + $url = ($request->getRequestUri() !== '') ? $request->getRequestUri() : '--'; + $method = \is_string($request->getMethod()) ? $request->getMethod() : '--'; + if (\OC::$server->getConfig()->getSystemValue('installed', false)) { + $user = (\OC_User::getUser()) ? \OC_User::getUser() : '--'; + } else { + $user = '--'; + } + $entry = \compact( + 'reqId', + 'level', + 'time', + 'remoteAddr', + 'user', + 'app', + 'method', + 'url', + 'message' + ); - $entry = \json_encode($entry); - if ($conditionalLogFile !== null) { - if ($conditionalLogFile[0] !== '/') { - $conditionalLogFile = \OC::$server->getConfig()->getSystemValue('datadirectory') . "/" . $conditionalLogFile; + if (!empty($extraFields)) { + // augment with additional fields + $entry = \array_merge($entry, $extraFields); } - self::createLogFile($conditionalLogFile); - $handle = @\fopen($conditionalLogFile, 'a'); - } else { - self::createLogFile(self::$logFile); - $handle = @\fopen(self::$logFile, 'a'); - } - if ($handle) { - \fwrite($handle, $entry."\n"); - \fclose($handle); - } else { - // Fall back to error_log - \error_log($entry); - } - if (\php_sapi_name() === 'cli-server') { + + $entry = \json_encode($entry); + if ($conditionalLogFile !== null) { + if ($conditionalLogFile[0] !== '/') { + $conditionalLogFile = \OC::$server->getConfig()->getSystemValue('datadirectory') . "/" . $conditionalLogFile; + } + self::createLogFile($conditionalLogFile); + $handle = @\fopen($conditionalLogFile, 'a'); + } else { + self::createLogFile(self::$logFile); + $handle = @\fopen(self::$logFile, 'a'); + } + if ($handle) { + \fwrite($handle, $entry."\n"); + \fclose($handle); + } else { + // Fall back to error_log + \error_log($entry); + } + if (\php_sapi_name() === 'cli-server') { + \error_log($message, 4); + } + } catch(\Exception $ex) { + \error_log($ex->getMessage()); \error_log($message, 4); } } From 9a919320316d66f0fbbc2178e791d9abedb9fa52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Mon, 2 Oct 2023 11:44:35 +0200 Subject: [PATCH 20/42] fix: Share20OcsController --- .../lib/Controller/Share20OcsController.php | 4 +- .../Controller/Share20OcsControllerTest.php | 1098 +++++++++-------- 2 files changed, 559 insertions(+), 543 deletions(-) diff --git a/apps/files_sharing/lib/Controller/Share20OcsController.php b/apps/files_sharing/lib/Controller/Share20OcsController.php index 0a2d706cb78b..6362dfa580cd 100644 --- a/apps/files_sharing/lib/Controller/Share20OcsController.php +++ b/apps/files_sharing/lib/Controller/Share20OcsController.php @@ -1173,10 +1173,10 @@ protected function canAccessShare(IShare $share) { * * @param string $expireDate * - * @throws Exception * @return \DateTime + *@throws Exception */ - private function parseDate($expireDate) { + private function parseDate(string $expireDate) { try { $date = new \DateTime($expireDate); } catch (Exception $e) { diff --git a/apps/files_sharing/tests/Controller/Share20OcsControllerTest.php b/apps/files_sharing/tests/Controller/Share20OcsControllerTest.php index 91ff881663de..9f583ecc2e23 100644 --- a/apps/files_sharing/tests/Controller/Share20OcsControllerTest.php +++ b/apps/files_sharing/tests/Controller/Share20OcsControllerTest.php @@ -44,7 +44,6 @@ use OCP\IUserSession; use OCP\Lock\ILockingProvider; use OCP\Lock\LockedException; -use OCP\Share; use OCP\User\Constants; use PHPUnit\Framework\MockObject\MockObject; use Symfony\Component\EventDispatcher\EventDispatcher; @@ -56,6 +55,10 @@ use OCP\Share\IShare; use OCP\Share\IAttributes as IShareAttributes; use OCP\Share\IManager; +use OCP\Files\File; +use OC\Files\Cache\Cache; +use OC\Files\Storage\Storage; +use OCP\Files\Cache\ICache; /** * Class Share20OcsControllerTest @@ -114,11 +117,9 @@ protected function setUp(): void { ->disableOriginalConstructor() ->getMock(); $this->shareManager - ->expects($this->any()) ->method('shareApiEnabled') ->willReturn(true); $this->shareManager - ->expects($this->any()) ->method('newShare') ->willReturn($this->newShare()); $this->shareManager @@ -148,18 +149,18 @@ protected function setUp(): void { $this->sharee = $this->createMock(IUser::class); $this->sharee->method('getUID')->willReturn('validUser'); - $this->l = $this->createMock('\OCP\IL10N'); + $this->l = $this->createMock(IL10N::class); $this->l->method('t') - ->will($this->returnCallback(function ($text, $parameters = []) { + ->willReturnCallback(function ($text, $parameters = []) { return \vsprintf($text, $parameters); - })); + }); $this->config = $this->createMock(IConfig::class); $this->config->method('getAppValue') - ->will($this->returnValueMap([ + ->willReturnMap([ ['core', 'shareapi_default_permissions', \OCP\Constants::PERMISSION_ALL, \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE], ['core', 'shareapi_auto_accept_share', 'yes', 'yes'], - ])); + ]); $this->notificationPublisher = $this->createMock(NotificationPublisher::class); $this->eventDispatcher = $this->createMock(EventDispatcher::class); @@ -167,7 +168,6 @@ protected function setUp(): void { $this->sharingAllowlist= $this->createMock(SharingAllowlist::class); $this->userTypeHelper = $this->createMock(UserTypeHelper::class); $this->userTypeHelper - ->expects($this->any()) ->method('getUserType') ->willReturn(Constants::USER_TYPE_USER); @@ -215,7 +215,7 @@ private function mockFormatShare() { ->getMock(); } - private function newShare() { + private function newShare(): IShare { return \OC::$server->getShareManager()->newShare(); } @@ -234,7 +234,7 @@ private function getGroupMock(array $attrs) { return $groupMock; } - private function mockShareAttributes() { + private function mockShareAttributes(): array { $formattedShareAttributes = [ [ [ @@ -253,17 +253,17 @@ private function mockShareAttributes() { return [$shareAttributes, \json_encode($formattedShareAttributes)]; } - public function testDeleteShareShareNotFound() { + public function testDeleteShareShareNotFound(): void { $this->shareManager ->expects($this->exactly(2)) ->method('getShareById') - ->will($this->returnCallback(function ($id) { + ->willReturnCallback(function ($id) { if ($id === 'ocinternal:42' || $id === 'ocFederatedSharing:42') { - throw new \OCP\Share\Exceptions\ShareNotFound(); - } else { - throw new \Exception(); + throw new ShareNotFound(); } - })); + + throw new \Exception(); + }); $this->shareManager->method('outgoingServer2ServerSharesAllowed')->willReturn(true); @@ -271,8 +271,8 @@ public function testDeleteShareShareNotFound() { $this->assertEquals($expected, $this->ocs->deleteShare(42)); } - public function testDeleteShare() { - $node = $this->createMock('\OCP\Files\File'); + public function testDeleteShare(): void { + $node = $this->createMock(File::class); $share = $this->newShare(); $share->setSharedBy($this->currentUser->getUID()) @@ -298,8 +298,8 @@ public function testDeleteShare() { $this->assertEquals($expected, $this->ocs->deleteShare(42)); } - public function testDeleteShareLocked() { - $node = $this->createMock('\OCP\Files\File'); + public function testDeleteShareLocked(): void { + $node = $this->createMock(File::class); $share = $this->newShare(); $share->setSharedBy($this->currentUser->getUID()) @@ -379,52 +379,52 @@ public function createShare( $share->method('getPassword')->willReturn($password); $share->method('getName')->willReturn($name); - if ($shareType === Share::SHARE_TYPE_USER || - $shareType === Share::SHARE_TYPE_GROUP || - $shareType === Share::SHARE_TYPE_LINK) { + if ($shareType === \OC\Share\Constants::SHARE_TYPE_USER || + $shareType === \OC\Share\Constants::SHARE_TYPE_GROUP || + $shareType === \OC\Share\Constants::SHARE_TYPE_LINK) { $share->method('getFullId')->willReturn('ocinternal:'.$id); } return $share; } - public function dataGetShare() { + public function dataGetShare(): array { $data = []; - $cache = $this->getMockBuilder('OC\Files\Cache\Cache') + $cache = $this->getMockBuilder(Cache::class) ->disableOriginalConstructor() ->getMock(); $cache->method('getNumericStorageId')->willReturn(101); - $storage = $this->getMockBuilder('OC\Files\Storage\Storage') + $storage = $this->getMockBuilder(Storage::class) ->disableOriginalConstructor() ->getMock(); $storage->method('getId')->willReturn('STORAGE'); $storage->method('getCache')->willReturn($cache); - $parentFolder = $this->createMock('OCP\Files\Folder'); + $parentFolder = $this->createMock(Folder::class); $parentFolder->method('getId')->willReturn(3); - $file = $this->createMock('OCP\Files\File'); + $file = $this->createMock(File::class); $file->method('getId')->willReturn(1); $file->method('getPath')->willReturn('file'); $file->method('getStorage')->willReturn($storage); $file->method('getParent')->willReturn($parentFolder); $file->method('getMimeType')->willReturn('myMimeType'); - $folder = $this->createMock('OCP\Files\Folder'); + $folder = $this->createMock(Folder::class); $folder->method('getId')->willReturn(2); $folder->method('getPath')->willReturn('folder'); $folder->method('getStorage')->willReturn($storage); $folder->method('getParent')->willReturn($parentFolder); $folder->method('getMimeType')->willReturn('myFolderMimeType'); - list($shareAttributes, $shareAttributesReturnJson) = $this->mockShareAttributes(); + [$shareAttributes, $shareAttributesReturnJson] = $this->mockShareAttributes(); // File shared with user $share = $this->createShare( 100, - Share::SHARE_TYPE_USER, + \OC\Share\Constants::SHARE_TYPE_USER, 'userId', 'initiatorId', 'ownerId', @@ -442,7 +442,7 @@ public function dataGetShare() { ); $expected = [ 'id' => 100, - 'share_type' => Share::SHARE_TYPE_USER, + 'share_type' => \OC\Share\Constants::SHARE_TYPE_USER, 'share_with' => 'userId', 'share_with_displayname' => 'userDisplay', 'share_with_user_type' => Constants::USER_TYPE_USER, @@ -475,7 +475,7 @@ public function dataGetShare() { // Folder shared with group $share = $this->createShare( 101, - Share::SHARE_TYPE_GROUP, + \OC\Share\Constants::SHARE_TYPE_GROUP, 'groupId', 'initiatorId', 'ownerId', @@ -493,7 +493,7 @@ public function dataGetShare() { ); $expected = [ 'id' => 101, - 'share_type' => Share::SHARE_TYPE_GROUP, + 'share_type' => \OC\Share\Constants::SHARE_TYPE_GROUP, 'share_with' => 'groupId', 'share_with_displayname' => 'groupId', 'uid_owner' => 'initiatorId', @@ -525,7 +525,7 @@ public function dataGetShare() { $expire = \DateTime::createFromFormat('Y-m-d h:i:s', '2000-01-02 01:02:03'); $share = $this->createShare( 101, - Share::SHARE_TYPE_LINK, + \OC\Share\Constants::SHARE_TYPE_LINK, null, 'initiatorId', 'ownerId', @@ -542,7 +542,7 @@ public function dataGetShare() { ); $expected = [ 'id' => 101, - 'share_type' => Share::SHARE_TYPE_LINK, + 'share_type' => \OC\Share\Constants::SHARE_TYPE_LINK, 'share_with' => '***redacted***', 'share_with_displayname' => '***redacted***', 'uid_owner' => 'initiatorId', @@ -578,7 +578,7 @@ public function dataGetShare() { /** * @dataProvider dataGetShare */ - public function testGetShare(\OCP\Share\IShare $share, array $result) { + public function testGetShare(IShare $share, array $result): void { $ocs = $this->getMockBuilder(Share20OcsController::class) ->setConstructorArgs([ 'files_sharing', @@ -599,7 +599,10 @@ public function testGetShare(\OCP\Share\IShare $share, array $result) { ])->setMethods(['canAccessShare']) ->getMock(); - $ocs->expects($this->any())->method('canAccessShare')->willReturn(true); + $ocs + ->expects($this->once()) + ->method('canAccessShare') + ->willReturn(true); $this->shareManager ->expects($this->once()) @@ -607,7 +610,7 @@ public function testGetShare(\OCP\Share\IShare $share, array $result) { ->with($share->getFullId()) ->willReturn($share); - $userFolder = $this->createMock('OCP\Files\Folder'); + $userFolder = $this->createMock(Folder::class); $userFolder ->method('getRelativePath') ->will($this->returnArgument(0)); @@ -624,35 +627,35 @@ public function testGetShare(\OCP\Share\IShare $share, array $result) { ->method('linkToRouteAbsolute') ->willReturn('url'); - $initiator = $this->createMock('OCP\IUser'); + $initiator = $this->createMock(IUser::class); $initiator->method('getUID')->willReturn('initiatorId'); $initiator->method('getDisplayName')->willReturn('initiatorDisplay'); - $owner = $this->createMock('OCP\IUser'); + $owner = $this->createMock(IUser::class); $owner->method('getUID')->willReturn('ownerId'); $owner->method('getDisplayName')->willReturn('ownerDisplay'); - $user = $this->createMock('OCP\IUser'); + $user = $this->createMock(IUser::class); $user->method('getUID')->willReturn('userId'); $user->method('getDisplayName')->willReturn('userDisplay'); - $group = $this->createMock('OCP\IGroup'); + $group = $this->createMock(IGroup::class); $group->method('getGID')->willReturn('groupId'); - $this->userManager->method('get')->will($this->returnValueMap([ + $this->userManager->method('get')->willReturnMap([ ['userId', false, $user], ['initiatorId', false, $initiator], ['ownerId', false, $owner], - ])); - $this->groupManager->method('get')->will($this->returnValueMap([ + ]); + $this->groupManager->method('get')->willReturnMap([ ['group', $group], - ])); + ]); $expected = new Result([$result]); $this->assertEquals($expected->getData(), $ocs->getShare($share->getId())->getData()); } - public function testGetShareInvalidNode() { + public function testGetShareInvalidNode(): void { $share = \OC::$server->getShareManager()->newShare(); $share->setSharedBy('initiator') ->setSharedWith('recipient') @@ -668,73 +671,73 @@ public function testGetShareInvalidNode() { $this->assertEquals($expected->getMeta(), $this->ocs->getShare(42)->getMeta()); } - public function testCanAccessShare() { - $share = $this->createMock('OCP\Share\IShare'); + public function testCanAccessShare(): void { + $share = $this->createMock(IShare::class); $share->method('getShareOwner')->willReturn($this->currentUser->getUID()); $this->assertTrue(self::invokePrivate($this->ocs, 'canAccessShare', [$share])); - $share = $this->createMock('OCP\Share\IShare'); + $share = $this->createMock(IShare::class); $share->method('getSharedBy')->willReturn($this->currentUser->getUID()); $this->assertTrue(self::invokePrivate($this->ocs, 'canAccessShare', [$share])); - $share = $this->createMock('OCP\Share\IShare'); - $share->method('getShareType')->willReturn(Share::SHARE_TYPE_USER); + $share = $this->createMock(IShare::class); + $share->method('getShareType')->willReturn(\OC\Share\Constants::SHARE_TYPE_USER); $share->method('getSharedWith')->willReturn($this->currentUser->getUID()); $this->assertTrue(self::invokePrivate($this->ocs, 'canAccessShare', [$share])); - $share = $this->createMock('OCP\Share\IShare'); - $share->method('getShareType')->willReturn(Share::SHARE_TYPE_USER); - $share->method('getSharedWith')->willReturn($this->createMock('OCP\IUser')); + $share = $this->createMock(IShare::class); + $share->method('getShareType')->willReturn(\OC\Share\Constants::SHARE_TYPE_USER); + $share->method('getSharedWith')->willReturn($this->createMock(IUser::class)); $this->assertFalse(self::invokePrivate($this->ocs, 'canAccessShare', [$share])); - $share = $this->createMock('OCP\Share\IShare'); - $share->method('getShareType')->willReturn(Share::SHARE_TYPE_GROUP); + $share = $this->createMock(IShare::class); + $share->method('getShareType')->willReturn(\OC\Share\Constants::SHARE_TYPE_GROUP); $share->method('getSharedWith')->willReturn('group'); - $group = $this->createMock('OCP\IGroup'); + $group = $this->createMock(IGroup::class); $group->method('inGroup')->with($this->currentUser)->willReturn(true); - $group2 = $this->createMock('OCP\IGroup'); + $group2 = $this->createMock(IGroup::class); $group2->method('inGroup')->with($this->currentUser)->willReturn(false); - $this->groupManager->method('get')->will($this->returnValueMap([ + $this->groupManager->method('get')->willReturnMap([ ['group', $group], ['group2', $group2], ['groupnull', null], - ])); + ]); $this->assertTrue(self::invokePrivate($this->ocs, 'canAccessShare', [$share])); - $share = $this->createMock('OCP\Share\IShare'); - $share->method('getShareType')->willReturn(Share::SHARE_TYPE_GROUP); + $share = $this->createMock(IShare::class); + $share->method('getShareType')->willReturn(\OC\Share\Constants::SHARE_TYPE_GROUP); $share->method('getSharedWith')->willReturn('group2'); $this->assertFalse(self::invokePrivate($this->ocs, 'canAccessShare', [$share])); // null group - $share = $this->createMock('OCP\Share\IShare'); - $share->method('getShareType')->willReturn(\OCP\Share::SHARE_TYPE_GROUP); + $share = $this->createMock(IShare::class); + $share->method('getShareType')->willReturn(\OC\Share\Constants::SHARE_TYPE_GROUP); $share->method('getSharedWith')->willReturn('groupnull'); $this->assertFalse(self::invokePrivate($this->ocs, 'canAccessShare', [$share])); - $share = $this->createMock('OCP\Share\IShare'); - $share->method('getShareType')->willReturn(Share::SHARE_TYPE_LINK); + $share = $this->createMock(IShare::class); + $share->method('getShareType')->willReturn(\OC\Share\Constants::SHARE_TYPE_LINK); $this->assertFalse(self::invokePrivate($this->ocs, 'canAccessShare', [$share])); // should not happen ever again, but who knows... let's cover it - $share = $this->createMock('OCP\Share\IShare'); - $share->method('getShareType')->willReturn(Share::SHARE_TYPE_USER); - $share->method('getState')->willReturn(Share::STATE_ACCEPTED); + $share = $this->createMock(IShare::class); + $share->method('getShareType')->willReturn(\OC\Share\Constants::SHARE_TYPE_USER); + $share->method('getState')->willReturn(\OC\Share\Constants::STATE_ACCEPTED); $share->method('getPermissions')->willReturn(0); $this->assertFalse(self::invokePrivate($this->ocs, 'canAccessShare', [$share])); // legacy zero permission entries from group sub-shares, let it pass - $share = $this->createMock('OCP\Share\IShare'); - $share->method('getShareType')->willReturn(Share::SHARE_TYPE_GROUP); + $share = $this->createMock(IShare::class); + $share->method('getShareType')->willReturn(\OC\Share\Constants::SHARE_TYPE_GROUP); $share->method('getSharedWith')->willReturn('group'); - $share->method('getState')->willReturn(Share::STATE_REJECTED); + $share->method('getState')->willReturn(\OC\Share\Constants::STATE_REJECTED); $share->method('getPermissions')->willReturn(0); $this->assertTrue(self::invokePrivate($this->ocs, 'canAccessShare', [$share])); } - public function testCreateShareNoPath() { + public function testCreateShareNoPath(): void { $expected = new Result(null, 404, 'Please specify a file or folder path'); $result = $this->ocs->createShare(); @@ -743,14 +746,14 @@ public function testCreateShareNoPath() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testCreateShareInvalidPath() { + public function testCreateShareInvalidPath(): void { $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['path', null, 'invalid-path'], - ])); + ]); - $userFolder = $this->createMock('\OCP\Files\Folder'); + $userFolder = $this->createMock(Folder::class); $this->rootFolder->expects($this->once()) ->method('getUserFolder') ->with('currentUser') @@ -759,7 +762,7 @@ public function testCreateShareInvalidPath() { $userFolder->expects($this->once()) ->method('get') ->with('invalid-path') - ->will($this->throwException(new \OCP\Files\NotFoundException())); + ->will($this->throwException(new NotFoundException())); $expected = new Result(null, 404, 'Wrong path, file/folder doesn\'t exist'); @@ -769,28 +772,29 @@ public function testCreateShareInvalidPath() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testCreateShareUserNoShareWith() { + public function testCreateShareUserNoShareWith(): void { $share = $this->newShare(); $this->shareManager->method('newShare')->willReturn($share); $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['path', null, 'valid-path'], ['permissions', null, \OCP\Constants::PERMISSION_ALL], - ['shareType', $this->any(), Share::SHARE_TYPE_USER], - ])); + ['shareType', $this->any(), \OC\Share\Constants::SHARE_TYPE_USER], + ['expireDate', '', ''], + ]); - $userFolder = $this->createMock('\OCP\Files\Folder'); + $userFolder = $this->createMock(Folder::class); $this->rootFolder->expects($this->once()) ->method('getUserFolder') ->with('currentUser') ->willReturn($userFolder); - $path = $this->createMock('\OCP\Files\File'); - $storage = $this->createMock('OCP\Files\Storage'); + $path = $this->createMock(File::class); + $storage = $this->createMock(\OCP\Files\Storage::class); $storage->method('instanceOfStorage') - ->with('OCA\Files_Sharing\External\Storage') + ->with(\OCA\Files_Sharing\External\Storage::class) ->willReturn(false); $path->method('getStorage')->willReturn($storage); $userFolder->expects($this->once()) @@ -812,7 +816,7 @@ public function testCreateShareUserNoShareWith() { $this->assertEquals($expected->getData(), $result->getData()); } - public function InvalidShareWithProvider() { + public function InvalidShareWithProvider(): array { return [ ['invaliduser'], [123456], @@ -824,29 +828,30 @@ public function InvalidShareWithProvider() { * @dataProvider InvalidShareWithProvider * @param mixed $shareWith */ - public function testCreateShareUserNoValidShareWith($shareWith) { + public function testCreateShareUserNoValidShareWith($shareWith): void { $share = $this->newShare(); $this->shareManager->method('newShare')->willReturn($share); $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['path', null, 'valid-path'], ['permissions', null, \OCP\Constants::PERMISSION_ALL], - ['shareType', $this->any(), Share::SHARE_TYPE_USER], + ['shareType', $this->any(), \OC\Share\Constants::SHARE_TYPE_USER], ['shareWith', $this->any(), $shareWith], - ])); + ['expireDate', '', ''], + ]); - $userFolder = $this->createMock('\OCP\Files\Folder'); + $userFolder = $this->createMock(Folder::class); $this->rootFolder->expects($this->once()) ->method('getUserFolder') ->with('currentUser') ->willReturn($userFolder); - $path = $this->createMock('\OCP\Files\File'); - $storage = $this->createMock('OCP\Files\Storage'); + $path = $this->createMock(File::class); + $storage = $this->createMock(\OCP\Files\Storage::class); $storage->method('instanceOfStorage') - ->with('OCA\Files_Sharing\External\Storage') + ->with(\OCA\Files_Sharing\External\Storage::class) ->willReturn(false); $path->method('getStorage')->willReturn($storage); $userFolder->expects($this->once()) @@ -866,7 +871,7 @@ public function testCreateShareUserNoValidShareWith($shareWith) { $this->assertEquals($expected->getData(), $result->getData()); } - public function testCreateShareUser() { + public function testCreateShareUser(): void { $share = $this->newShare(); $this->shareManager->method('newShare')->willReturn($share); @@ -879,20 +884,21 @@ public function testCreateShareUser() { ->willReturnMap([ ['path', null, 'valid-path'], ['permissions', null, \OCP\Constants::PERMISSION_ALL], - ['shareType', $this->any(), Share::SHARE_TYPE_USER], + ['shareType', $this->any(), \OC\Share\Constants::SHARE_TYPE_USER], ['shareWith', null, 'validUser'], + ['expireDate', '', ''], ]); - $userFolder = $this->createMock('\OCP\Files\Folder'); + $userFolder = $this->createMock(Folder::class); $this->rootFolder->expects($this->once()) ->method('getUserFolder') ->with('currentUser') ->willReturn($userFolder); - $path = $this->createMock('\OCP\Files\File'); - $storage = $this->createMock('OCP\Files\Storage'); + $path = $this->createMock(File::class); + $storage = $this->createMock(\OCP\Files\Storage::class); $storage->method('instanceOfStorage') - ->with('OCA\Files_Sharing\External\Storage') + ->with(\OCA\Files_Sharing\External\Storage::class) ->willReturn(false); $path->method('getStorage')->willReturn($storage); $userFolder->expects($this->once()) @@ -910,12 +916,12 @@ public function testCreateShareUser() { ->with(ILockingProvider::LOCK_SHARED); $this->shareManager->method('createShare') - ->with($this->callback(function (\OCP\Share\IShare $share) use ($path) { + ->with($this->callback(function (IShare $share) use ($path) { return $share->getNode() === $path && $share->getPermissions() === ( \OCP\Constants::PERMISSION_ALL ) && - $share->getShareType() === Share::SHARE_TYPE_USER && + $share->getShareType() === \OC\Share\Constants::SHARE_TYPE_USER && $share->getSharedWith() === 'validUser' && $share->getSharedBy() === 'currentUser'; })) @@ -932,30 +938,31 @@ public function testCreateShareUser() { * @dataProvider InvalidShareWithProvider * @param mixed $shareWith */ - public function testCreateShareGroupNoValidShareWith($shareWith) { + public function testCreateShareGroupNoValidShareWith($shareWith): void { $share = $this->newShare(); $this->shareManager->method('newShare')->willReturn($share); $this->shareManager->method('createShare')->will($this->returnArgument(0)); $this->request ->method('getParam') - ->will($this->returnValueMap([ - ['path', null, 'valid-path'], - ['permissions', null, \OCP\Constants::PERMISSION_ALL], - ['shareType', '-1', Share::SHARE_TYPE_GROUP], - ['shareWith', null, $shareWith], - ])); - - $userFolder = $this->createMock('\OCP\Files\Folder'); + ->willReturnMap([ + ['path', null, 'valid-path'], + ['permissions', null, \OCP\Constants::PERMISSION_ALL], + ['shareType', '-1', \OC\Share\Constants::SHARE_TYPE_GROUP], + ['shareWith', null, $shareWith], + ['expireDate', '', ''], + ]); + + $userFolder = $this->createMock(Folder::class); $this->rootFolder->expects($this->once()) ->method('getUserFolder') ->with('currentUser') ->willReturn($userFolder); - $path = $this->createMock('\OCP\Files\File'); - $storage = $this->createMock('OCP\Files\Storage'); + $path = $this->createMock(File::class); + $storage = $this->createMock(\OCP\Files\Storage::class); $storage->method('instanceOfStorage') - ->with('OCA\Files_Sharing\External\Storage') + ->with(\OCA\Files_Sharing\External\Storage::class) ->willReturn(false); $path->method('getStorage')->willReturn($storage); $userFolder->expects($this->once()) @@ -979,28 +986,29 @@ public function testCreateShareGroupNoValidShareWith($shareWith) { $this->assertEquals($expected->getData(), $result->getData()); } - public function testCreateShareGroupBlacklisted() { + public function testCreateShareGroupBlacklisted(): void { $share = $this->newShare(); $this->shareManager->method('newShare')->willReturn($share); $this->request->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['path', null, 'valid-path'], ['permissions', null, \OCP\Constants::PERMISSION_ALL], - ['shareType', '-1', Share::SHARE_TYPE_GROUP], + ['shareType', '-1', \OC\Share\Constants::SHARE_TYPE_GROUP], ['shareWith', null, 'validGroup'], - ])); + ['expireDate', '', ''], + ]); - $userFolder = $this->createMock('\OCP\Files\Folder'); + $userFolder = $this->createMock(Folder::class); $this->rootFolder->expects($this->once()) ->method('getUserFolder') ->with('currentUser') ->willReturn($userFolder); - $path = $this->createMock('\OCP\Files\Folder'); - $storage = $this->createMock('OCP\Files\Storage'); + $path = $this->createMock(Folder::class); + $storage = $this->createMock(\OCP\Files\Storage::class); $storage->method('instanceOfStorage') - ->with('OCA\Files_Sharing\External\Storage') + ->with(\OCA\Files_Sharing\External\Storage::class) ->willReturn(false); $path->method('getStorage')->willReturn($storage); $userFolder->expects($this->once()) @@ -1027,7 +1035,7 @@ public function testCreateShareGroupBlacklisted() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testCreateShareGroup() { + public function testCreateShareGroup(): void { $share = $this->newShare(); $this->shareManager->method('newShare')->willReturn($share); @@ -1035,23 +1043,24 @@ public function testCreateShareGroup() { $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['path', null, 'valid-path'], ['permissions', null, \OCP\Constants::PERMISSION_ALL], - ['shareType', '-1', Share::SHARE_TYPE_GROUP], + ['shareType', '-1', \OC\Share\Constants::SHARE_TYPE_GROUP], ['shareWith', null, 'validGroup'], - ])); + ['expireDate', '', ''], + ]); - $userFolder = $this->createMock('\OCP\Files\Folder'); + $userFolder = $this->createMock(Folder::class); $this->rootFolder->expects($this->once()) ->method('getUserFolder') ->with('currentUser') ->willReturn($userFolder); - $path = $this->createMock('\OCP\Files\Folder'); - $storage = $this->createMock('OCP\Files\Storage'); + $path = $this->createMock(Folder::class); + $storage = $this->createMock(\OCP\Files\Storage::class); $storage->method('instanceOfStorage') - ->with('OCA\Files_Sharing\External\Storage') + ->with(\OCA\Files_Sharing\External\Storage::class) ->willReturn(false); $path->method('getStorage')->willReturn($storage); $userFolder->expects($this->once()) @@ -1076,10 +1085,10 @@ public function testCreateShareGroup() { ->with(ILockingProvider::LOCK_SHARED); $this->shareManager->method('createShare') - ->with($this->callback(function (\OCP\Share\IShare $share) use ($path) { + ->with($this->callback(function (IShare $share) use ($path) { return $share->getNode() === $path && $share->getPermissions() === \OCP\Constants::PERMISSION_ALL && - $share->getShareType() === Share::SHARE_TYPE_GROUP && + $share->getShareType() === \OC\Share\Constants::SHARE_TYPE_GROUP && $share->getSharedWith() === 'validGroup' && $share->getSharedBy() === 'currentUser'; })) @@ -1094,29 +1103,30 @@ public function testCreateShareGroup() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testCreateShareGroupNotAllowed() { + public function testCreateShareGroupNotAllowed(): void { $share = $this->newShare(); $this->shareManager->method('newShare')->willReturn($share); $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['path', null, 'valid-path'], ['permissions', null, \OCP\Constants::PERMISSION_ALL], - ['shareType', '-1', Share::SHARE_TYPE_GROUP], + ['shareType', '-1', \OC\Share\Constants::SHARE_TYPE_GROUP], ['shareWith', null, 'validGroup'], - ])); + ['expireDate', '', ''], + ]); - $userFolder = $this->createMock('\OCP\Files\Folder'); + $userFolder = $this->createMock(Folder::class); $this->rootFolder->expects($this->once()) ->method('getUserFolder') ->with('currentUser') ->willReturn($userFolder); - $path = $this->createMock('\OCP\Files\Folder'); - $storage = $this->createMock('OCP\Files\Storage'); + $path = $this->createMock(Folder::class); + $storage = $this->createMock(\OCP\Files\Storage::class); $storage->method('instanceOfStorage') - ->with('OCA\Files_Sharing\External\Storage') + ->with(\OCA\Files_Sharing\External\Storage::class) ->willReturn(false); $path->method('getStorage')->willReturn($storage); $userFolder->expects($this->once()) @@ -1138,18 +1148,19 @@ public function testCreateShareGroupNotAllowed() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testCreateShareLinkNoLinksAllowed() { + public function testCreateShareLinkNoLinksAllowed(): void { $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['path', null, 'valid-path'], - ['shareType', '-1', Share::SHARE_TYPE_LINK], - ])); + ['shareType', '-1', \OC\Share\Constants::SHARE_TYPE_LINK], + ['expireDate', '', ''], + ]); - $path = $this->createMock('\OCP\Files\Folder'); - $storage = $this->createMock('OCP\Files\Storage'); + $path = $this->createMock(Folder::class); + $storage = $this->createMock(\OCP\Files\Storage::class); $storage->method('instanceOfStorage') - ->with('OCA\Files_Sharing\External\Storage') + ->with(\OCA\Files_Sharing\External\Storage::class) ->willReturn(false); $path->method('getStorage')->willReturn($storage); $this->rootFolder->method('getUserFolder')->with($this->currentUser->getUID())->will($this->returnSelf()); @@ -1164,19 +1175,20 @@ public function testCreateShareLinkNoLinksAllowed() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testCreateShareLinkNoPublicUpload() { + public function testCreateShareLinkNoPublicUpload(): void { $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['path', null, 'valid-path'], - ['shareType', '-1', Share::SHARE_TYPE_LINK], + ['shareType', '-1', \OC\Share\Constants::SHARE_TYPE_LINK], ['publicUpload', null, 'true'], - ])); + ['expireDate', '', ''], + ]); - $path = $this->createMock('\OCP\Files\Folder'); - $storage = $this->createMock('OCP\Files\Storage'); + $path = $this->createMock(Folder::class); + $storage = $this->createMock(\OCP\Files\Storage::class); $storage->method('instanceOfStorage') - ->with('OCA\Files_Sharing\External\Storage') + ->with(\OCA\Files_Sharing\External\Storage::class) ->willReturn(false); $path->method('getStorage')->willReturn($storage); $this->rootFolder->method('getUserFolder')->with($this->currentUser->getUID())->will($this->returnSelf()); @@ -1192,19 +1204,20 @@ public function testCreateShareLinkNoPublicUpload() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testCreateShareLinkNotInAllowlist() { + public function testCreateShareLinkNotInAllowlist(): void { $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['path', null, 'valid-path'], - ['shareType', '-1', Share::SHARE_TYPE_LINK], + ['shareType', '-1', \OC\Share\Constants::SHARE_TYPE_LINK], ['publicUpload', null, 'true'], - ])); + ['expireDate', '', ''], + ]); - $path = $this->createMock('\OCP\Files\Folder'); - $storage = $this->createMock('OCP\Files\Storage'); + $path = $this->createMock(Folder::class); + $storage = $this->createMock(\OCP\Files\Storage::class); $storage->method('instanceOfStorage') - ->with('OCA\Files_Sharing\External\Storage') + ->with(\OCA\Files_Sharing\External\Storage::class) ->willReturn(false); $path->method('getStorage')->willReturn($storage); $this->rootFolder->method('getUserFolder')->with($this->currentUser->getUID())->will($this->returnSelf()); @@ -1224,19 +1237,20 @@ public function testCreateShareLinkNotInAllowlist() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testCreateShareLinkPublicUploadFile() { + public function testCreateShareLinkPublicUploadFile(): void { $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['path', null, 'valid-path'], - ['shareType', '-1', Share::SHARE_TYPE_LINK], + ['shareType', '-1', \OC\Share\Constants::SHARE_TYPE_LINK], ['publicUpload', null, 'true'], - ])); + ['expireDate', '', ''], + ]); - $path = $this->createMock('\OCP\Files\File'); - $storage = $this->createMock('OCP\Files\Storage'); + $path = $this->createMock(File::class); + $storage = $this->createMock(\OCP\Files\Storage::class); $storage->method('instanceOfStorage') - ->with('OCA\Files_Sharing\External\Storage') + ->with(\OCA\Files_Sharing\External\Storage::class) ->willReturn(false); $path->method('getStorage')->willReturn($storage); $this->rootFolder->method('getUserFolder')->with($this->currentUser->getUID())->will($this->returnSelf()); @@ -1253,23 +1267,23 @@ public function testCreateShareLinkPublicUploadFile() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testCreateShareLinkPublicUploadFolder() { + public function testCreateShareLinkPublicUploadFolder(): void { $ocs = $this->mockFormatShare(); $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['path', null, 'valid-path'], - ['shareType', '-1', Share::SHARE_TYPE_LINK], + ['shareType', '-1', \OC\Share\Constants::SHARE_TYPE_LINK], ['publicUpload', null, 'true'], ['expireDate', '', ''], ['password', '', ''], - ])); + ]); - $path = $this->createMock('\OCP\Files\Folder'); - $storage = $this->createMock('OCP\Files\Storage'); + $path = $this->createMock(Folder::class); + $storage = $this->createMock(\OCP\Files\Storage::class); $storage->method('instanceOfStorage') - ->with('OCA\Files_Sharing\External\Storage') + ->with(\OCA\Files_Sharing\External\Storage::class) ->willReturn(false); $path->method('getStorage')->willReturn($storage); $this->rootFolder->method('getUserFolder')->with($this->currentUser->getUID())->will($this->returnSelf()); @@ -1280,9 +1294,9 @@ public function testCreateShareLinkPublicUploadFolder() { $this->shareManager->method('shareApiLinkAllowPublicUpload')->willReturn(true); $this->shareManager->expects($this->once())->method('createShare')->with( - $this->callback(function (\OCP\Share\IShare $share) use ($path) { + $this->callback(function (IShare $share) use ($path) { return $share->getNode() === $path && - $share->getShareType() === Share::SHARE_TYPE_LINK && + $share->getShareType() === \OC\Share\Constants::SHARE_TYPE_LINK && $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) && $share->getSharedBy() === 'currentUser' && $share->getPassword() === null && @@ -1297,23 +1311,23 @@ public function testCreateShareLinkPublicUploadFolder() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testCreateShareLinkReadWritePermissions() { + public function testCreateShareLinkReadWritePermissions(): void { $ocs = $this->mockFormatShare(); $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['path', null, 'valid-path'], - ['shareType', '-1', Share::SHARE_TYPE_LINK], + ['shareType', '-1', \OC\Share\Constants::SHARE_TYPE_LINK], ['permissions', null, '15'], ['expireDate', '', ''], ['password', '', ''], - ])); + ]); - $path = $this->createMock('\OCP\Files\Folder'); - $storage = $this->createMock('OCP\Files\Storage'); + $path = $this->createMock(Folder::class); + $storage = $this->createMock(\OCP\Files\Storage::class); $storage->method('instanceOfStorage') - ->with('OCA\Files_Sharing\External\Storage') + ->with(\OCA\Files_Sharing\External\Storage::class) ->willReturn(false); $path->method('getStorage')->willReturn($storage); $this->rootFolder->method('getUserFolder')->with($this->currentUser->getUID())->will($this->returnSelf()); @@ -1324,9 +1338,9 @@ public function testCreateShareLinkReadWritePermissions() { $this->shareManager->method('shareApiLinkAllowPublicUpload')->willReturn(true); $this->shareManager->expects($this->once())->method('createShare')->with( - $this->callback(function (\OCP\Share\IShare $share) use ($path) { + $this->callback(function (IShare $share) use ($path) { return $share->getNode() === $path && - $share->getShareType() === Share::SHARE_TYPE_LINK && + $share->getShareType() === \OC\Share\Constants::SHARE_TYPE_LINK && $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) && $share->getSharedBy() === 'currentUser' && $share->getPassword() === null && @@ -1341,23 +1355,23 @@ public function testCreateShareLinkReadWritePermissions() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testCreateShareLinkCreateOnlyFolder() { + public function testCreateShareLinkCreateOnlyFolder(): void { $ocs = $this->mockFormatShare(); $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['path', null, 'valid-path'], - ['shareType', '-1', Share::SHARE_TYPE_LINK], + ['shareType', '-1', \OC\Share\Constants::SHARE_TYPE_LINK], ['permissions', null, '4'], ['expireDate', '', ''], ['password', '', ''], - ])); + ]); - $path = $this->createMock('\OCP\Files\Folder'); - $storage = $this->createMock('OCP\Files\Storage'); + $path = $this->createMock(Folder::class); + $storage = $this->createMock(\OCP\Files\Storage::class); $storage->method('instanceOfStorage') - ->with('OCA\Files_Sharing\External\Storage') + ->with(\OCA\Files_Sharing\External\Storage::class) ->willReturn(false); $path->method('getStorage')->willReturn($storage); $this->rootFolder->method('getUserFolder')->with($this->currentUser->getUID())->will($this->returnSelf()); @@ -1368,9 +1382,9 @@ public function testCreateShareLinkCreateOnlyFolder() { $this->shareManager->method('shareApiLinkAllowPublicUpload')->willReturn(true); $this->shareManager->expects($this->once())->method('createShare')->with( - $this->callback(function (\OCP\Share\IShare $share) use ($path) { + $this->callback(function (IShare $share) use ($path) { return $share->getNode() === $path && - $share->getShareType() === Share::SHARE_TYPE_LINK && + $share->getShareType() === \OC\Share\Constants::SHARE_TYPE_LINK && $share->getPermissions() === (\OCP\Constants::PERMISSION_CREATE) && $share->getSharedBy() === 'currentUser' && $share->getPassword() === null && @@ -1385,23 +1399,23 @@ public function testCreateShareLinkCreateOnlyFolder() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testCreateShareLinkCreateOnlyFolderPublicUploadDisabled() { + public function testCreateShareLinkCreateOnlyFolderPublicUploadDisabled(): void { $ocs = $this->mockFormatShare(); $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['path', null, 'valid-path'], - ['shareType', '-1', Share::SHARE_TYPE_LINK], + ['shareType', '-1', \OC\Share\Constants::SHARE_TYPE_LINK], ['permissions', null, '4'], ['expireDate', '', ''], ['password', '', ''], - ])); + ]); - $path = $this->createMock('\OCP\Files\Folder'); - $storage = $this->createMock('OCP\Files\Storage'); + $path = $this->createMock(Folder::class); + $storage = $this->createMock(\OCP\Files\Storage::class); $storage->method('instanceOfStorage') - ->with('OCA\Files_Sharing\External\Storage') + ->with(\OCA\Files_Sharing\External\Storage::class) ->willReturn(false); $path->method('getStorage')->willReturn($storage); $this->rootFolder->method('getUserFolder')->with($this->currentUser->getUID())->will($this->returnSelf()); @@ -1418,22 +1432,22 @@ public function testCreateShareLinkCreateOnlyFolderPublicUploadDisabled() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testCreateShareLinkDefaultPerms() { + public function testCreateShareLinkDefaultPerms(): void { $ocs = $this->mockFormatShare(); $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['path', null, 'valid-path'], - ['shareType', '-1', Share::SHARE_TYPE_LINK], + ['shareType', '-1', \OC\Share\Constants::SHARE_TYPE_LINK], ['expireDate', '', ''], ['password', '', ''], - ])); + ]); - $path = $this->createMock('\OCP\Files\Folder'); - $storage = $this->createMock('OCP\Files\Storage'); + $path = $this->createMock(Folder::class); + $storage = $this->createMock(\OCP\Files\Storage::class); $storage->method('instanceOfStorage') - ->with('OCA\Files_Sharing\External\Storage') + ->with(\OCA\Files_Sharing\External\Storage::class) ->willReturn(false); $path->method('getStorage')->willReturn($storage); $this->rootFolder->method('getUserFolder')->with($this->currentUser->getUID())->will($this->returnSelf()); @@ -1444,9 +1458,9 @@ public function testCreateShareLinkDefaultPerms() { $this->shareManager->method('shareApiLinkAllowPublicUpload')->willReturn(false); $this->shareManager->expects($this->once())->method('createShare')->with( - $this->callback(function (\OCP\Share\IShare $share) use ($path) { + $this->callback(function (IShare $share) use ($path) { return $share->getNode() === $path && - $share->getShareType() === Share::SHARE_TYPE_LINK && + $share->getShareType() === \OC\Share\Constants::SHARE_TYPE_LINK && $share->getPermissions() === (\OCP\Constants::PERMISSION_READ) && $share->getSharedBy() === 'currentUser' && $share->getPassword() === null && @@ -1461,23 +1475,23 @@ public function testCreateShareLinkDefaultPerms() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testCreateShareLinkPassword() { + public function testCreateShareLinkPassword(): void { $ocs = $this->mockFormatShare(); $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['path', null, 'valid-path'], - ['shareType', '-1', Share::SHARE_TYPE_LINK], + ['shareType', '-1', \OC\Share\Constants::SHARE_TYPE_LINK], ['publicUpload', null, 'false'], ['expireDate', '', ''], ['password', '', 'password'], - ])); + ]); - $path = $this->createMock('\OCP\Files\Folder'); - $storage = $this->createMock('OCP\Files\Storage'); + $path = $this->createMock(Folder::class); + $storage = $this->createMock(\OCP\Files\Storage::class); $storage->method('instanceOfStorage') - ->with('OCA\Files_Sharing\External\Storage') + ->with(\OCA\Files_Sharing\External\Storage::class) ->willReturn(false); $path->method('getStorage')->willReturn($storage); $this->rootFolder->method('getUserFolder')->with($this->currentUser->getUID())->will($this->returnSelf()); @@ -1488,9 +1502,9 @@ public function testCreateShareLinkPassword() { $this->shareManager->method('shareApiLinkAllowPublicUpload')->willReturn(true); $this->shareManager->expects($this->once())->method('createShare')->with( - $this->callback(function (\OCP\Share\IShare $share) use ($path) { + $this->callback(function (IShare $share) use ($path) { return $share->getNode() === $path && - $share->getShareType() === Share::SHARE_TYPE_LINK && + $share->getShareType() === \OC\Share\Constants::SHARE_TYPE_LINK && $share->getPermissions() === \OCP\Constants::PERMISSION_READ && $share->getSharedBy() === 'currentUser' && $share->getPassword() === 'password' && @@ -1509,30 +1523,31 @@ public function testCreateShareLinkPassword() { * @dataProvider InvalidShareWithProvider * @param mixed $shareWith */ - public function testCreateShareRemoteNoValidShareWith($shareWith) { + public function testCreateShareRemoteNoValidShareWith($shareWith): void { $share = $this->newShare(); $this->shareManager->method('newShare')->willReturn($share); $this->shareManager->method('outgoingServer2ServerSharesAllowed')->willReturn(true); $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['path', null, 'valid-path'], ['permissions', null, \OCP\Constants::PERMISSION_ALL], - ['shareType', '-1', Share::SHARE_TYPE_REMOTE], - ['shareWith', $this->any(), $shareWith] - ])); + ['shareType', '-1', \OC\Share\Constants::SHARE_TYPE_REMOTE], + ['shareWith', $this->any(), $shareWith], + ['expireDate', '', ''], + ]); - $userFolder = $this->createMock('\OCP\Files\Folder'); + $userFolder = $this->createMock(Folder::class); $this->rootFolder->expects($this->once()) ->method('getUserFolder') ->with('currentUser') ->willReturn($userFolder); - $path = $this->createMock('\OCP\Files\File'); - $storage = $this->createMock('OCP\Files\Storage'); + $path = $this->createMock(File::class); + $storage = $this->createMock(\OCP\Files\Storage::class); $storage->method('instanceOfStorage') - ->with('OCA\Files_Sharing\External\Storage') + ->with(\OCA\Files_Sharing\External\Storage::class) ->willReturn(false); $path->method('getStorage')->willReturn($storage); $userFolder->expects($this->once()) @@ -1551,23 +1566,23 @@ public function testCreateShareRemoteNoValidShareWith($shareWith) { $this->assertEquals($expected->getData(), $result->getData()); } - public function testCreateShareValidExpireDate() { + public function testCreateShareValidExpireDate(): void { $ocs = $this->mockFormatShare(); $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['path', null, 'valid-path'], - ['shareType', '-1', Share::SHARE_TYPE_LINK], + ['shareType', '-1', \OC\Share\Constants::SHARE_TYPE_LINK], ['publicUpload', null, 'false'], ['expireDate', '', '2000-01-01'], ['password', '', ''], - ])); + ]); - $path = $this->createMock('\OCP\Files\Folder'); - $storage = $this->createMock('OCP\Files\Storage'); + $path = $this->createMock(Folder::class); + $storage = $this->createMock(\OCP\Files\Storage::class); $storage->method('instanceOfStorage') - ->with('OCA\Files_Sharing\External\Storage') + ->with(\OCA\Files_Sharing\External\Storage::class) ->willReturn(false); $path->method('getStorage')->willReturn($storage); $this->rootFolder->method('getUserFolder')->with($this->currentUser->getUID())->will($this->returnSelf()); @@ -1578,12 +1593,12 @@ public function testCreateShareValidExpireDate() { $this->shareManager->method('shareApiLinkAllowPublicUpload')->willReturn(true); $this->shareManager->expects($this->once())->method('createShare')->with( - $this->callback(function (\OCP\Share\IShare $share) use ($path) { + $this->callback(function (IShare $share) use ($path) { $date = new \DateTime('2000-01-01'); $date->setTime(0, 0, 0); return $share->getNode() === $path && - $share->getShareType() === Share::SHARE_TYPE_LINK && + $share->getShareType() === \OC\Share\Constants::SHARE_TYPE_LINK && $share->getPermissions() === \OCP\Constants::PERMISSION_READ && $share->getSharedBy() === 'currentUser' && $share->getPassword() === null && @@ -1598,23 +1613,23 @@ public function testCreateShareValidExpireDate() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testCreateShareInvalidExpireDate() { + public function testCreateShareInvalidExpireDate(): void { $ocs = $this->mockFormatShare(); $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['path', null, 'valid-path'], - ['shareType', '-1', Share::SHARE_TYPE_LINK], + ['shareType', '-1', \OC\Share\Constants::SHARE_TYPE_LINK], ['publicUpload', null, 'false'], ['expireDate', '', 'a1b2d3'], ['password', '', ''], - ])); + ]); - $path = $this->createMock('\OCP\Files\Folder'); - $storage = $this->createMock('OCP\Files\Storage'); + $path = $this->createMock(Folder::class); + $storage = $this->createMock(\OCP\Files\Storage::class); $storage->method('instanceOfStorage') - ->with('OCA\Files_Sharing\External\Storage') + ->with(\OCA\Files_Sharing\External\Storage::class) ->willReturn(false); $path->method('getStorage')->willReturn($storage); $this->rootFolder->method('getUserFolder')->with($this->currentUser->getUID())->will($this->returnSelf()); @@ -1635,7 +1650,7 @@ public function testCreateShareInvalidExpireDate() { * Test for https://github.com/owncloud/core/issues/22587 * TODO: Remove once proper solution is in place */ - public function testCreateReshareOfFederatedMountNoDeletePermissions() { + public function testCreateReshareOfFederatedMountNoDeletePermissions(): void { $share = \OC::$server->getShareManager()->newShare(); $this->shareManager->method('newShare')->willReturn($share); @@ -1643,23 +1658,24 @@ public function testCreateReshareOfFederatedMountNoDeletePermissions() { $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['path', null, 'valid-path'], ['permissions', null, \OCP\Constants::PERMISSION_ALL], - ['shareType', $this->any(), Share::SHARE_TYPE_USER], + ['shareType', $this->any(), \OC\Share\Constants::SHARE_TYPE_USER], ['shareWith', null, 'validUser'], - ])); + ['expireDate', '', ''], + ]); - $userFolder = $this->createMock('\OCP\Files\Folder'); + $userFolder = $this->createMock(Folder::class); $this->rootFolder->expects($this->once()) ->method('getUserFolder') ->with('currentUser') ->willReturn($userFolder); - $path = $this->createMock('\OCP\Files\Folder'); - $storage = $this->createMock('OCP\Files\Storage'); + $path = $this->createMock(Folder::class); + $storage = $this->createMock(\OCP\Files\Storage::class); $storage->method('instanceOfStorage') - ->with('OCA\Files_Sharing\External\Storage') + ->with(\OCA\Files_Sharing\External\Storage::class) ->willReturn(true); $path->method('getStorage')->willReturn($storage); $path->method('getPermissions')->willReturn(\OCP\Constants::PERMISSION_READ); @@ -1673,7 +1689,7 @@ public function testCreateReshareOfFederatedMountNoDeletePermissions() { $this->shareManager ->expects($this->once()) ->method('createShare') - ->with($this->callback(function (\OCP\Share\IShare $share) { + ->with($this->callback(function (IShare $share) { return $share->getPermissions() === \OCP\Constants::PERMISSION_READ; })) ->will($this->returnArgument(0)); @@ -1681,8 +1697,8 @@ public function testCreateReshareOfFederatedMountNoDeletePermissions() { $ocs->createShare(); } - public function testUpdateShareCantChange() { - $node = $this->createMock('\OCP\Files\Folder'); + public function testUpdateShareCantChange(): void { + $node = $this->createMock(Folder::class); $share = $this->newShare(); $share->setNode($node); @@ -1699,12 +1715,12 @@ public function testUpdateShareCantChange() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testUpdateNoParametersLink() { - $node = $this->createMock('\OCP\Files\Folder'); + public function testUpdateNoParametersLink(): void { + $node = $this->createMock(Folder::class); $share = $this->newShare(); $share->setPermissions(\OCP\Constants::PERMISSION_ALL) ->setSharedBy($this->currentUser->getUID()) - ->setShareType(Share::SHARE_TYPE_LINK) + ->setShareType(\OC\Share\Constants::SHARE_TYPE_LINK) ->setNode($node); $node->expects($this->once()) @@ -1720,20 +1736,20 @@ public function testUpdateNoParametersLink() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testUpdateNoParametersOther() { + public function testUpdateNoParametersOther(): void { $ocs = $this->mockFormatShare(); - $node = $this->createMock('\OCP\Files\Folder'); + $node = $this->createMock(Folder::class); $share = $this->newShare(); $share->setPermissions(\OCP\Constants::PERMISSION_ALL) ->setSharedBy($this->currentUser->getUID()) - ->setShareType(Share::SHARE_TYPE_GROUP) + ->setShareType(\OC\Share\Constants::SHARE_TYPE_GROUP) ->setNode($node); $this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share); $this->shareManager->expects($this->once())->method('updateShare')->with( - $this->callback(function (\OCP\Share\IShare $share) { + $this->callback(function (IShare $share) { return $share->getPermissions() === \OCP\Constants::PERMISSION_ALL && $share->getPassword() === null && $share->getExpirationDate() === null; @@ -1748,7 +1764,7 @@ public function testUpdateNoParametersOther() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testUpdateLinkShareClear() { + public function testUpdateLinkShareClear(): void { $userFolder = $this->createMock(Folder::class); $userFolder->method('getById')->willReturn([]); $this->rootFolder @@ -1757,12 +1773,12 @@ public function testUpdateLinkShareClear() { $ocs = $this->mockFormatShare(); - $node = $this->createMock('\OCP\Files\Folder'); + $node = $this->createMock(Folder::class); $share = $this->newShare(); $share->setPermissions(\OCP\Constants::PERMISSION_ALL) ->setSharedBy($this->currentUser->getUID()) ->setShareOwner($this->currentUser->getUID()) - ->setShareType(Share::SHARE_TYPE_LINK) + ->setShareType(\OC\Share\Constants::SHARE_TYPE_LINK) ->setPassword('password') ->setExpirationDate(new \DateTime()) ->setPermissions(\OCP\Constants::PERMISSION_ALL) @@ -1777,16 +1793,16 @@ public function testUpdateLinkShareClear() { $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['publicUpload', null, 'false'], ['expireDate', null, ''], ['password', null, ''], - ])); + ]); $this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share); $this->shareManager->expects($this->once())->method('updateShare')->with( - $this->callback(function (\OCP\Share\IShare $share) { + $this->callback(function (IShare $share) { return $share->getPermissions() === \OCP\Constants::PERMISSION_READ && $share->getPassword() === null && $share->getExpirationDate() === null; @@ -1800,7 +1816,7 @@ public function testUpdateLinkShareClear() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testUpdateLinkShareSet() { + public function testUpdateLinkShareSet(): void { $userFolder = $this->createMock(Folder::class); $userFolder->method('getById')->willReturn([]); $this->rootFolder @@ -1809,28 +1825,28 @@ public function testUpdateLinkShareSet() { $ocs = $this->mockFormatShare(); - $folder = $this->createMock('\OCP\Files\Folder'); + $folder = $this->createMock(Folder::class); $share = \OC::$server->getShareManager()->newShare(); $share->setPermissions(\OCP\Constants::PERMISSION_ALL) ->setSharedBy($this->currentUser->getUID()) ->setShareOwner($this->currentUser->getUID()) - ->setShareType(Share::SHARE_TYPE_LINK) + ->setShareType(\OC\Share\Constants::SHARE_TYPE_LINK) ->setNode($folder); $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['publicUpload', null, 'true'], ['expireDate', null, '2000-01-01'], ['password', null, 'password'], - ])); + ]); $this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share); $this->shareManager->method('shareApiLinkAllowPublicUpload')->willReturn(true); $this->shareManager->expects($this->once())->method('updateShare')->with( - $this->callback(function (\OCP\Share\IShare $share) { + $this->callback(function (IShare $share) { $date = new \DateTime('2000-01-01'); $date->setTime(0, 0, 0); @@ -1850,7 +1866,7 @@ public function testUpdateLinkShareSet() { /** * @dataProvider publicUploadParamsProvider */ - public function testUpdateLinkShareEnablePublicUpload($params) { + public function testUpdateLinkShareEnablePublicUpload($params): void { $userFolder = $this->createMock(Folder::class); $userFolder->method('getById')->willReturn([]); $this->rootFolder @@ -1859,35 +1875,35 @@ public function testUpdateLinkShareEnablePublicUpload($params) { $ocs = $this->mockFormatShare(); - $folder = $this->createMock('\OCP\Files\Folder'); + $folder = $this->createMock(Folder::class); $share = \OC::$server->getShareManager()->newShare(); $share->setPermissions(\OCP\Constants::PERMISSION_ALL) ->setSharedBy($this->currentUser->getUID()) ->setShareOwner($this->currentUser->getUID()) - ->setShareType(Share::SHARE_TYPE_LINK) + ->setShareType(\OC\Share\Constants::SHARE_TYPE_LINK) ->setPassword('password') ->setNode($folder); $this->request ->method('getParam') - ->will($this->returnValueMap($params)); + ->willReturnMap($params); $this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share); $this->shareManager->method('shareApiLinkAllowPublicUpload')->willReturn(true); $this->shareManager->method('getSharedWith')->willReturn([]); $this->shareManager->expects($this->once())->method('updateShare')->with( - $this->callback(function (\OCP\Share\IShare $share) { + $this->callback(function (IShare $share) { if ($share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE)) { return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) && $share->getPassword() === 'password' && $share->getExpirationDate() === null; - } else { - return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE) && - $share->getPassword() === 'password' && - $share->getExpirationDate() === null; } + + return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE) && + $share->getPassword() === 'password' && + $share->getExpirationDate() === null; }) )->will($this->returnArgument(0)); @@ -1898,7 +1914,7 @@ public function testUpdateLinkShareEnablePublicUpload($params) { $this->assertEquals($expected->getData(), $result->getData()); } - public function testUpdateLinkShareInvalidDate() { + public function testUpdateLinkShareInvalidDate(): void { $userFolder = $this->createMock(Folder::class); $userFolder->method('getById')->willReturn([]); $this->rootFolder @@ -1907,21 +1923,21 @@ public function testUpdateLinkShareInvalidDate() { $ocs = $this->mockFormatShare(); - $folder = $this->createMock('\OCP\Files\Folder'); + $folder = $this->createMock(Folder::class); $share = \OC::$server->getShareManager()->newShare(); $share->setPermissions(\OCP\Constants::PERMISSION_ALL) ->setSharedBy($this->currentUser->getUID()) - ->setShareType(Share::SHARE_TYPE_LINK) + ->setShareType(\OC\Share\Constants::SHARE_TYPE_LINK) ->setNode($folder); $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['publicUpload', null, 'true'], ['expireDate', null, '2000-01-a'], ['password', null, 'password'], - ])); + ]); $this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share); $this->shareManager->method('shareApiLinkAllowPublicUpload')->willReturn(true); @@ -1933,7 +1949,7 @@ public function testUpdateLinkShareInvalidDate() { $this->assertEquals($expected->getData(), $result->getData()); } - public function publicUploadParamsProvider() { + public function publicUploadParamsProvider(): array { return [ [[ ['publicUpload', null, 'true'], @@ -1956,20 +1972,20 @@ public function publicUploadParamsProvider() { /** * @dataProvider publicUploadParamsProvider */ - public function testUpdateLinkSharePublicUploadNotAllowed($params) { + public function testUpdateLinkSharePublicUploadNotAllowed($params): void { $ocs = $this->mockFormatShare(); - $folder = $this->createMock('\OCP\Files\Folder'); + $folder = $this->createMock(Folder::class); $share = \OC::$server->getShareManager()->newShare(); $share->setPermissions(\OCP\Constants::PERMISSION_ALL) ->setSharedBy($this->currentUser->getUID()) - ->setShareType(Share::SHARE_TYPE_LINK) + ->setShareType(\OC\Share\Constants::SHARE_TYPE_LINK) ->setNode($folder); $this->request ->method('getParam') - ->will($this->returnValueMap($params)); + ->willReturnMap($params); $this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share); $this->shareManager->method('shareApiLinkAllowPublicUpload')->willReturn(false); @@ -1981,24 +1997,24 @@ public function testUpdateLinkSharePublicUploadNotAllowed($params) { $this->assertEquals($expected->getData(), $result->getData()); } - public function testUpdateLinkSharePublicUploadOnFile() { + public function testUpdateLinkSharePublicUploadOnFile(): void { $ocs = $this->mockFormatShare(); - $file = $this->createMock('\OCP\Files\File'); + $file = $this->createMock(File::class); $share = \OC::$server->getShareManager()->newShare(); $share->setPermissions(\OCP\Constants::PERMISSION_ALL) ->setSharedBy($this->currentUser->getUID()) - ->setShareType(Share::SHARE_TYPE_LINK) + ->setShareType(\OC\Share\Constants::SHARE_TYPE_LINK) ->setNode($file); $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['publicUpload', null, 'true'], ['expireDate', '', ''], ['password', '', 'password'], - ])); + ]); $this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share); $this->shareManager->method('shareApiLinkAllowPublicUpload')->willReturn(true); @@ -2010,17 +2026,17 @@ public function testUpdateLinkSharePublicUploadOnFile() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testUpdateLinkSharePasswordDoesNotChangeOther() { + public function testUpdateLinkSharePasswordDoesNotChangeOther(): void { $ocs = $this->mockFormatShare(); $date = new \DateTime('2000-01-01'); $date->setTime(0, 0, 0); - $node = $this->createMock('\OCP\Files\File'); + $node = $this->createMock(File::class); $share = $this->newShare(); $share->setPermissions(\OCP\Constants::PERMISSION_ALL) ->setSharedBy($this->currentUser->getUID()) - ->setShareType(Share::SHARE_TYPE_LINK) + ->setShareType(\OC\Share\Constants::SHARE_TYPE_LINK) ->setPassword('password') ->setExpirationDate($date) ->setPermissions(\OCP\Constants::PERMISSION_ALL) @@ -2035,14 +2051,14 @@ public function testUpdateLinkSharePasswordDoesNotChangeOther() { $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['password', null, 'newpassword'], - ])); + ]); $this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share); $this->shareManager->expects($this->once())->method('updateShare')->with( - $this->callback(function (\OCP\Share\IShare $share) use ($date) { + $this->callback(function (IShare $share) use ($date) { return $share->getPermissions() === \OCP\Constants::PERMISSION_ALL && $share->getPassword() === 'newpassword' && $share->getExpirationDate() === $date; @@ -2056,14 +2072,14 @@ public function testUpdateLinkSharePasswordDoesNotChangeOther() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testUpdateLinkShareExpireDateDoesNotChangeOther() { + public function testUpdateLinkShareExpireDateDoesNotChangeOther(): void { $ocs = $this->mockFormatShare(); - $node = $this->createMock('\OCP\Files\File'); + $node = $this->createMock(File::class); $share = $this->newShare(); $share->setPermissions(\OCP\Constants::PERMISSION_ALL) ->setSharedBy($this->currentUser->getUID()) - ->setShareType(Share::SHARE_TYPE_LINK) + ->setShareType(\OC\Share\Constants::SHARE_TYPE_LINK) ->setPassword('password') ->setExpirationDate(new \DateTime()) ->setPermissions(\OCP\Constants::PERMISSION_ALL) @@ -2071,9 +2087,9 @@ public function testUpdateLinkShareExpireDateDoesNotChangeOther() { $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['expireDate', null, '2010-12-23'], - ])); + ]); $node->expects($this->once()) ->method('lock') @@ -2085,7 +2101,7 @@ public function testUpdateLinkShareExpireDateDoesNotChangeOther() { $this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share); $this->shareManager->expects($this->once())->method('updateShare')->with( - $this->callback(function (\OCP\Share\IShare $share) { + $this->callback(function (IShare $share) { $date = new \DateTime('2010-12-23'); $date->setTime(0, 0, 0); @@ -2102,7 +2118,7 @@ public function testUpdateLinkShareExpireDateDoesNotChangeOther() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testUpdateLinkSharePublicUploadDoesNotChangeOther() { + public function testUpdateLinkSharePublicUploadDoesNotChangeOther(): void { $userFolder = $this->createMock(Folder::class); $userFolder->method('getById')->willReturn([]); $this->rootFolder @@ -2113,13 +2129,13 @@ public function testUpdateLinkSharePublicUploadDoesNotChangeOther() { $date = new \DateTime('2000-01-01'); - $folder = $this->createMock('\OCP\Files\Folder'); + $folder = $this->createMock(Folder::class); $share = \OC::$server->getShareManager()->newShare(); $share->setPermissions(\OCP\Constants::PERMISSION_ALL) ->setSharedBy($this->currentUser->getUID()) ->setShareOwner($this->currentUser->getUID()) - ->setShareType(Share::SHARE_TYPE_LINK) + ->setShareType(\OC\Share\Constants::SHARE_TYPE_LINK) ->setPassword('password') ->setExpirationDate($date) ->setPermissions(\OCP\Constants::PERMISSION_ALL) @@ -2127,15 +2143,15 @@ public function testUpdateLinkSharePublicUploadDoesNotChangeOther() { $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['publicUpload', null, 'true'], - ])); + ]); $this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share); $this->shareManager->method('shareApiLinkAllowPublicUpload')->willReturn(true); $this->shareManager->expects($this->once())->method('updateShare')->with( - $this->callback(function (\OCP\Share\IShare $share) use ($date) { + $this->callback(function (IShare $share) use ($date) { return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) && $share->getPassword() === 'password' && $share->getExpirationDate() === $date; @@ -2149,7 +2165,7 @@ public function testUpdateLinkSharePublicUploadDoesNotChangeOther() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testUpdateLinkSharePermissions() { + public function testUpdateLinkSharePermissions(): void { $userFolder = $this->createMock(Folder::class); $userFolder->method('getById')->willReturn([]); $this->rootFolder @@ -2160,12 +2176,12 @@ public function testUpdateLinkSharePermissions() { $date = new \DateTime('2000-01-01'); - $folder = $this->createMock('\OCP\Files\Folder'); + $folder = $this->createMock(Folder::class); $share = \OC::$server->getShareManager()->newShare(); $share->setPermissions(\OCP\Constants::PERMISSION_ALL) ->setSharedBy($this->currentUser->getUID()) - ->setShareType(Share::SHARE_TYPE_LINK) + ->setShareType(\OC\Share\Constants::SHARE_TYPE_LINK) ->setPassword('password') ->setExpirationDate($date) ->setPermissions(\OCP\Constants::PERMISSION_ALL) @@ -2173,15 +2189,15 @@ public function testUpdateLinkSharePermissions() { $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['permissions', null, '7'], - ])); + ]); $this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share); $this->shareManager->method('shareApiLinkAllowPublicUpload')->willReturn(true); $this->shareManager->expects($this->once())->method('updateShare')->with( - $this->callback(function (\OCP\Share\IShare $share) use ($date) { + $this->callback(function (IShare $share) use ($date) { return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE) && $share->getPassword() === 'password' && $share->getExpirationDate() === $date; @@ -2197,7 +2213,7 @@ public function testUpdateLinkSharePermissions() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testUpdateLinkShareCreateOnly() { + public function testUpdateLinkShareCreateOnly(): void { $userFolder = $this->createMock(Folder::class); $userFolder->method('getById')->willReturn([]); $this->rootFolder @@ -2206,26 +2222,26 @@ public function testUpdateLinkShareCreateOnly() { $ocs = $this->mockFormatShare(); - $folder = $this->createMock('\OCP\Files\Folder'); + $folder = $this->createMock(Folder::class); $share = \OC::$server->getShareManager()->newShare(); $share->setPermissions(\OCP\Constants::PERMISSION_ALL) ->setSharedBy($this->currentUser->getUID()) - ->setShareType(Share::SHARE_TYPE_LINK) + ->setShareType(\OC\Share\Constants::SHARE_TYPE_LINK) ->setPermissions(\OCP\Constants::PERMISSION_READ) ->setNode($folder); $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['permissions', null, '4'], - ])); + ]); $this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share); $this->shareManager->method('shareApiLinkAllowPublicUpload')->willReturn(true); $this->shareManager->expects($this->once())->method('updateShare')->with( - $this->callback(function (\OCP\Share\IShare $share) { + $this->callback(function (IShare $share) { return $share->getPermissions() === \OCP\Constants::PERMISSION_CREATE; }) )->will($this->returnArgument(0)); @@ -2239,17 +2255,17 @@ public function testUpdateLinkShareCreateOnly() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testUpdateLinkShareInvalidPermissions() { + public function testUpdateLinkShareInvalidPermissions(): void { $ocs = $this->mockFormatShare(); $date = new \DateTime('2000-01-01'); - $folder = $this->createMock('\OCP\Files\Folder'); + $folder = $this->createMock(Folder::class); $share = \OC::$server->getShareManager()->newShare(); $share->setPermissions(\OCP\Constants::PERMISSION_ALL) ->setSharedBy($this->currentUser->getUID()) - ->setShareType(Share::SHARE_TYPE_LINK) + ->setShareType(\OC\Share\Constants::SHARE_TYPE_LINK) ->setPassword('password') ->setExpirationDate($date) ->setPermissions(\OCP\Constants::PERMISSION_ALL) @@ -2257,9 +2273,9 @@ public function testUpdateLinkShareInvalidPermissions() { $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['permissions', null, '31'], - ])); + ]); $this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share); $this->shareManager->method('shareApiLinkAllowPublicUpload')->willReturn(true); @@ -2271,28 +2287,28 @@ public function testUpdateLinkShareInvalidPermissions() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testUpdateOtherPermissions() { + public function testUpdateOtherPermissions(): void { $ocs = $this->mockFormatShare(); - $file = $this->createMock('\OCP\Files\File'); + $file = $this->createMock(File::class); $share = \OC::$server->getShareManager()->newShare(); $share->setPermissions(\OCP\Constants::PERMISSION_ALL) ->setSharedBy($this->currentUser->getUID()) - ->setShareType(Share::SHARE_TYPE_USER) + ->setShareType(\OC\Share\Constants::SHARE_TYPE_USER) ->setNode($file); $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['permissions', null, '31'], - ])); + ]); $this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share); $this->shareManager->method('shareApiLinkAllowPublicUpload')->willReturn(true); $this->shareManager->expects($this->once())->method('updateShare')->with( - $this->callback(function (\OCP\Share\IShare $share) { + $this->callback(function (IShare $share) { return $share->getPermissions() === \OCP\Constants::PERMISSION_ALL; }) )->will($this->returnArgument(0)); @@ -2306,30 +2322,30 @@ public function testUpdateOtherPermissions() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testUpdateLinkShareNameAlone() { + public function testUpdateLinkShareNameAlone(): void { $ocs = $this->mockFormatShare(); $date = new \DateTime('2000-01-01'); - $folder = $this->createMock('\OCP\Files\Folder'); + $folder = $this->createMock(Folder::class); $share = \OC::$server->getShareManager()->newShare(); $share->setPermissions(\OCP\Constants::PERMISSION_READ) ->setSharedBy($this->currentUser->getUID()) - ->setShareType(Share::SHARE_TYPE_LINK) + ->setShareType(\OC\Share\Constants::SHARE_TYPE_LINK) ->setName('somename') ->setNode($folder); $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['name', null, 'another'], - ])); + ]); $this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share); $this->shareManager->expects($this->once())->method('updateShare')->with( - $this->callback(function (\OCP\Share\IShare $share) use ($date) { + $this->callback(function (IShare $share) use ($date) { return $share->getName() === 'another'; }) )->will($this->returnArgument(0)); @@ -2343,30 +2359,30 @@ public function testUpdateLinkShareNameAlone() { $this->assertEquals($expected->getData(), $result->getData()); } - public function testUpdateLinkShareKeepNameWhenNotSpecified() { + public function testUpdateLinkShareKeepNameWhenNotSpecified(): void { $ocs = $this->mockFormatShare(); $date = new \DateTime('2000-01-01'); - $folder = $this->createMock('\OCP\Files\Folder'); + $folder = $this->createMock(Folder::class); $share = \OC::$server->getShareManager()->newShare(); $share->setPermissions(\OCP\Constants::PERMISSION_READ) ->setSharedBy($this->currentUser->getUID()) - ->setShareType(Share::SHARE_TYPE_LINK) + ->setShareType(\OC\Share\Constants::SHARE_TYPE_LINK) ->setName('somename') ->setNode($folder); $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['password', null, 'test'], - ])); + ]); $this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share); $this->shareManager->expects($this->once())->method('updateShare')->with( - $this->callback(function (\OCP\Share\IShare $share) use ($date) { + $this->callback(function (IShare $share) use ($date) { return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ) && $share->getPassword() === 'test' && $share->getName() === 'somename'; @@ -2382,10 +2398,10 @@ public function testUpdateLinkShareKeepNameWhenNotSpecified() { $this->assertEquals($expected->getData(), $result->getData()); } - private function getMockFileFolder() { - $file = $this->createMock('\OCP\Files\File'); - $folder = $this->createMock('\OCP\Files\Folder'); - $parent = $this->createMock('\OCP\Files\Folder'); + private function getMockFileFolder(): array { + $file = $this->createMock(File::class); + $folder = $this->createMock(Folder::class); + $parent = $this->createMock(Folder::class); $file->method('getMimeType')->willReturn('myMimeType'); $folder->method('getMimeType')->willReturn('myFolderMimeType'); @@ -2400,9 +2416,9 @@ private function getMockFileFolder() { $file->method('getParent')->willReturn($parent); $folder->method('getParent')->willReturn($parent); - $cache = $this->createMock('OCP\Files\Cache\ICache'); + $cache = $this->createMock(ICache::class); $cache->method('getNumericStorageId')->willReturn(100); - $storage = $this->createMock('\OCP\Files\Storage'); + $storage = $this->createMock(\OCP\Files\Storage::class); $storage->method('getId')->willReturn('storageId'); $storage->method('getCache')->willReturn($cache); @@ -2412,20 +2428,20 @@ private function getMockFileFolder() { return [$file, $folder]; } - public function dataFormatShare() { - list($file, $folder) = $this->getMockFileFolder(); - $owner = $this->createMock('\OCP\IUser'); + public function dataFormatShare(): array { + [$file, $folder] = $this->getMockFileFolder(); + $owner = $this->createMock(IUser::class); $owner->method('getDisplayName')->willReturn('ownerDN'); - $initiator = $this->createMock('\OCP\IUser'); + $initiator = $this->createMock(IUser::class); $initiator->method('getDisplayName')->willReturn('initiatorDN'); - $recipient = $this->createMock('\OCP\IUser'); + $recipient = $this->createMock(IUser::class); $recipient->method('getDisplayName')->willReturn('recipientDN'); - list($shareAttributes, $shareAttributesReturnJson) = $this->mockShareAttributes(); + [$shareAttributes, $shareAttributesReturnJson] = $this->mockShareAttributes(); $result = []; $share = \OC::$server->getShareManager()->newShare(); - $share->setShareType(Share::SHARE_TYPE_USER) + $share->setShareType(\OC\Share\Constants::SHARE_TYPE_USER) ->setSharedWith('recipient') ->setSharedBy('initiator') ->setShareOwner('owner') @@ -2440,7 +2456,7 @@ public function dataFormatShare() { $result[] = [ [ 'id' => 42, - 'share_type' => Share::SHARE_TYPE_USER, + 'share_type' => \OC\Share\Constants::SHARE_TYPE_USER, 'uid_owner' => 'initiator', 'displayname_owner' => 'initiator', 'share_with_user_type' => Constants::USER_TYPE_USER, @@ -2471,7 +2487,7 @@ public function dataFormatShare() { $result[] = [ [ 'id' => 42, - 'share_type' => Share::SHARE_TYPE_USER, + 'share_type' => \OC\Share\Constants::SHARE_TYPE_USER, 'uid_owner' => 'initiator', 'displayname_owner' => 'initiatorDN', 'share_with_user_type' => Constants::USER_TYPE_USER, @@ -2506,7 +2522,7 @@ public function dataFormatShare() { ]; $share = \OC::$server->getShareManager()->newShare(); - $share->setShareType(Share::SHARE_TYPE_USER) + $share->setShareType(\OC\Share\Constants::SHARE_TYPE_USER) ->setSharedWith('recipient') ->setSharedBy('initiator') ->setShareOwner('owner') @@ -2520,7 +2536,7 @@ public function dataFormatShare() { $result[] = [ [ 'id' => 42, - 'share_type' => Share::SHARE_TYPE_USER, + 'share_type' => \OC\Share\Constants::SHARE_TYPE_USER, 'share_with_user_type' => Constants::USER_TYPE_USER, 'uid_owner' => 'initiator', 'displayname_owner' => 'initiator', @@ -2549,7 +2565,7 @@ public function dataFormatShare() { // with existing group $share = \OC::$server->getShareManager()->newShare(); - $share->setShareType(Share::SHARE_TYPE_GROUP) + $share->setShareType(\OC\Share\Constants::SHARE_TYPE_GROUP) ->setSharedWith('recipientGroup') ->setSharedBy('initiator') ->setShareOwner('owner') @@ -2562,7 +2578,7 @@ public function dataFormatShare() { $result[] = [ [ 'id' => 42, - 'share_type' => Share::SHARE_TYPE_GROUP, + 'share_type' => \OC\Share\Constants::SHARE_TYPE_GROUP, 'uid_owner' => 'initiator', 'displayname_owner' => 'initiator', 'permissions' => 1, @@ -2590,7 +2606,7 @@ public function dataFormatShare() { // with unknown group / no group backend $share = \OC::$server->getShareManager()->newShare(); - $share->setShareType(Share::SHARE_TYPE_GROUP) + $share->setShareType(\OC\Share\Constants::SHARE_TYPE_GROUP) ->setSharedWith('recipientGroup2') ->setSharedBy('initiator') ->setShareOwner('owner') @@ -2602,7 +2618,7 @@ public function dataFormatShare() { $result[] = [ [ 'id' => 42, - 'share_type' => Share::SHARE_TYPE_GROUP, + 'share_type' => \OC\Share\Constants::SHARE_TYPE_GROUP, 'uid_owner' => 'initiator', 'displayname_owner' => 'initiator', 'permissions' => 1, @@ -2629,7 +2645,7 @@ public function dataFormatShare() { ]; $share = \OC::$server->getShareManager()->newShare(); - $share->setShareType(Share::SHARE_TYPE_LINK) + $share->setShareType(\OC\Share\Constants::SHARE_TYPE_LINK) ->setSharedBy('initiator') ->setShareOwner('owner') ->setPermissions(\OCP\Constants::PERMISSION_READ) @@ -2645,7 +2661,7 @@ public function dataFormatShare() { $result[] = [ [ 'id' => 42, - 'share_type' => Share::SHARE_TYPE_LINK, + 'share_type' => \OC\Share\Constants::SHARE_TYPE_LINK, 'uid_owner' => 'initiator', 'displayname_owner' => 'initiator', 'permissions' => 1, @@ -2674,7 +2690,7 @@ public function dataFormatShare() { ]; $share = \OC::$server->getShareManager()->newShare(); - $share->setShareType(Share::SHARE_TYPE_REMOTE) + $share->setShareType(\OC\Share\Constants::SHARE_TYPE_REMOTE) ->setSharedBy('initiator') ->setSharedWith('user@server.com') ->setShareOwner('owner') @@ -2687,7 +2703,7 @@ public function dataFormatShare() { $result[] = [ [ 'id' => 42, - 'share_type' => Share::SHARE_TYPE_REMOTE, + 'share_type' => \OC\Share\Constants::SHARE_TYPE_REMOTE, 'uid_owner' => 'initiator', 'displayname_owner' => 'initiator', 'permissions' => 1, @@ -2714,7 +2730,7 @@ public function dataFormatShare() { ]; $share = \OC::$server->getShareManager()->newShare(); - $share->setShareType(Share::SHARE_TYPE_USER) + $share->setShareType(\OC\Share\Constants::SHARE_TYPE_USER) ->setSharedBy('initiator') ->setSharedWith('recipient') ->setShareOwner('owner') @@ -2734,18 +2750,18 @@ public function dataFormatShare() { * @dataProvider dataFormatShare * * @param array $expects - * @param \OCP\Share\IShare $share + * @param IShare $share * @param array $users * @param $exception */ - public function testFormatShare(array $expects, \OCP\Share\IShare $share, array $users, $exception) { - $this->userManager->method('get')->will($this->returnValueMap($users)); + public function testFormatShare(array $expects, IShare $share, array $users, $exception): void { + $this->userManager->method('get')->willReturnMap($users); - $recipientGroup = $this->createMock('\OCP\IGroup'); + $recipientGroup = $this->createMock(IGroup::class); $recipientGroup->method('getDisplayName')->willReturn('recipientGroupDisplayName'); - $this->groupManager->method('get')->will($this->returnValueMap([ - ['recipientGroup', $recipientGroup], - ])); + $this->groupManager->method('get')->willReturnMap([ + ['recipientGroup', $recipientGroup], + ]); $this->urlGenerator->method('linkToRouteAbsolute') ->with('files_sharing.sharecontroller.showShare', ['token' => 'myToken']) @@ -2777,12 +2793,11 @@ public function testFormatShare(array $expects, \OCP\Share\IShare $share, array /** * @return Share20OcsController */ - public function getOcsDisabledAPI() { - $shareManager = $this->getMockBuilder('OCP\Share\IManager') + public function getOcsDisabledAPI(): Share20OcsController { + $shareManager = $this->getMockBuilder(IManager::class) ->disableOriginalConstructor() ->getMock(); $shareManager - ->expects($this->any()) ->method('shareApiEnabled') ->willReturn(false); @@ -2805,7 +2820,7 @@ public function getOcsDisabledAPI() { ); } - public function testGetShareApiDisabled() { + public function testGetShareApiDisabled(): void { $ocs = $this->getOcsDisabledAPI(); $expected = new Result(null, 404, 'Share API is disabled'); @@ -2814,7 +2829,7 @@ public function testGetShareApiDisabled() { $this->assertEquals($expected, $result); } - public function testDeleteShareApiDisabled() { + public function testDeleteShareApiDisabled(): void { $ocs = $this->getOcsDisabledAPI(); $expected = new Result(null, 404, 'Share API is disabled'); @@ -2823,7 +2838,7 @@ public function testDeleteShareApiDisabled() { $this->assertEquals($expected, $result); } - public function testCreateShareApiDisabled() { + public function testCreateShareApiDisabled(): void { $ocs = $this->getOcsDisabledAPI(); $expected = new Result(null, 404, 'Share API is disabled'); @@ -2832,7 +2847,7 @@ public function testCreateShareApiDisabled() { $this->assertEquals($expected, $result); } - public function testGetSharesApiDisabled() { + public function testGetSharesApiDisabled(): void { $ocs = $this->getOcsDisabledAPI(); $expected = new Result(); @@ -2841,7 +2856,7 @@ public function testGetSharesApiDisabled() { $this->assertEquals($expected, $result); } - public function testUpdateShareApiDisabled() { + public function testUpdateShareApiDisabled(): void { $ocs = $this->getOcsDisabledAPI(); $expected = new Result(null, 404, 'Share API is disabled'); @@ -2850,7 +2865,7 @@ public function testUpdateShareApiDisabled() { $this->assertEquals($expected, $result); } - public function additionalInfoDataProvider() { + public function additionalInfoDataProvider(): array { return [ ['', null, null, null], ['unsupported', null, null, null], @@ -2862,13 +2877,13 @@ public function additionalInfoDataProvider() { /** * @dataProvider additionalInfoDataProvider */ - public function testGetShareAdditionalInfo($configValue, $expectedInfo, $expectedOwnerInfo, $expectedInitiatorInfo) { + public function testGetShareAdditionalInfo($configValue, $expectedInfo, $expectedOwnerInfo, $expectedInitiatorInfo): void { $config = $this->createMock(IConfig::class); $config->method('getAppValue') - ->will($this->returnValueMap([ + ->willReturnMap([ ['core', 'shareapi_default_permissions', \OCP\Constants::PERMISSION_ALL, \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE], ['core', 'user_additional_info_field', '', $configValue], - ])); + ]); $initiator = $this->createMock(IUser::class); $initiator->method('getUID')->willReturn('initiator_id'); @@ -2882,11 +2897,11 @@ public function testGetShareAdditionalInfo($configValue, $expectedInfo, $expecte $recipient->method('getUID')->willReturn('recipient_id'); $recipient->method('getEMailAddress')->willReturn('email@example.com'); - $this->userManager->method('get')->will($this->returnValueMap([ + $this->userManager->method('get')->willReturnMap([ ['initiator', false, $initiator], ['recipient', false, $recipient], ['owner', false, $owner], - ])); + ]); $ocs = new Share20OcsController( 'files_sharing', @@ -2906,10 +2921,10 @@ public function testGetShareAdditionalInfo($configValue, $expectedInfo, $expecte $this->userTypeHelper ); - list($file, ) = $this->getMockFileFolder(); + [$file,] = $this->getMockFileFolder(); $share = \OC::$server->getShareManager()->newShare(); - $share->setShareType(Share::SHARE_TYPE_USER) + $share->setShareType(\OC\Share\Constants::SHARE_TYPE_USER) ->setSharedWith('recipient') ->setSharedBy('initiator') ->setShareOwner('owner') @@ -2938,7 +2953,7 @@ public function testGetShareAdditionalInfo($configValue, $expectedInfo, $expecte $this->assertEquals($expectedInitiatorInfo, $result['additional_info_owner']); } - public function providesGetSharesAll() { + public function providesGetSharesAll(): array { return [ [ null, @@ -2968,25 +2983,25 @@ public function providesGetSharesAll() { null, false, false, - [\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_REMOTE], + [\OC\Share\Constants::SHARE_TYPE_USER, \OC\Share\Constants::SHARE_TYPE_REMOTE], ], [ '/requested/path', true, false, - [\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_REMOTE], + [\OC\Share\Constants::SHARE_TYPE_USER, \OC\Share\Constants::SHARE_TYPE_REMOTE], ], [ '/requested/path', false, false, - [\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_REMOTE], + [\OC\Share\Constants::SHARE_TYPE_USER, \OC\Share\Constants::SHARE_TYPE_REMOTE], ], [ '/requested/path', true, true, - [\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_REMOTE], + [\OC\Share\Constants::SHARE_TYPE_USER, \OC\Share\Constants::SHARE_TYPE_REMOTE], ], ]; } @@ -2994,7 +3009,7 @@ public function providesGetSharesAll() { /** * @dataProvider providesGetSharesAll */ - public function testGetSharesAll($requestedPath, $requestedReshares, $fedAllowed, $shareTypes) { + public function testGetSharesAll($requestedPath, $requestedReshares, $fedAllowed, $shareTypes): void { $userShare = $this->newShare(); $groupShare = $this->newShare(); $linkShare = $this->newShare(); @@ -3022,62 +3037,62 @@ public function testGetSharesAll($requestedPath, $requestedReshares, $fedAllowed } $this->shareManager->method('getSharesBy') - ->will($this->returnValueMap([ - ['currentUser', \OCP\Share::SHARE_TYPE_USER, $node, $requestedReshares, -1, 0, [$userShare]], - ['currentUser', \OCP\Share::SHARE_TYPE_GROUP, $node, $requestedReshares, -1, 0, [$groupShare]], - ['currentUser', \OCP\Share::SHARE_TYPE_LINK, $node, $requestedReshares, -1, 0, [$linkShare]], - ['currentUser', \OCP\Share::SHARE_TYPE_REMOTE, $node, $requestedReshares, -1, 0, [$federatedShare]], - ])); + ->willReturnMap([ + ['currentUser', \OC\Share\Constants::SHARE_TYPE_USER, $node, $requestedReshares, -1, 0, [$userShare]], + ['currentUser', \OC\Share\Constants::SHARE_TYPE_GROUP, $node, $requestedReshares, -1, 0, [$groupShare]], + ['currentUser', \OC\Share\Constants::SHARE_TYPE_LINK, $node, $requestedReshares, -1, 0, [$linkShare]], + ['currentUser', \OC\Share\Constants::SHARE_TYPE_REMOTE, $node, $requestedReshares, -1, 0, [$federatedShare]], + ]); $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['path', null, $requestedPath], ['reshares', null, $requestedReshares ? 'true' : 'false'], ['share_types', '', \implode(',', $shareTypes)], - ])); + ]); $this->shareManager->method('outgoingServer2ServerSharesAllowed')->willReturn($fedAllowed); $ocs = $this->mockFormatShare(); - $ocs->expects($this->any())->method('formatShare')->will($this->returnArgument(0)); + $ocs->expects(self::atLeast(1))->method('formatShare')->will($this->returnArgument(0)); $result = $ocs->getShares(); if (\count($shareTypes) === 0) { $shareTypes = [ - \OCP\Share::SHARE_TYPE_USER, - \OCP\Share::SHARE_TYPE_GROUP, - \OCP\Share::SHARE_TYPE_LINK, - \OCP\Share::SHARE_TYPE_REMOTE, + \OC\Share\Constants::SHARE_TYPE_USER, + \OC\Share\Constants::SHARE_TYPE_GROUP, + \OC\Share\Constants::SHARE_TYPE_LINK, + \OC\Share\Constants::SHARE_TYPE_REMOTE, ]; } foreach ($shareTypes as $shareType) { switch ($shareType) { - case \OCP\Share::SHARE_TYPE_USER: + case \OC\Share\Constants::SHARE_TYPE_USER: $this->assertContains($userShare, $result->getData(), 'result contains user share'); break; - case \OCP\Share::SHARE_TYPE_GROUP: + case \OC\Share\Constants::SHARE_TYPE_GROUP: $this->assertContains($groupShare, $result->getData(), 'result contains group share'); break; - case \OCP\Share::SHARE_TYPE_LINK: + case \OC\Share\Constants::SHARE_TYPE_LINK: $this->assertContains($linkShare, $result->getData(), 'result contains link share'); break; - case \OCP\Share::SHARE_TYPE_REMOTE: + case \OC\Share\Constants::SHARE_TYPE_REMOTE: if ($fedAllowed) { $this->assertContains($federatedShare, $result->getData(), 'result contains federated share'); } break; } } - if ($fedAllowed && \in_array(\OCP\Share::SHARE_TYPE_REMOTE, $shareTypes, true)) { + if ($fedAllowed && \in_array(\OC\Share\Constants::SHARE_TYPE_REMOTE, $shareTypes, true)) { $this->assertCount(\count($shareTypes), $result->getData()); } else { $this->assertCount(\count($shareTypes) - 1, $result->getData()); } } - public function providesGetSharesSharedWithMe() { + public function providesGetSharesSharedWithMe(): array { return [ [ null, @@ -3091,12 +3106,12 @@ public function providesGetSharesSharedWithMe() { ], [ '/requested/path', - \OCP\Share::STATE_PENDING, + \OC\Share\Constants::STATE_PENDING, [], ], [ '/requested/path', - \OCP\Share::STATE_ACCEPTED, + \OC\Share\Constants::STATE_ACCEPTED, [], ], [ @@ -3107,52 +3122,52 @@ public function providesGetSharesSharedWithMe() { [ null, 'all', - [\OCP\Share::SHARE_TYPE_USER], + [\OC\Share\Constants::SHARE_TYPE_USER], ], [ '/requested/path', 'all', - [\OCP\Share::SHARE_TYPE_USER], + [\OC\Share\Constants::SHARE_TYPE_USER], ], [ '/requested/path', - \OCP\Share::STATE_PENDING, - [\OCP\Share::SHARE_TYPE_USER], + \OC\Share\Constants::STATE_PENDING, + [\OC\Share\Constants::SHARE_TYPE_USER], ], [ '/requested/path', - \OCP\Share::STATE_ACCEPTED, - [\OCP\Share::SHARE_TYPE_USER], + \OC\Share\Constants::STATE_ACCEPTED, + [\OC\Share\Constants::SHARE_TYPE_USER], ], [ '/requested/path', '', - [\OCP\Share::SHARE_TYPE_USER], + [\OC\Share\Constants::SHARE_TYPE_USER], ], [ null, 'all', - [\OCP\Share::SHARE_TYPE_GROUP], + [\OC\Share\Constants::SHARE_TYPE_GROUP], ], [ '/requested/path', 'all', - [\OCP\Share::SHARE_TYPE_GROUP], + [\OC\Share\Constants::SHARE_TYPE_GROUP], ], [ '/requested/path', - \OCP\Share::STATE_PENDING, - [\OCP\Share::SHARE_TYPE_GROUP], + \OC\Share\Constants::STATE_PENDING, + [\OC\Share\Constants::SHARE_TYPE_GROUP], ], [ '/requested/path', - \OCP\Share::STATE_ACCEPTED, - [\OCP\Share::SHARE_TYPE_GROUP], + \OC\Share\Constants::STATE_ACCEPTED, + [\OC\Share\Constants::SHARE_TYPE_GROUP], ], [ '/requested/path', '', - [\OCP\Share::SHARE_TYPE_GROUP], + [\OC\Share\Constants::SHARE_TYPE_GROUP], ], ]; } @@ -3160,27 +3175,27 @@ public function providesGetSharesSharedWithMe() { /** * @dataProvider providesGetSharesSharedWithMe */ - public function testGetSharesSharedWithMe($requestedPath, $stateFilter, $shareTypes) { + public function testGetSharesSharedWithMe($requestedPath, $stateFilter, $shareTypes): void { $testStateFilter = $stateFilter; if ($testStateFilter === '' || $testStateFilter === 'all') { - $testStateFilter = \OCP\Share::STATE_ACCEPTED; + $testStateFilter = \OC\Share\Constants::STATE_ACCEPTED; } $userShare = $this->newShare(); $userShare->setShareOwner('shareOwner'); $userShare->setSharedWith('currentUser'); - $userShare->setShareType(\OCP\Share::SHARE_TYPE_USER); + $userShare->setShareType(\OC\Share\Constants::SHARE_TYPE_USER); $userShare->setState($testStateFilter); $userShare->setPermissions(\OCP\Constants::PERMISSION_ALL); $userShareNoAccess = $this->newShare(); $userShareNoAccess->setShareOwner('shareOwner'); $userShareNoAccess->setSharedWith('currentUser'); - $userShareNoAccess->setShareType(\OCP\Share::SHARE_TYPE_USER); + $userShareNoAccess->setShareType(\OC\Share\Constants::SHARE_TYPE_USER); $userShareNoAccess->setState($testStateFilter); $userShareNoAccess->setPermissions(0); $userShareDifferentState = $this->newShare(); $userShareDifferentState->setShareOwner('shareOwner'); $userShareDifferentState->setSharedWith('currentUser'); - $userShareDifferentState->setShareType(\OCP\Share::SHARE_TYPE_USER); + $userShareDifferentState->setShareType(\OC\Share\Constants::SHARE_TYPE_USER); $userShareDifferentState->setState($testStateFilter + 1); $userShareDifferentState->setPermissions(\OCP\Constants::PERMISSION_ALL); $groupShare = $this->newShare(); @@ -3188,15 +3203,15 @@ public function testGetSharesSharedWithMe($requestedPath, $stateFilter, $shareTy $groupShare->setSharedWith('group1'); $groupShare->setState($testStateFilter); $groupShare->setPermissions(\OCP\Constants::PERMISSION_ALL); - $groupShare->setShareType(\OCP\Share::SHARE_TYPE_GROUP); + $groupShare->setShareType(\OC\Share\Constants::SHARE_TYPE_GROUP); $groupShareNonOwner = $this->newShare(); $groupShareNonOwner->setShareOwner('currentUser'); $groupShareNonOwner->setSharedWith('group1'); $groupShareNonOwner->setState($testStateFilter); - $groupShareNonOwner->setShareType(\OCP\Share::SHARE_TYPE_GROUP); + $groupShareNonOwner->setShareType(\OC\Share\Constants::SHARE_TYPE_GROUP); $groupShareNonOwner->setPermissions(\OCP\Constants::PERMISSION_ALL); - $group = $this->createMock('OCP\IGroup'); + $group = $this->createMock(IGroup::class); $group->method('inGroup')->with($this->currentUser)->willReturn(true); $this->groupManager->method('get')->with('group1')->willReturn($group); @@ -3223,43 +3238,43 @@ public function testGetSharesSharedWithMe($requestedPath, $stateFilter, $shareTy } $this->shareManager->method('getSharedWith') - ->will($this->returnValueMap([ - ['currentUser', \OCP\Share::SHARE_TYPE_USER, $node, -1, 0, [$userShare, $userShareDifferentState, $userShareNoAccess]], - ['currentUser', \OCP\Share::SHARE_TYPE_GROUP, $node, -1, 0, [$groupShare, $groupShareNonOwner]], - ])); + ->willReturnMap([ + ['currentUser', \OC\Share\Constants::SHARE_TYPE_USER, $node, -1, 0, [$userShare, $userShareDifferentState, $userShareNoAccess]], + ['currentUser', \OC\Share\Constants::SHARE_TYPE_GROUP, $node, -1, 0, [$groupShare, $groupShareNonOwner]], + ]); $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['path', null, $requestedPath], - ['state', \OCP\Share::STATE_ACCEPTED, $stateFilter], + ['state', \OC\Share\Constants::STATE_ACCEPTED, $stateFilter], ['shared_with_me', null, 'true'], ['share_types', '', \implode(',', $shareTypes)], - ])); + ]); $ocs = $this->mockFormatShare(); - $ocs->expects($this->any())->method('formatShare')->will($this->returnArgument(0)); + $ocs->expects($this->atLeast(1))->method('formatShare')->will($this->returnArgument(0)); $result = $ocs->getShares(); if (empty($shareTypes)) { $shareTypes = [ - \OCP\Share::SHARE_TYPE_USER, - \OCP\Share::SHARE_TYPE_GROUP, + \OC\Share\Constants::SHARE_TYPE_USER, + \OC\Share\Constants::SHARE_TYPE_GROUP, ]; } - if (\in_array(\OCP\Share::SHARE_TYPE_USER, $shareTypes, true)) { + if (\in_array(\OC\Share\Constants::SHARE_TYPE_USER, $shareTypes, true)) { $this->assertContains($userShare, $result->getData(), 'result contains user share'); $this->assertNotContains($userShareNoAccess, $result->getData(), 'result does not contain inaccessible share'); if ($stateFilter === 'all') { - if (\in_array(\OCP\Share::SHARE_TYPE_GROUP, $shareTypes, true)) { + if (\in_array(\OC\Share\Constants::SHARE_TYPE_GROUP, $shareTypes, true)) { $this->assertCount(3, $result->getData()); } else { $this->assertCount(2, $result->getData()); } $this->assertContains($userShareDifferentState, $result->getData(), 'result contains shares from all states'); } else { - if (\in_array(\OCP\Share::SHARE_TYPE_GROUP, $shareTypes, true)) { + if (\in_array(\OC\Share\Constants::SHARE_TYPE_GROUP, $shareTypes, true)) { $this->assertCount(2, $result->getData()); } else { $this->assertCount(1, $result->getData()); @@ -3268,23 +3283,23 @@ public function testGetSharesSharedWithMe($requestedPath, $stateFilter, $shareTy } } - if (\in_array(\OCP\Share::SHARE_TYPE_GROUP, $shareTypes, true)) { + if (\in_array(\OC\Share\Constants::SHARE_TYPE_GROUP, $shareTypes, true)) { $this->assertContains($groupShare, $result->getData(), 'result contains group share'); $this->assertNotContains($groupShareNonOwner, $result->getData(), 'result does not contain share from same owner'); } } - public function testGetSharesSharedWithMeAndBlockGroup() { + public function testGetSharesSharedWithMeAndBlockGroup(): void { $requestedPath = "/requested/path"; $stateFilter = "all"; $testStateFilter = $stateFilter; if ($testStateFilter === '' || $testStateFilter === 'all') { - $testStateFilter = \OCP\Share::STATE_ACCEPTED; + $testStateFilter = \OC\Share\Constants::STATE_ACCEPTED; } $userShare = $this->newShare(); $userShare->setShareOwner('shareOwner'); $userShare->setSharedWith('currentUser'); - $userShare->setShareType(\OCP\Share::SHARE_TYPE_USER); + $userShare->setShareType(\OC\Share\Constants::SHARE_TYPE_USER); $userShare->setState($testStateFilter); $userShare->setPermissions(\OCP\Constants::PERMISSION_ALL); @@ -3296,10 +3311,10 @@ public function testGetSharesSharedWithMeAndBlockGroup() { ->willReturn(true); $this->groupManager->method('get') - ->will($this->returnValueMap([ + ->willReturnMap([ ['group', $group], ['excluded_group', $groupObj] - ])); + ]); $node = $this->createMock(Node::class); $node->expects($this->once()) @@ -3318,63 +3333,63 @@ public function testGetSharesSharedWithMeAndBlockGroup() { ->willReturn($userFolder); $this->shareManager->method('getSharedWith') - ->will($this->returnValueMap([ - ['currentUser', \OCP\Share::SHARE_TYPE_USER, $node, -1, 0, [$userShare]], - ['currentUser', \OCP\Share::SHARE_TYPE_GROUP, $node, -1, 0, []], - ])); + ->willReturnMap([ + ['currentUser', \OC\Share\Constants::SHARE_TYPE_USER, $node, -1, 0, [$userShare]], + ['currentUser', \OC\Share\Constants::SHARE_TYPE_GROUP, $node, -1, 0, []], + ]); $this->shareManager->method('sharingDisabledForUser') ->with('currentUser') ->willReturn(true); $this->request ->method('getParam') - ->will($this->returnValueMap([ + ->willReturnMap([ ['path', null, $requestedPath], - ['state', \OCP\Share::STATE_ACCEPTED, $stateFilter], + ['state', \OC\Share\Constants::STATE_ACCEPTED, $stateFilter], ['shared_with_me', null, 'true'], ['share_types', '', ''], - ])); + ]); $ocs = $this->mockFormatShare(); - $ocs->expects($this->any())->method('formatShare')->will($this->returnArgument(0)); + $ocs->expects($this->atLeastOnce())->method('formatShare')->will($this->returnArgument(0)); $result = $ocs->getShares(); $this->assertEquals($userShare->getPermissions(), $result->getData()[0]->getPermissions()); } - public function providesAcceptRejectShare() { + public function providesAcceptRejectShare(): array { return [ - ['acceptShare', '/target', true, \OCP\Share::STATE_ACCEPTED], - ['acceptShare', '/sfoo/target', true, \OCP\Share::STATE_ACCEPTED], - ['acceptShare', '/target', false, \OCP\Share::STATE_ACCEPTED], - ['acceptShare', '/sfoo/target', false, \OCP\Share::STATE_ACCEPTED], - ['declineShare', '/target', true, \OCP\Share::STATE_REJECTED], - ['declineShare', '/sfoo/target', true, \OCP\Share::STATE_REJECTED], + ['acceptShare', '/target', true, \OC\Share\Constants::STATE_ACCEPTED], + ['acceptShare', '/sfoo/target', true, \OC\Share\Constants::STATE_ACCEPTED], + ['acceptShare', '/target', false, \OC\Share\Constants::STATE_ACCEPTED], + ['acceptShare', '/sfoo/target', false, \OC\Share\Constants::STATE_ACCEPTED], + ['declineShare', '/target', true, \OC\Share\Constants::STATE_REJECTED], + ['declineShare', '/sfoo/target', true, \OC\Share\Constants::STATE_REJECTED], ]; } /** * @dataProvider providesAcceptRejectShare */ - public function testAcceptRejectShare($method, $target, $targetExists, $expectedState) { + public function testAcceptRejectShare($method, $target, $targetExists, $expectedState): void { $userShare = $this->makeReceivedUserShareForOperation($target); - $this->shareManager->expects($this->exactly(1)) + $this->shareManager->expects($this->once()) ->method('getShareById') ->with('ocinternal:123', 'currentUser') ->willReturn($userShare); $this->shareManager->expects($this->exactly(2)) ->method('getSharedWith') - ->will($this->returnValueMap([ - ['currentUser', Share::SHARE_TYPE_USER, $userShare->getNode(), -1, 0, [$userShare]], - ['currentUser', Share::SHARE_TYPE_GROUP, $userShare->getNode(), -1, 0, []] - ])); + ->willReturnMap([ + ['currentUser', \OC\Share\Constants::SHARE_TYPE_USER, $userShare->getNode(), -1, 0, [$userShare]], + ['currentUser', \OC\Share\Constants::SHARE_TYPE_GROUP, $userShare->getNode(), -1, 0, []] + ]); $this->shareManager->expects($this->once()) ->method('updateShareForRecipient') ->with($userShare, 'currentUser'); - $userFolder = $this->createMock('OCP\Files\Folder'); + $userFolder = $this->createMock(Folder::class); if ($method === 'acceptShare') { // deduplicate if ($targetExists) { @@ -3419,7 +3434,7 @@ public function testAcceptRejectShare($method, $target, $targetExists, $expected ->willReturn($userFolder); $ocs = $this->mockFormatShare(); - $ocs->expects($this->any())->method('formatShare')->will($this->returnArgument(0)); + $ocs->expects($this->atLeastOnce())->method('formatShare')->will($this->returnArgument(0)); $result = $ocs->$method(123); $this->assertEquals(100, $result->getStatusCode()); @@ -3427,31 +3442,31 @@ public function testAcceptRejectShare($method, $target, $targetExists, $expected if ($method === 'acceptShare' && $targetExists) { $this->assertEquals("$target (2)", $userShare->getTarget()); } else { - $this->assertEquals("$target", $userShare->getTarget()); + $this->assertEquals((string)$target, $userShare->getTarget()); } $this->assertSame($expectedState, $userShare->getState()); } - public function providesAcceptRejectShareSameState() { + public function providesAcceptRejectShareSameState(): array { return [ - ['acceptShare', '/target', true, \OCP\Share::STATE_ACCEPTED], - ['declineShare', '/sfoo/target', true, \OCP\Share::STATE_REJECTED], + ['acceptShare', '/target', true, \OC\Share\Constants::STATE_ACCEPTED], + ['declineShare', '/sfoo/target', true, \OC\Share\Constants::STATE_REJECTED], ]; } /** * @dataProvider providesAcceptRejectShareSameState */ - public function testAcceptRejectShareSameState($method, $target) { + public function testAcceptRejectShareSameState($method, $target): void { $userShare = $this->makeReceivedUserShareForOperation($target); - $this->shareManager->expects($this->exactly(1)) + $this->shareManager->expects($this->once()) ->method('getShareById') ->with('ocinternal:123', 'currentUser') ->willReturn($userShare); if ($method === 'acceptShare') { - $userShare->setState(\OCP\Share::STATE_ACCEPTED); + $userShare->setState(\OC\Share\Constants::STATE_ACCEPTED); $this->eventDispatcher->expects($this->exactly(2)) ->method('dispatch') ->withConsecutive( @@ -3459,7 +3474,7 @@ public function testAcceptRejectShareSameState($method, $target) { [$this->equalTo(new GenericEvent(null, ['share' => $userShare])), $this->equalTo('share.afteraccept')] ); } else { - $userShare->setState(\OCP\Share::STATE_REJECTED); + $userShare->setState(\OC\Share\Constants::STATE_REJECTED); $this->eventDispatcher->expects($this->exactly(2)) ->method('dispatch') ->withConsecutive( @@ -3478,32 +3493,32 @@ public function testAcceptRejectShareSameState($method, $target) { /** * @dataProvider providesAcceptRejectShare */ - public function testAcceptRejectShareMultiple($method, $target, $targetExists, $expectedState) { + public function testAcceptRejectShareMultiple($method, $target, $targetExists, $expectedState): void { $userShare = $this->makeReceivedUserShareForOperation($target); $groupShare = $this->newShare(); $groupShare->setId(234); - $groupShare->setShareType(Share::SHARE_TYPE_GROUP); + $groupShare->setShareType(\OC\Share\Constants::SHARE_TYPE_GROUP); $groupShare->setSharedWith('group1'); $groupShare->setNode($userShare->getNode()); $groupShare->setTarget($target); - $this->shareManager->expects($this->exactly(1)) + $this->shareManager->expects($this->once()) ->method('getShareById') ->with('ocinternal:123', 'currentUser') ->willReturn($userShare); $this->shareManager->expects($this->exactly(2)) ->method('getSharedWith') - ->will($this->returnValueMap([ - ['currentUser', Share::SHARE_TYPE_USER, $userShare->getNode(), -1, 0, [$userShare]], - ['currentUser', Share::SHARE_TYPE_GROUP, $userShare->getNode(), -1, 0, [$groupShare]] - ])); + ->willReturnMap([ + ['currentUser', \OC\Share\Constants::SHARE_TYPE_USER, $userShare->getNode(), -1, 0, [$userShare]], + ['currentUser', \OC\Share\Constants::SHARE_TYPE_GROUP, $userShare->getNode(), -1, 0, [$groupShare]] + ]); $this->shareManager->expects($this->exactly(2)) ->method('updateShareForRecipient') ->withConsecutive([$userShare], [$groupShare]); - $userFolder = $this->createMock('OCP\Files\Folder'); + $userFolder = $this->createMock(Folder::class); if ($method === 'acceptShare') { // deduplicate if ($targetExists) { @@ -3548,7 +3563,7 @@ public function testAcceptRejectShareMultiple($method, $target, $targetExists, $ ->willReturn($userFolder); $ocs = $this->mockFormatShare(); - $ocs->expects($this->any())->method('formatShare')->will($this->returnArgument(0)); + $ocs->expects($this->atLeastOnce())->method('formatShare')->will($this->returnArgument(0)); $result = $ocs->$method(123); $this->assertEquals(100, $result->getStatusCode()); @@ -3556,7 +3571,7 @@ public function testAcceptRejectShareMultiple($method, $target, $targetExists, $ if ($method === 'acceptShare' && $targetExists) { $this->assertEquals("$target (2)", $userShare->getTarget()); } else { - $this->assertEquals("$target", $userShare->getTarget()); + $this->assertEquals((string)$target, $userShare->getTarget()); } $this->assertSame($expectedState, $userShare->getState()); } @@ -3564,7 +3579,7 @@ public function testAcceptRejectShareMultiple($method, $target, $targetExists, $ /** * @dataProvider providesAcceptRejectShare */ - public function testAcceptRejectShareApiDisabled($method, $target, $targetExists, $expectedState) { + public function testAcceptRejectShareApiDisabled($method, $target, $targetExists): void { $ocs = $this->getOcsDisabledAPI(); $expected = new Result(null, 404, 'Share API is disabled'); @@ -3573,19 +3588,19 @@ public function testAcceptRejectShareApiDisabled($method, $target, $targetExists $this->assertEquals($expected, $result); } - private function makeReceivedUserShareForOperation($target) { + private function makeReceivedUserShareForOperation($target): IShare { $node = $this->createMock(Node::class); - $node->expects($this->any()) + $node->expects($this->atLeastOnce()) ->method('lock'); - $node->expects($this->any()) + $node->expects($this->atLeastOnce()) ->method('unlock'); $userShare = $this->newShare(); $userShare->setId(123); $userShare->setShareOwner('shareOwner'); $userShare->setSharedWith('currentUser'); - $userShare->setShareType(\OCP\Share::SHARE_TYPE_USER); - $userShare->setState(\OCP\Share::STATE_PENDING); + $userShare->setShareType(\OC\Share\Constants::SHARE_TYPE_USER); + $userShare->setState(\OC\Share\Constants::STATE_PENDING); $userShare->setPermissions(\OCP\Constants::PERMISSION_ALL); $userShare->setTarget($target); $userShare->setNode($node); @@ -3596,7 +3611,7 @@ private function makeReceivedUserShareForOperation($target) { /** * @dataProvider providesAcceptRejectShare */ - public function testAcceptRejectShareInvalidId($method, $target, $targetExists, $expectedState) { + public function testAcceptRejectShareInvalidId($method, $target, $targetExists, $expectedState): void { $this->shareManager->expects($this->any()) ->method('getShareById') ->with('ocinternal:123') @@ -3614,7 +3629,7 @@ public function testAcceptRejectShareInvalidId($method, $target, $targetExists, /** * @dataProvider providesAcceptRejectShare */ - public function testAcceptRejectShareAccessDenied($method, $target, $targetExists, $expectedState) { + public function testAcceptRejectShareAccessDenied($method, $target, $targetExists): void { $userShare = $this->makeReceivedUserShareForOperation($target); $userShare->setPermissions(0); @@ -3635,10 +3650,10 @@ public function testAcceptRejectShareAccessDenied($method, $target, $targetExist /** * @dataProvider providesAcceptRejectShare */ - public function testAcceptRejectShareAccessNotRecipient($method, $target, $targetExists, $expectedState) { + public function testAcceptRejectShareAccessNotRecipient($method, $target, $targetExists): void { $userShare = $this->makeReceivedUserShareForOperation($target); - $this->shareManager->expects($this->any()) + $this->shareManager ->method('getShareById') ->with('ocinternal:123', 'currentUser') ->willReturn($userShare); @@ -3664,7 +3679,7 @@ public function testAcceptRejectShareAccessNotRecipient($method, $target, $targe /** * @dataProvider providesAcceptRejectShare */ - public function testAcceptRejectShareOperationError($method, $target, $targetExists, $expectedState) { + public function testAcceptRejectShareOperationError($method, $target, $targetExists): void { $userShare = $this->makeReceivedUserShareForOperation($target); $this->shareManager->expects($this->once()) @@ -3679,8 +3694,8 @@ public function testAcceptRejectShareOperationError($method, $target, $targetExi $this->shareManager->expects($this->exactly(2)) ->method('getSharedWith') ->willReturnMap([ - ['currentUser', Share::SHARE_TYPE_USER, $userShare->getNode(), -1, 0, [$userShare]], - ['currentUser', Share::SHARE_TYPE_GROUP, $userShare->getNode(), -1, 0, []] + ['currentUser', \OC\Share\Constants::SHARE_TYPE_USER, $userShare->getNode(), -1, 0, [$userShare]], + ['currentUser', \OC\Share\Constants::SHARE_TYPE_GROUP, $userShare->getNode(), -1, 0, []] ]); $userFolder = $this->createMock(Folder::class); @@ -3711,7 +3726,7 @@ public function testAcceptRejectShareOperationError($method, $target, $targetExi /** * @dataProvider providesTestAttributes */ - public function testSettingAttributes($attributes, $expectedAttributeObject) { + public function testSettingAttributes($attributes, $expectedAttributeObject): void { $share = $this->newShare(); $this->shareManager->method('newShare')->willReturn($share); @@ -3731,20 +3746,21 @@ public function testSettingAttributes($attributes, $expectedAttributeObject) { ['path', null, 'valid-path'], ['permissions', null, 1], ['attributes', null, $attributes], - ['shareType', $this->any(), Share::SHARE_TYPE_USER], + ['shareType', $this->any(), \OC\Share\Constants::SHARE_TYPE_USER], ['shareWith', null, 'validUser'], + ['expireDate', '', ''] ]); - $userFolder = $this->createMock('\OCP\Files\Folder'); + $userFolder = $this->createMock(Folder::class); $this->rootFolder->expects($this->once()) ->method('getUserFolder') ->with('currentUser') ->willReturn($userFolder); - $path = $this->createMock('\OCP\Files\File'); - $storage = $this->createMock('OCP\Files\Storage'); + $path = $this->createMock(File::class); + $storage = $this->createMock(\OCP\Files\Storage::class); $storage->method('instanceOfStorage') - ->with('OCA\Files_Sharing\External\Storage') + ->with(\OCA\Files_Sharing\External\Storage::class) ->willReturn(false); $path->method('getStorage')->willReturn($storage); $userFolder->expects($this->once()) @@ -3769,7 +3785,7 @@ public function testSettingAttributes($attributes, $expectedAttributeObject) { $this->assertEquals($expectedAttributeObject, $result->getData()['attributes']->toArray()); } - public function providesTestAttributes() { + public function providesTestAttributes(): array { return [ [ [ @@ -3821,7 +3837,7 @@ public function providesTestAttributes() { /** * @dataProvider providesPermissionsViaAttributes */ - public function testPermissionsViaAttributes($expectedPermission, $attributes) { + public function testPermissionsViaAttributes($expectedPermission, $attributes): void { $share = $this->newShare(); $this->shareManager->method('newShare')->willReturn($share); @@ -3841,21 +3857,21 @@ public function testPermissionsViaAttributes($expectedPermission, $attributes) { ['path', null, 'valid-path'], ['permissions', null, null], ['attributes', null, $attributes], - ['shareType', $this->any(), Share::SHARE_TYPE_USER], + ['shareType', $this->any(), \OC\Share\Constants::SHARE_TYPE_USER], ['shareWith', null, 'validUser'], ['expireDate', '', ''] ]); - $userFolder = $this->createMock('\OCP\Files\Folder'); + $userFolder = $this->createMock(Folder::class); $this->rootFolder->expects($this->once()) ->method('getUserFolder') ->with('currentUser') ->willReturn($userFolder); - $path = $this->createMock('\OCP\Files\File'); - $storage = $this->createMock('OCP\Files\Storage'); + $path = $this->createMock(File::class); + $storage = $this->createMock(\OCP\Files\Storage::class); $storage->method('instanceOfStorage') - ->with('OCA\Files_Sharing\External\Storage') + ->with(\OCA\Files_Sharing\External\Storage::class) ->willReturn(false); $path->method('getStorage')->willReturn($storage); $userFolder->expects($this->once()) @@ -3879,7 +3895,7 @@ public function testPermissionsViaAttributes($expectedPermission, $attributes) { $this->assertEquals($expectedPermission, $result->getData()['permissions']); } - public function providesPermissionsViaAttributes() { + public function providesPermissionsViaAttributes(): array { return [ [ \OCP\Constants::PERMISSION_READ, [ From 1d525679deecab122aa4034af32ab136e3087790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Mon, 2 Oct 2023 12:16:17 +0200 Subject: [PATCH 21/42] fix: Dispatcher/DispatcherTest --- lib/private/AppFramework/Http/Dispatcher.php | 44 +++---- lib/public/AppFramework/Controller.php | 3 +- .../lib/AppFramework/Http/DispatcherTest.php | 112 +++++++++--------- 3 files changed, 84 insertions(+), 75 deletions(-) diff --git a/lib/private/AppFramework/Http/Dispatcher.php b/lib/private/AppFramework/Http/Dispatcher.php index 98666bc2dc7b..ae92ea6942f1 100644 --- a/lib/private/AppFramework/Http/Dispatcher.php +++ b/lib/private/AppFramework/Http/Dispatcher.php @@ -26,23 +26,28 @@ namespace OC\AppFramework\Http; -use \OC\AppFramework\Middleware\MiddlewareDispatcher; -use \OC\AppFramework\Http; -use \OC\AppFramework\Utility\ControllerMethodReflector; +use Exception; +use OC\AppFramework\Middleware\MiddlewareDispatcher; +use OC\AppFramework\Http; +use OC\AppFramework\Utility\ControllerMethodReflector; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\Response; use OCP\AppFramework\Http\DataResponse; use OCP\IRequest; +use function array_merge; +use function call_user_func_array; +use function in_array; +use function settype; /** * Class to dispatch the request to the middleware dispatcher */ class Dispatcher { - private $middlewareDispatcher; - private $protocol; - private $reflector; - private $request; + private MiddlewareDispatcher $middlewareDispatcher; + private Http $protocol; + private ControllerMethodReflector $reflector; + private IRequest $request; /** * @param Http $protocol the http protocol with contains all status headers @@ -66,19 +71,20 @@ public function __construct( /** * Handles a request and calls the dispatcher on the controller + * * @param Controller $controller the controller which will be called * @param string $methodName the method name which will be called on * the controller * @return array $array[0] contains a string with the http main header, * $array[1] contains headers in the form: $key => value, $array[2] contains * the response output - * @throws \Exception + * @throws Exception */ - public function dispatch(Controller $controller, $methodName) { + public function dispatch(Controller $controller, string $methodName): array { $out = [null, [], null]; try { - // prefill reflector with everything thats needed for the + // prefill reflector with everything that's needed for the // middlewares $this->reflector->reflect($controller, $methodName); @@ -90,9 +96,9 @@ public function dispatch(Controller $controller, $methodName) { // if an exception appears, the middleware checks if it can handle the // exception and creates a response. If no response is created, it is - // assumed that theres no middleware who can handle it and the error is + // assumed that there's no middleware who can handle it and the error is // thrown again - } catch (\Exception $exception) { + } catch (Exception $exception) { $response = $this->middlewareDispatcher->afterException( $controller, $methodName, @@ -115,7 +121,7 @@ public function dispatch(Controller $controller, $methodName) { $response->getLastModified(), $response->getETag() ); - $out[1] = \array_merge($response->getHeaders()); + $out[1] = array_merge($response->getHeaders()); $out[2] = $response->getCookies(); $out[3] = $this->middlewareDispatcher->beforeOutput( $controller, @@ -130,11 +136,12 @@ public function dispatch(Controller $controller, $methodName) { /** * Uses the reflected parameters, types and request parameters to execute * the controller + * * @param Controller $controller the controller to be executed * @param string $methodName the method on the controller that should be executed * @return Response */ - private function executeController($controller, $methodName) { + private function executeController(Controller $controller, string $methodName): Response { $arguments = []; // valid types that will be casted @@ -152,15 +159,12 @@ private function executeController($controller, $methodName) { $value === 'false' && ( $this->request->method === 'GET' || - \strpos( - $this->request->getHeader('Content-Type') ?? '', - 'application/x-www-form-urlencoded' - ) !== false + str_contains($this->request->getHeader('Content-Type') ?? '', 'application/x-www-form-urlencoded') ) ) { $value = false; - } elseif ($value !== null && \in_array($type, $types)) { - \settype($value, $type); + } elseif ($value !== null && \in_array($type, $types, true)) { + settype($value, $type); } $arguments[] = $value; diff --git a/lib/public/AppFramework/Controller.php b/lib/public/AppFramework/Controller.php index d69f4f5c1d5b..def3a5803c4a 100644 --- a/lib/public/AppFramework/Controller.php +++ b/lib/public/AppFramework/Controller.php @@ -100,11 +100,12 @@ public function __construct( /** * Parses an HTTP accept header and returns the supported responder type + * * @param string $acceptHeader * @return string the responder type * @since 7.0.0 */ - public function getResponderByHTTPHeader($acceptHeader) { + public function getResponderByHTTPHeader(string $acceptHeader): string { $headers = \explode(',', $acceptHeader); // return the first matching responder diff --git a/tests/lib/AppFramework/Http/DispatcherTest.php b/tests/lib/AppFramework/Http/DispatcherTest.php index 81640667052f..8938dcdfb8bd 100644 --- a/tests/lib/AppFramework/Http/DispatcherTest.php +++ b/tests/lib/AppFramework/Http/DispatcherTest.php @@ -32,11 +32,15 @@ use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\Response; +use OCP\IRequest; +use OC\AppFramework\DependencyInjection\DIContainer; +use OCP\Security\ISecureRandom; +use OCP\IConfig; class TestController extends Controller { /** * @param string $appName - * @param \OCP\IRequest $request + * @param IRequest $request */ public function __construct($appName, $request) { parent::__construct($appName, $request); @@ -49,7 +53,7 @@ public function __construct($appName, $request) { * @param int $test2 * @return array */ - public function exec($int, $bool, $test=4, $test2=1) { + public function exec($int, $bool, $test=4, $test2=1): array { $this->registerResponder('text', function ($in) { return new JSONResponse(['text' => $in]); }); @@ -63,7 +67,7 @@ public function exec($int, $bool, $test=4, $test2=1) { * @param int $test2 * @return DataResponse */ - public function execDataResponse($int, $bool, $test=4, $test2=1) { + public function execDataResponse($int, $bool, $test=4, $test2=1): DataResponse { return new DataResponse([ 'text' => [$int, $bool, $test, $test2] ]); @@ -93,33 +97,33 @@ protected function setUp(): void { $this->controllerMethod = 'test'; $app = $this->getMockBuilder( - 'OC\AppFramework\DependencyInjection\DIContainer' + DIContainer::class ) ->disableOriginalConstructor() ->getMock(); $request = $this->getMockBuilder( - '\OC\AppFramework\Http\Request' + Request::class ) ->disableOriginalConstructor() ->getMock(); $this->http = $this->getMockBuilder( - '\OC\AppFramework\Http' + Http::class ) ->disableOriginalConstructor() ->getMock(); $this->middlewareDispatcher = $this->getMockBuilder( - '\OC\AppFramework\Middleware\MiddlewareDispatcher' + MiddlewareDispatcher::class ) ->disableOriginalConstructor() ->getMock(); - $this->controller = $this->getMockBuilder('\OCP\AppFramework\Controller') + $this->controller = $this->getMockBuilder(Controller::class) ->setMethods([$this->controllerMethod]) ->setConstructorArgs([$app, $request]) ->getMock(); $this->request = $this->getMockBuilder( - '\OC\AppFramework\Http\Request' + Request::class ) ->disableOriginalConstructor() ->getMock(); @@ -134,12 +138,12 @@ protected function setUp(): void { ); $this->response = $this->getMockBuilder( - '\OCP\AppFramework\Http\Response' + Response::class ) ->disableOriginalConstructor() ->getMock(); - $this->lastModified = new \DateTime(null, new \DateTimeZone('GMT')); + $this->lastModified = new \DateTime('now', new \DateTimeZone('GMT')); $this->etag = 'hi'; } @@ -153,7 +157,7 @@ private function setMiddlewareExpectations( $responseHeaders= [], $ex=false, $catchEx=true - ) { + ): void { if ($ex) { $exception = new \Exception(); $this->middlewareDispatcher->expects($this->once()) @@ -171,7 +175,7 @@ private function setMiddlewareExpectations( $this->equalTo($this->controllerMethod), $this->equalTo($exception) ) - ->will($this->returnValue($this->response)); + ->willReturn($this->response); } else { $this->middlewareDispatcher->expects($this->once()) ->method('afterException') @@ -180,7 +184,7 @@ private function setMiddlewareExpectations( $this->equalTo($this->controllerMethod), $this->equalTo($exception) ) - ->will($this->returnValue(null)); + ->willReturn(null); return; } } else { @@ -192,24 +196,24 @@ private function setMiddlewareExpectations( ); $this->controller->expects($this->once()) ->method($this->controllerMethod) - ->will($this->returnValue($this->response)); + ->willReturn($this->response); } $this->response->expects($this->once()) ->method('render') - ->will($this->returnValue($out)); + ->willReturn($out); $this->response->expects($this->once()) ->method('getStatus') - ->will($this->returnValue(Http::STATUS_OK)); + ->willReturn(Http::STATUS_OK); $this->response->expects($this->once()) ->method('getLastModified') - ->will($this->returnValue($this->lastModified)); + ->willReturn($this->lastModified); $this->response->expects($this->once()) ->method('getETag') - ->will($this->returnValue($this->etag)); + ->willReturn($this->etag); $this->response->expects($this->once()) ->method('getHeaders') - ->will($this->returnValue($responseHeaders)); + ->willReturn($responseHeaders); $this->http->expects($this->once()) ->method('getStatusHeader') ->with( @@ -217,7 +221,7 @@ private function setMiddlewareExpectations( $this->equalTo($this->lastModified), $this->equalTo($this->etag) ) - ->will($this->returnValue($httpHeaders)); + ->willReturn($httpHeaders); $this->middlewareDispatcher->expects($this->once()) ->method('afterController') @@ -226,7 +230,7 @@ private function setMiddlewareExpectations( $this->equalTo($this->controllerMethod), $this->equalTo($this->response) ) - ->will($this->returnValue($this->response)); + ->willReturn($this->response); $this->middlewareDispatcher->expects($this->once()) ->method('afterController') @@ -235,7 +239,7 @@ private function setMiddlewareExpectations( $this->equalTo($this->controllerMethod), $this->equalTo($this->response) ) - ->will($this->returnValue($this->response)); + ->willReturn($this->response); $this->middlewareDispatcher->expects($this->once()) ->method('beforeOutput') @@ -244,10 +248,10 @@ private function setMiddlewareExpectations( $this->equalTo($this->controllerMethod), $this->equalTo($out) ) - ->will($this->returnValue($out)); + ->willReturn($out); } - public function testDispatcherReturnsArrayWith2Entries() { + public function testDispatcherReturnsArrayWith2Entries(): void { $this->setMiddlewareExpectations(); $response = $this->dispatcher->dispatch( @@ -259,7 +263,7 @@ public function testDispatcherReturnsArrayWith2Entries() { $this->assertNull($response[2]); } - public function testHeadersAndOutputAreReturned() { + public function testHeadersAndOutputAreReturned(): void { $out = 'yo'; $httpHeaders = 'Http'; $responseHeaders = ['hell' => 'yeah']; @@ -275,7 +279,7 @@ public function testHeadersAndOutputAreReturned() { $this->assertEquals($out, $response[3]); } - public function testExceptionCallsAfterException() { + public function testExceptionCallsAfterException(): void { $out = 'yo'; $httpHeaders = 'Http'; $responseHeaders = ['hell' => 'yeah']; @@ -291,7 +295,7 @@ public function testExceptionCallsAfterException() { $this->assertEquals($out, $response[3]); } - public function testExceptionThrowsIfCanNotBeHandledByAfterException() { + public function testExceptionThrowsIfCanNotBeHandledByAfterException(): void { $out = 'yo'; $httpHeaders = 'Http'; $responseHeaders = ['hell' => 'yeah']; @@ -304,32 +308,32 @@ public function testExceptionThrowsIfCanNotBeHandledByAfterException() { ); } - private function dispatcherPassthrough() { + private function dispatcherPassthrough(): void { $this->middlewareDispatcher->expects($this->once()) ->method('beforeController'); $this->middlewareDispatcher->expects($this->once()) ->method('afterController') - ->will($this->returnCallback(function ($a, $b, $in) { + ->willReturnCallback(function ($a, $b, $in) { return $in; - })); + }); $this->middlewareDispatcher->expects($this->once()) ->method('beforeOutput') - ->will($this->returnCallback(function ($a, $b, $in) { + ->willReturnCallback(function ($a, $b, $in) { return $in; - })); + }); } - public function testControllerParametersInjected() { + public function testControllerParametersInjected(): void { $this->request = new Request( [ 'post' => [ - 'int' => '3', - 'bool' => 'false' + 'int' => '3', + 'bool' => 'false' ], - 'method' => 'POST' + 'method' => 'POST', ], - $this->createMock('\OCP\Security\ISecureRandom'), - $this->createMock('\OCP\IConfig') + $this->createMock(ISecureRandom::class), + $this->createMock(IConfig::class) ); $this->dispatcher = new Dispatcher( $this->http, @@ -346,7 +350,7 @@ public function testControllerParametersInjected() { $this->assertEquals('[3,true,4,1]', $response[3]); } - public function testControllerParametersInjectedDefaultOverwritten() { + public function testControllerParametersInjectedDefaultOverwritten(): void { $this->request = new Request( [ 'post' => [ @@ -356,8 +360,8 @@ public function testControllerParametersInjectedDefaultOverwritten() { ], 'method' => 'POST', ], - $this->createMock('\OCP\Security\ISecureRandom'), - $this->createMock('\OCP\IConfig') + $this->createMock(ISecureRandom::class), + $this->createMock(IConfig::class) ); $this->dispatcher = new Dispatcher( $this->http, @@ -374,7 +378,7 @@ public function testControllerParametersInjectedDefaultOverwritten() { $this->assertEquals('[3,true,4,7]', $response[3]); } - public function testResponseTransformedByUrlFormat() { + public function testResponseTransformedByUrlFormat(): void { $this->request = new Request( [ 'post' => [ @@ -386,8 +390,8 @@ public function testResponseTransformedByUrlFormat() { ], 'method' => 'GET' ], - $this->createMock('\OCP\Security\ISecureRandom'), - $this->createMock('\OCP\IConfig') + $this->createMock(ISecureRandom::class), + $this->createMock(IConfig::class) ); $this->dispatcher = new Dispatcher( $this->http, @@ -404,7 +408,7 @@ public function testResponseTransformedByUrlFormat() { $this->assertEquals('{"text":[3,false,4,1]}', $response[3]); } - public function testResponseTransformsDataResponse() { + public function testResponseTransformsDataResponse(): void { $this->request = new Request( [ 'post' => [ @@ -416,8 +420,8 @@ public function testResponseTransformsDataResponse() { ], 'method' => 'GET' ], - $this->createMock('\OCP\Security\ISecureRandom'), - $this->createMock('\OCP\IConfig') + $this->createMock(ISecureRandom::class), + $this->createMock(IConfig::class) ); $this->dispatcher = new Dispatcher( $this->http, @@ -434,7 +438,7 @@ public function testResponseTransformsDataResponse() { $this->assertEquals('{"text":[3,false,4,1]}', $response[3]); } - public function testResponseTransformedByAcceptHeader() { + public function testResponseTransformedByAcceptHeader(): void { $this->request = new Request( [ 'post' => [ @@ -447,8 +451,8 @@ public function testResponseTransformedByAcceptHeader() { ], 'method' => 'PUT' ], - $this->createMock('\OCP\Security\ISecureRandom'), - $this->createMock('\OCP\IConfig') + $this->createMock(ISecureRandom::class), + $this->createMock(IConfig::class) ); $this->dispatcher = new Dispatcher( $this->http, @@ -465,7 +469,7 @@ public function testResponseTransformedByAcceptHeader() { $this->assertEquals('{"text":[3,false,4,1]}', $response[3]); } - public function testResponsePrimarilyTransformedByParameterFormat() { + public function testResponsePrimarilyTransformedByParameterFormat(): void { $this->request = new Request( [ 'post' => [ @@ -480,8 +484,8 @@ public function testResponsePrimarilyTransformedByParameterFormat() { ], 'method' => 'POST' ], - $this->createMock('\OCP\Security\ISecureRandom'), - $this->createMock('\OCP\IConfig') + $this->createMock(ISecureRandom::class), + $this->createMock(IConfig::class) ); $this->dispatcher = new Dispatcher( $this->http, From 19a39b0b8e99cf20d57aa37759f8010004325975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Mon, 2 Oct 2023 12:20:49 +0200 Subject: [PATCH 22/42] fix: Http and tests --- lib/private/AppFramework/Http.php | 17 ++++--- lib/private/AppFramework/Http/Request.php | 14 +++--- .../lib/AppFramework/Http/DispatcherTest.php | 16 ++---- tests/lib/AppFramework/Http/HttpTest.php | 27 +++++----- tests/lib/AppFramework/Http/RequestTest.php | 4 +- tests/lib/AppFramework/Http/ResponseTest.php | 49 +++++++++---------- 6 files changed, 59 insertions(+), 68 deletions(-) diff --git a/lib/private/AppFramework/Http.php b/lib/private/AppFramework/Http.php index b8ef72c83a45..17a89cfd17e4 100644 --- a/lib/private/AppFramework/Http.php +++ b/lib/private/AppFramework/Http.php @@ -30,15 +30,15 @@ use OCP\AppFramework\Http as BaseHttp; class Http extends BaseHttp { - private $server; - private $protocolVersion; - protected $headers; + private array $server; + private string $protocolVersion; + protected array $headers; /** * @param array $server $_SERVER * @param string $protocolVersion the http version to use defaults to HTTP/1.1 */ - public function __construct($server, $protocolVersion='HTTP/1.1') { + public function __construct(array $server, string $protocolVersion='HTTP/1.1') { $this->server = $server; $this->protocolVersion = $protocolVersion; @@ -107,16 +107,17 @@ public function __construct($server, $protocolVersion='HTTP/1.1') { /** * Gets the correct header + * * @param Http::CONSTANT $status the constant from the Http class - * @param \DateTime $lastModified formatted last modified date - * @param string $ETag the etag + * @param \DateTime|null $lastModified formatted last modified date + * @param null $ETag the etag * @return string */ public function getStatusHeader( $status, \DateTime $lastModified=null, $ETag=null - ) { + ): string { if ($lastModified !== null) { $lastModified = $lastModified->format(\DateTime::RFC2822); } @@ -135,7 +136,7 @@ public function getStatusHeader( // we have one change currently for the http 1.0 header that differs // from 1.1: STATUS_TEMPORARY_REDIRECT should be STATUS_FOUND - // if this differs any more, we want to create childclasses for this + // if this differs anymore, we want to create child-classes for this if ($status === self::STATUS_TEMPORARY_REDIRECT && $this->protocolVersion === 'HTTP/1.0') { $status = self::STATUS_FOUND; diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php index 74f7ca082609..2f6bf6993b66 100644 --- a/lib/private/AppFramework/Http/Request.php +++ b/lib/private/AppFramework/Http/Request.php @@ -586,8 +586,8 @@ public function getServerProtocol() { * * @return string HTTP protocol. HTTP/2, HTTP/1.1 or HTTP/1.0. */ - public function getHttpProtocol() { - $claimedProtocol = \strtoupper($this->server['SERVER_PROTOCOL']); + public function getHttpProtocol(): string { + $claimedProtocol = \strtoupper($this->server['SERVER_PROTOCOL'] ?? 'HTTP/1.1'); $validProtocols = [ 'HTTP/1.0', @@ -658,18 +658,18 @@ public function getRawPathInfo() { // strip off the script name's dir and file name // FIXME: Sabre does not really belong here - list($path, $name) = \Sabre\Uri\split($scriptName); + [$path, $name] = \Sabre\Uri\split($scriptName); if (!empty($path)) { - if ($path === $pathInfo || \strpos($pathInfo, $path.'/') === 0) { + if ($path === $pathInfo || str_starts_with($pathInfo, $path . '/')) { $pathInfo = \substr($pathInfo, \strlen($path)); } else { throw new \Exception("The requested uri($requestUri) cannot be processed by the script '$scriptName')"); } } - if (\strpos($pathInfo, "/$name") === 0) { - $pathInfo = \substr($pathInfo, \strlen($name) + 1); + if (str_starts_with($pathInfo, "/$name")) { + $pathInfo = \substr($pathInfo, \strlen($name ?? '') + 1); } - if (\is_string($name) && \strpos($pathInfo, $name) === 0) { + if (\is_string($name) && str_starts_with($pathInfo, $name)) { $pathInfo = \substr($pathInfo, \strlen($name)); } diff --git a/tests/lib/AppFramework/Http/DispatcherTest.php b/tests/lib/AppFramework/Http/DispatcherTest.php index 8938dcdfb8bd..031bf3c5aea0 100644 --- a/tests/lib/AppFramework/Http/DispatcherTest.php +++ b/tests/lib/AppFramework/Http/DispatcherTest.php @@ -147,13 +147,8 @@ protected function setUp(): void { $this->etag = 'hi'; } - /** - * @param string $out - * @param string $httpHeaders - */ private function setMiddlewareExpectations( $out=null, - $httpHeaders=null, $responseHeaders= [], $ex=false, $catchEx=true @@ -221,7 +216,7 @@ private function setMiddlewareExpectations( $this->equalTo($this->lastModified), $this->equalTo($this->etag) ) - ->willReturn($httpHeaders); + ->willReturn('Http'); $this->middlewareDispatcher->expects($this->once()) ->method('afterController') @@ -258,7 +253,7 @@ public function testDispatcherReturnsArrayWith2Entries(): void { $this->controller, $this->controllerMethod ); - $this->assertNull($response[0]); + $this->assertEquals('Http', $response[0]); $this->assertEquals([], $response[1]); $this->assertNull($response[2]); } @@ -267,7 +262,7 @@ public function testHeadersAndOutputAreReturned(): void { $out = 'yo'; $httpHeaders = 'Http'; $responseHeaders = ['hell' => 'yeah']; - $this->setMiddlewareExpectations($out, $httpHeaders, $responseHeaders); + $this->setMiddlewareExpectations($out, $responseHeaders); $response = $this->dispatcher->dispatch( $this->controller, @@ -283,7 +278,7 @@ public function testExceptionCallsAfterException(): void { $out = 'yo'; $httpHeaders = 'Http'; $responseHeaders = ['hell' => 'yeah']; - $this->setMiddlewareExpectations($out, $httpHeaders, $responseHeaders, true); + $this->setMiddlewareExpectations($out, $responseHeaders, true); $response = $this->dispatcher->dispatch( $this->controller, @@ -297,9 +292,8 @@ public function testExceptionCallsAfterException(): void { public function testExceptionThrowsIfCanNotBeHandledByAfterException(): void { $out = 'yo'; - $httpHeaders = 'Http'; $responseHeaders = ['hell' => 'yeah']; - $this->setMiddlewareExpectations($out, $httpHeaders, $responseHeaders, true, false); + $this->setMiddlewareExpectations($out, $responseHeaders, true, false); $this->expectException('\Exception'); $response = $this->dispatcher->dispatch( diff --git a/tests/lib/AppFramework/Http/HttpTest.php b/tests/lib/AppFramework/Http/HttpTest.php index 881f9b0d1271..eab00b8c3759 100644 --- a/tests/lib/AppFramework/Http/HttpTest.php +++ b/tests/lib/AppFramework/Http/HttpTest.php @@ -23,15 +23,14 @@ namespace Test\AppFramework\Http; +use DateTime; +use DateTimeZone; use OC\AppFramework\Http; +use Test\TestCase; -class HttpTest extends \Test\TestCase { - private $server; - - /** - * @var Http - */ - private $http; +class HttpTest extends TestCase { + private array $server; + private Http $http; protected function setUp(): void { parent::setUp(); @@ -40,33 +39,33 @@ protected function setUp(): void { $this->http = new Http($this->server); } - public function testProtocol() { + public function testProtocol(): void { $header = $this->http->getStatusHeader(Http::STATUS_TEMPORARY_REDIRECT); $this->assertEquals('HTTP/1.1 307 Temporary Redirect', $header); } - public function testProtocol10() { + public function testProtocol10(): void { $this->http = new Http($this->server, 'HTTP/1.0'); $header = $this->http->getStatusHeader(Http::STATUS_OK); $this->assertEquals('HTTP/1.0 200 OK', $header); } - public function testEtagMatchReturnsNotModified() { + public function testEtagMatchReturnsNotModified(): void { $http = new Http(['HTTP_IF_NONE_MATCH' => 'hi']); $header = $http->getStatusHeader(Http::STATUS_OK, null, 'hi'); $this->assertEquals('HTTP/1.1 304 Not Modified', $header); } - public function testQuotedEtagMatchReturnsNotModified() { + public function testQuotedEtagMatchReturnsNotModified(): void { $http = new Http(['HTTP_IF_NONE_MATCH' => '"hi"']); $header = $http->getStatusHeader(Http::STATUS_OK, null, 'hi'); $this->assertEquals('HTTP/1.1 304 Not Modified', $header); } - public function testLastModifiedMatchReturnsNotModified() { - $dateTime = new \DateTime(null, new \DateTimeZone('GMT')); + public function testLastModifiedMatchReturnsNotModified(): void { + $dateTime = new DateTime('now', new DateTimeZone('GMT')); $dateTime->setTimestamp('12'); $http = new Http( @@ -78,7 +77,7 @@ public function testLastModifiedMatchReturnsNotModified() { $this->assertEquals('HTTP/1.1 304 Not Modified', $header); } - public function testTempRedirectBecomesFoundInHttp10() { + public function testTempRedirectBecomesFoundInHttp10(): void { $http = new Http([], 'HTTP/1.0'); $header = $http->getStatusHeader(Http::STATUS_TEMPORARY_REDIRECT); diff --git a/tests/lib/AppFramework/Http/RequestTest.php b/tests/lib/AppFramework/Http/RequestTest.php index 24aecbe42af6..d452deecd18c 100644 --- a/tests/lib/AppFramework/Http/RequestTest.php +++ b/tests/lib/AppFramework/Http/RequestTest.php @@ -1283,7 +1283,7 @@ public function testGetPathInfoWithoutSetEnvGeneric($requestUri, $scriptName, $e * @param string $expectedGetPathInfo * @param string $expectedGetRawPathInfo */ - public function testGetRawPathInfoWithoutSetEnvGeneric($requestUri, $scriptName, $expectedGetPathInfo, $expectedGetRawPathInfo) { + public function testGetRawPathInfoWithoutSetEnvGeneric($requestUri, $scriptName, $expectedGetPathInfo, $expectedGetRawPathInfo): void { if ($expectedGetRawPathInfo === '') { $expected = $expectedGetPathInfo; } else { @@ -1354,7 +1354,7 @@ public function testGetPathInfoWithoutSetEnv($requestUri, $scriptName, $expected /** * @return array */ - public function genericPathInfoProvider() { + public function genericPathInfoProvider(): array { return [ ['/core/index.php?XDEBUG_SESSION_START=14600', '/core/index.php', '', ''], ['/index.php/apps/files/', 'index.php', '/apps/files/', ''], diff --git a/tests/lib/AppFramework/Http/ResponseTest.php b/tests/lib/AppFramework/Http/ResponseTest.php index 2d17ba9ab659..5ca4d5f4b491 100644 --- a/tests/lib/AppFramework/Http/ResponseTest.php +++ b/tests/lib/AppFramework/Http/ResponseTest.php @@ -31,23 +31,20 @@ use Test\TestCase; class ResponseTest extends TestCase { - /** - * @var \OCP\AppFramework\Http\Response - */ - private $childResponse; + private Response $childResponse; protected function setUp(): void { parent::setUp(); $this->childResponse = new Response(); } - public function testAddHeader() { + public function testAddHeader(): void { $this->childResponse->addHeader(' hello ', 'world'); $headers = $this->childResponse->getHeaders(); $this->assertEquals('world', $headers['hello']); } - public function testSetHeaders() { + public function testSetHeaders(): void { $expected = [ 'Last-Modified' => 1, 'ETag' => 3, @@ -61,7 +58,7 @@ public function testSetHeaders() { $this->assertEquals($expected, $headers); } - public function testOverwriteCsp() { + public function testOverwriteCsp(): void { $expected = [ 'Content-Security-Policy' => "default-src 'none';script-src 'self' 'unsafe-inline' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self';font-src 'self';connect-src 'self';media-src 'self'", ]; @@ -74,7 +71,7 @@ public function testOverwriteCsp() { $this->assertEquals(\array_merge($expected, $headers), $headers); } - public function testGetCsp() { + public function testGetCsp(): void { $policy = new ContentSecurityPolicy(); $policy->allowInlineScript(true); @@ -82,22 +79,22 @@ public function testGetCsp() { $this->assertEquals($policy, $this->childResponse->getContentSecurityPolicy()); } - public function testGetCspEmpty() { + public function testGetCspEmpty(): void { $this->assertNull($this->childResponse->getContentSecurityPolicy()); } - public function testAddHeaderValueNullDeletesIt() { + public function testAddHeaderValueNullDeletesIt(): void { $this->childResponse->addHeader('hello', 'world'); $this->childResponse->addHeader('hello', null); $this->assertCount(2, $this->childResponse->getHeaders()); } - public function testCacheHeadersAreDisabledByDefault() { + public function testCacheHeadersAreDisabledByDefault(): void { $headers = $this->childResponse->getHeaders(); $this->assertEquals('no-cache, no-store, must-revalidate', $headers['Cache-Control']); } - public function testAddCookie() { + public function testAddCookie(): void { $this->childResponse->addCookie('foo', 'bar'); $this->childResponse->addCookie('bar', 'foo', new DateTime('1970-01-01')); @@ -114,7 +111,7 @@ public function testAddCookie() { $this->assertEquals($expectedResponse, $this->childResponse->getCookies()); } - public function testSetCookies() { + public function testSetCookies(): void { $expected = [ 'foo' => [ 'value' => 'bar', @@ -132,7 +129,7 @@ public function testSetCookies() { $this->assertEquals($expected, $cookies); } - public function testInvalidateCookie() { + public function testInvalidateCookie(): void { $this->childResponse->addCookie('foo', 'bar'); $this->childResponse->invalidateCookie('foo'); $expected = [ @@ -147,7 +144,7 @@ public function testInvalidateCookie() { $this->assertEquals($expected, $cookies); } - public function testInvalidateCookies() { + public function testInvalidateCookies(): void { $this->childResponse->addCookie('foo', 'bar'); $this->childResponse->addCookie('bar', 'foo'); $expected = [ @@ -179,11 +176,11 @@ public function testInvalidateCookies() { $this->assertEquals($expected, $cookies); } - public function testRenderReturnNullByDefault() { + public function testRenderReturnNullByDefault(): void { $this->assertNull($this->childResponse->render()); } - public function testGetStatus() { + public function testGetStatus(): void { $default = $this->childResponse->getStatus(); $this->childResponse->setStatus(Http::STATUS_NOT_FOUND); @@ -192,26 +189,26 @@ public function testGetStatus() { $this->assertEquals(Http::STATUS_NOT_FOUND, $this->childResponse->getStatus()); } - public function testGetEtag() { + public function testGetEtag(): void { $this->childResponse->setETag('hi'); $this->assertSame('hi', $this->childResponse->getETag()); } - public function testGetLastModified() { - $lastModified = new DateTime(null, new DateTimeZone('GMT')); + public function testGetLastModified(): void { + $lastModified = new DateTime('now', new DateTimeZone('GMT')); $lastModified->setTimestamp(1); $this->childResponse->setLastModified($lastModified); $this->assertEquals($lastModified, $this->childResponse->getLastModified()); } - public function testCacheSecondsZero() { + public function testCacheSecondsZero(): void { $this->childResponse->cacheFor(0); $headers = $this->childResponse->getHeaders(); $this->assertEquals('no-cache, no-store, must-revalidate', $headers['Cache-Control']); } - public function testCacheSeconds() { + public function testCacheSeconds(): void { $this->childResponse->cacheFor(33); $headers = $this->childResponse->getHeaders(); @@ -221,16 +218,16 @@ public function testCacheSeconds() { ); } - public function testEtagLastModifiedHeaders() { - $lastModified = new DateTime(null, new DateTimeZone('GMT')); + public function testEtagLastModifiedHeaders(): void { + $lastModified = new DateTime('now', new DateTimeZone('GMT')); $lastModified->setTimestamp(1); $this->childResponse->setLastModified($lastModified); $headers = $this->childResponse->getHeaders(); $this->assertEquals('Thu, 01 Jan 1970 00:00:01 +0000', $headers['Last-Modified']); } - public function testChainability() { - $lastModified = new DateTime(null, new DateTimeZone('GMT')); + public function testChainability(): void { + $lastModified = new DateTime('now', new DateTimeZone('GMT')); $lastModified->setTimestamp(1); $this->childResponse->setETag('hi') From f6e2e3fa8387279e7b231b37ba5bf08204c5db75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Mon, 2 Oct 2023 14:51:35 +0200 Subject: [PATCH 23/42] fix: more deprcations ... --- .../Connector/Sabre/FilesReportPluginTest.php | 83 +++++++++-------- .../lib/Controller/ShareesController.php | 2 +- apps/files_sharing/tests/ApiTest.php | 1 + .../tests/EtagPropagationTest.php | 2 +- lib/private/Avatar.php | 2 +- lib/private/BackgroundJob/JobList.php | 2 +- lib/private/Files/Cache/Wrapper/CacheJail.php | 7 +- lib/private/Files/External/ConfigAdapter.php | 4 +- lib/private/Setup.php | 44 +++++----- lib/private/Share20/DefaultShareProvider.php | 2 +- lib/private/Template/JSResourceLocator.php | 2 +- lib/private/User/User.php | 6 +- lib/private/legacy/helper.php | 5 +- tests/lib/AppFramework/AppTest.php | 40 +++++---- tests/lib/AvatarManagerTest.php | 3 +- tests/lib/AvatarTest.php | 35 +++++--- .../lib/Files/External/ConfigAdapterTest.php | 38 +++----- tests/lib/SetupTest.php | 67 +++++++------- .../lib/Share20/DefaultShareProviderTest.php | 88 +++++++++++-------- tests/lib/TempManagerTest.php | 6 +- tests/lib/Template/CSSResourceLocatorTest.php | 3 +- tests/lib/Template/JSResourceLocatorTest.php | 7 +- tests/lib/legacy/AppTest.php | 5 +- tests/lib/legacy/HelperTest.php | 16 +--- 24 files changed, 248 insertions(+), 222 deletions(-) diff --git a/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php index 9eacbcb2e158..ed3167881e16 100644 --- a/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php @@ -25,45 +25,57 @@ namespace OCA\DAV\Tests\unit\Connector\Sabre; use OC\Files\View; +use OCA\DAV\Connector\Sabre\Directory; +use OCA\DAV\Connector\Sabre\FilesPlugin; use OCA\DAV\Connector\Sabre\FilesReportPlugin as FilesReportPluginImplementation; use OCA\DAV\Files\Xml\FilterRequest; +use OCP\Files\File; use OCP\Files\Folder; use OCP\IConfig; use OCP\IGroupManager; use OCP\IRequest; use OCP\ITags; +use OCP\IUserSession; use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\ISystemTagObjectMapper; - -class FilesReportPluginTest extends \Test\TestCase { - /** @var \Sabre\DAV\Server|\PHPUnit\Framework\MockObject\MockObject */ +use OCP\SystemTag\TagNotFoundException; +use PHPUnit\Framework\MockObject\MockObject; +use Sabre\DAV\Server; +use Sabre\DAV\Tree; +use Sabre\HTTP\ResponseInterface; +use Test\TestCase; +use function array_keys; +use function array_values; + +class FilesReportPluginTest extends TestCase { + /** @var Server|MockObject */ private $server; - /** @var \Sabre\DAV\Tree|\PHPUnit\Framework\MockObject\MockObject */ + /** @var Tree|MockObject */ private $tree; - /** @var ISystemTagObjectMapper|\PHPUnit\Framework\MockObject\MockObject */ + /** @var ISystemTagObjectMapper|MockObject */ private $tagMapper; - /** @var ISystemTagManager|\PHPUnit\Framework\MockObject\MockObject */ + /** @var ISystemTagManager|MockObject */ private $tagManager; - /** @var ITags|\PHPUnit\Framework\MockObject\MockObject */ + /** @var ITags|MockObject */ private $privateTags; - /** @var \OCP\IUserSession */ + /** @var IUserSession */ private $userSession; /** @var FilesReportPluginImplementation */ private $plugin; - /** @var View|\PHPUnit\Framework\MockObject\MockObject **/ + /** @var View|MockObject **/ private $view; - /** @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject **/ + /** @var IGroupManager|MockObject **/ private $groupManager; - /** @var Folder|\PHPUnit\Framework\MockObject\MockObject **/ + /** @var Folder|MockObject **/ private $userFolder; public function setUp(): void { @@ -111,7 +123,7 @@ public function setUp(): void { // add FilesPlugin to test more properties $this->server->addPlugin( - new \OCA\DAV\Connector\Sabre\FilesPlugin( + new FilesPlugin( $this->tree, $this->createMock(IConfig::class), $this->createMock(IRequest::class) @@ -193,9 +205,9 @@ public function testOnReport() { ['111', '222', '333'], ); - $reportTargetNode = $this->createMock(\OCA\DAV\Connector\Sabre\Directory::class); + $reportTargetNode = $this->createMock(Directory::class); $reportTargetNode->method('getPath')->willReturn(''); - $response = $this->createMock(\Sabre\HTTP\ResponseInterface::class); + $response = $this->createMock(ResponseInterface::class); $response->expects($this->once()) ->method('setHeader') ->with('Content-Type', 'application/xml; charset=utf-8'); @@ -212,12 +224,12 @@ public function testOnReport() { ->with('/' . $path) ->willReturn($reportTargetNode); - $filesNode1 = $this->createMock(\OCP\Files\Folder::class); + $filesNode1 = $this->createMock(Folder::class); $filesNode1->method('getId')->willReturn(111); $filesNode1->method('getPath')->willReturn('/node1'); $filesNode1->method('isReadable')->willReturn(true); $filesNode1->method('getSize')->willReturn(2048); - $filesNode2 = $this->createMock(\OCP\Files\File::class); + $filesNode2 = $this->createMock(File::class); $filesNode2->method('getId')->willReturn(222); $filesNode2->method('getPath')->willReturn('/sub/node2'); $filesNode2->method('getSize')->willReturn(1024); @@ -274,7 +286,7 @@ function ($responsesArg) use (&$responses) { $this->assertCount(0, $props2[200]['{DAV:}resourcetype']->getValue()); } - public function testOnReportPaginationFiltered() { + public function testOnReportPaginationFiltered(): void { $path = 'test'; $parameters = new FilterRequest(); @@ -292,7 +304,7 @@ public function testOnReportPaginationFiltered() { $filesNodes = []; for ($i = 0; $i < 20; $i++) { - $filesNode = $this->createMock(\OCP\Files\File::class); + $filesNode = $this->createMock(File::class); $filesNode->method('getId')->willReturn(1000 + $i); $filesNode->method('getPath')->willReturn('/nodes/node' . $i); $filesNode->method('isReadable')->willReturn(true); @@ -302,14 +314,15 @@ public function testOnReportPaginationFiltered() { // return all above nodes as favorites $this->privateTags->expects($this->once()) ->method('getFavorites') - ->will($this->returnValue(\array_keys($filesNodes))); + ->willReturn(array_keys($filesNodes)); - $reportTargetNode = $this->createMock(\OCA\DAV\Connector\Sabre\Directory::class); + $reportTargetNode = $this->createMock(Directory::class); + $reportTargetNode->method('getPath')->willReturn('/'); $this->tree->expects($this->any()) ->method('getNodeForPath') ->with('/' . $path) - ->will($this->returnValue($reportTargetNode)); + ->willReturn($reportTargetNode); // getById must only be called for the required nodes $this->userFolder @@ -328,17 +341,17 @@ public function testOnReportPaginationFiltered() { $this->server->expects($this->any()) ->method('getRequestUri') - ->will($this->returnValue($path)); + ->willReturn($path); $this->plugin->initialize($this->server); $responses = null; $this->server->expects($this->once()) ->method('generateMultiStatus') - ->will( - $this->returnCallback(function ($responsesArg) use (&$responses) { + ->willReturnCallback( + function ($responsesArg) use (&$responses) { $responses = $responsesArg; - }) + } ); $this->assertFalse($this->plugin->onReport(FilesReportPluginImplementation::REPORT_NAME, $parameters, '/' . $path)); @@ -384,7 +397,7 @@ public function testFindNodesByFileIdsRoot() { [$filesNode2], ); - /** @var \OCA\DAV\Connector\Sabre\Directory|\PHPUnit\Framework\MockObject\MockObject $reportTargetNode */ + /** @var Directory|MockObject $reportTargetNode */ $result = $this->plugin->findNodesByFileIds($reportTargetNode, ['111', '222']); $this->assertCount(2, $result); @@ -437,7 +450,7 @@ public function testFindNodesByFileIdsSubDir() { [$filesNode2], ); - /** @var \OCA\DAV\Connector\Sabre\Directory|\PHPUnit\Framework\MockObject\MockObject $reportTargetNode */ + /** @var Directory|MockObject $reportTargetNode */ $result = $this->plugin->findNodesByFileIds($reportTargetNode, ['111', '222']); $this->assertCount(2, $result); @@ -490,7 +503,7 @@ public function testProcessFilterRulesAndCondition() { 'favorite' => null ]; - $this->assertEquals(['222'], \array_values(self::invokePrivate($this->plugin, 'processFilterRules', [$rules]))); + $this->assertEquals(['222'], array_values(self::invokePrivate($this->plugin, 'processFilterRules', [$rules]))); } public function testProcessFilterRulesAndConditionWithOneEmptyResult() { @@ -514,7 +527,7 @@ public function testProcessFilterRulesAndConditionWithOneEmptyResult() { 'favorite' => null ]; - $this->assertEquals([], \array_values(self::invokePrivate($this->plugin, 'processFilterRules', [$rules]))); + $this->assertEquals([], array_values(self::invokePrivate($this->plugin, 'processFilterRules', [$rules]))); } public function testProcessFilterRulesAndConditionWithFirstEmptyResult() { @@ -538,7 +551,7 @@ public function testProcessFilterRulesAndConditionWithFirstEmptyResult() { 'favorite' => null ]; - $this->assertEquals([], \array_values(self::invokePrivate($this->plugin, 'processFilterRules', [$rules]))); + $this->assertEquals([], array_values(self::invokePrivate($this->plugin, 'processFilterRules', [$rules]))); } public function testProcessFilterRulesAndConditionWithEmptyMidResult() { @@ -564,7 +577,7 @@ public function testProcessFilterRulesAndConditionWithEmptyMidResult() { 'favorite' => null ]; - $this->assertEquals([], \array_values(self::invokePrivate($this->plugin, 'processFilterRules', [$rules]))); + $this->assertEquals([], array_values(self::invokePrivate($this->plugin, 'processFilterRules', [$rules]))); } public function testProcessFilterRulesInvisibleTagAsAdmin() { @@ -609,13 +622,13 @@ public function testProcessFilterRulesInvisibleTagAsAdmin() { 'favorite' => null ]; - $this->assertEquals(['222'], \array_values(self::invokePrivate($this->plugin, 'processFilterRules', [$rules]))); + $this->assertEquals(['222'], array_values(self::invokePrivate($this->plugin, 'processFilterRules', [$rules]))); } /** */ public function testProcessFilterRulesInvisibleTagAsUser() { - $this->expectException(\OCP\SystemTag\TagNotFoundException::class); + $this->expectException(TagNotFoundException::class); $this->groupManager->expects($this->any()) ->method('isAdmin') @@ -693,7 +706,7 @@ public function testProcessFilterRulesVisibleTagAsUser() { 'favorite' => null ]; - $this->assertEquals(['222'], \array_values(self::invokePrivate($this->plugin, 'processFilterRules', [$rules]))); + $this->assertEquals(['222'], array_values(self::invokePrivate($this->plugin, 'processFilterRules', [$rules]))); } public function testProcessFavoriteFilter() { @@ -706,7 +719,7 @@ public function testProcessFavoriteFilter() { ->method('getFavorites') ->will($this->returnValue(['456', '789'])); - $this->assertEquals(['456', '789'], \array_values(self::invokePrivate($this->plugin, 'processFilterRules', [$rules]))); + $this->assertEquals(['456', '789'], array_values(self::invokePrivate($this->plugin, 'processFilterRules', [$rules]))); } public function filesBaseUriProvider() { diff --git a/apps/files_sharing/lib/Controller/ShareesController.php b/apps/files_sharing/lib/Controller/ShareesController.php index ecf6b9bfa22e..7287e83d5120 100644 --- a/apps/files_sharing/lib/Controller/ShareesController.php +++ b/apps/files_sharing/lib/Controller/ShareesController.php @@ -226,7 +226,7 @@ protected function getUsers($search) { // Check if exact display name || \strtolower($user->getDisplayName()) === $lowerSearch // Check if exact first email - || \strtolower($user->getEMailAddress()) === $lowerSearch + || \strtolower($user->getEMailAddress() ?? '') === $lowerSearch // Check for exact search term matches (when mail attributes configured as search terms + no enumeration) || \in_array($lowerSearch, \array_map('strtolower', $user->getSearchTerms())) ) diff --git a/apps/files_sharing/tests/ApiTest.php b/apps/files_sharing/tests/ApiTest.php index ee7a08558604..082fb848341d 100644 --- a/apps/files_sharing/tests/ApiTest.php +++ b/apps/files_sharing/tests/ApiTest.php @@ -1,4 +1,5 @@ * @author Joas Schilling diff --git a/apps/files_sharing/tests/EtagPropagationTest.php b/apps/files_sharing/tests/EtagPropagationTest.php index 2240db0743e1..4478dd5c3888 100644 --- a/apps/files_sharing/tests/EtagPropagationTest.php +++ b/apps/files_sharing/tests/EtagPropagationTest.php @@ -181,7 +181,7 @@ protected function setUpShares() { $this->logout(); } - public function testOwnerWritesToShare() { + public function testOwnerWritesToShare(): void { self::loginAsUser(self::TEST_FILES_SHARING_API_USER1); Filesystem::file_put_contents('/sub1/sub2/folder/asd.txt', 'bar'); $this->assertEtagsNotChanged([self::TEST_FILES_SHARING_API_USER4]); diff --git a/lib/private/Avatar.php b/lib/private/Avatar.php index 293499b3464c..653eac875608 100644 --- a/lib/private/Avatar.php +++ b/lib/private/Avatar.php @@ -72,7 +72,7 @@ public function __construct(IStorage $storage, IL10N $l, User $user, ILogger $lo $this->path = $this->buildAvatarPath(); } - private function buildAvatarPath() { + private function buildAvatarPath(): string { return 'avatars/' . \substr_replace(\substr_replace(\md5($this->user->getUID()), '/', 4, 0), '/', 2, 0); } diff --git a/lib/private/BackgroundJob/JobList.php b/lib/private/BackgroundJob/JobList.php index bd0d265c1d04..e37b1db0ba70 100644 --- a/lib/private/BackgroundJob/JobList.php +++ b/lib/private/BackgroundJob/JobList.php @@ -209,7 +209,7 @@ public function getNext() { } // skip jobs marked as disabled - $jobs_disabled = \explode(',', $this->config->getAppValue('backgroundjob', 'jobs_disabled', '')); + $jobs_disabled = \explode(',', $this->config->getAppValue('backgroundjob', 'jobs_disabled', '') ?? ''); if (\in_array($row['id'], $jobs_disabled, true)) { $this->logger->warning("Background job configuration has the job {$row['id']} as disabled. Skipping it"); return $this->getNext(); diff --git a/lib/private/Files/Cache/Wrapper/CacheJail.php b/lib/private/Files/Cache/Wrapper/CacheJail.php index c425e9767a6a..10eef736c02b 100644 --- a/lib/private/Files/Cache/Wrapper/CacheJail.php +++ b/lib/private/Files/Cache/Wrapper/CacheJail.php @@ -61,7 +61,7 @@ protected function getSourcePath($path) { * @param string $path * @return null|string the jailed path or null if the path is outside the jail */ - protected function getJailedPath($path) { + protected function getJailedPath(string $path) { if ($this->root === '') { return $path; } @@ -296,8 +296,11 @@ public function getIncomplete() { * @param int $id * @return string|null */ - public function getPathById($id) { + public function getPathById($id): ?string { $path = $this->cache->getPathById($id); + if ($path === null) { + return null; + } return $this->getJailedPath($path); } diff --git a/lib/private/Files/External/ConfigAdapter.php b/lib/private/Files/External/ConfigAdapter.php index 448b0e018adf..dd544dfc1d53 100644 --- a/lib/private/Files/External/ConfigAdapter.php +++ b/lib/private/Files/External/ConfigAdapter.php @@ -187,10 +187,10 @@ public function getMountsForUser(IUser $user, IStorageFactory $loader) { * fill in the correct values for $user * * @param string $user user value - * @param string|array $input + * @param array|string $input * @return string */ - private function setUserVars($user, $input) { + private function setUserVars(string $user, array|string $input): array|string { if (\is_array($input)) { foreach ($input as $key => $value) { if (\is_string($value)) { diff --git a/lib/private/Setup.php b/lib/private/Setup.php index 616912ea3ddb..2b5157607703 100644 --- a/lib/private/Setup.php +++ b/lib/private/Setup.php @@ -92,19 +92,21 @@ public function __construct( /** * Wrapper around the "class_exists" PHP function to be able to mock it + * * @param string $name * @return bool */ - protected function IsClassExisting($name) { + protected function IsClassExisting(string $name): bool { return \class_exists($name); } /** * Wrapper around the "is_callable" PHP function to be able to mock it + * * @param string $name * @return bool */ - protected function is_callable($name) { + protected function is_callable(string $name): bool { return \is_callable($name); } @@ -113,7 +115,7 @@ protected function is_callable($name) { * * @return array */ - protected function getAvailableDbDriversForPdo() { + protected function getAvailableDbDriversForPdo(): array { return \PDO::getAvailableDrivers(); } @@ -124,7 +126,7 @@ protected function getAvailableDbDriversForPdo() { * @return array * @throws Exception */ - public function getSupportedDatabases($allowAllDatabases = false) { + public function getSupportedDatabases(bool $allowAllDatabases = false): array { $availableDatabases = [ 'sqlite' => [ 'type' => 'class', @@ -190,7 +192,7 @@ public function getSupportedDatabases($allowAllDatabases = false) { * @return array of system info, including an "errors" value * in case of errors/warnings */ - public function getSystemInfo($allowAllDatabases = false) { + public function getSystemInfo(bool $allowAllDatabases = false): array { $databases = $this->getSupportedDatabases($allowAllDatabases); $dataDir = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT.'/data'); @@ -205,7 +207,7 @@ public function getSystemInfo($allowAllDatabases = false) { } if (\is_dir($dataDir) && \is_writable($dataDir)) { // Protect data directory here, so we can test if the protection is working - \OC\Setup::protectDataDirectory(); + self::protectDataDirectory(); } if (!\OC_Util::runningOn('linux')) { @@ -247,11 +249,7 @@ public function getSystemInfo($allowAllDatabases = false) { ]; } - /** - * @param $options - * @return array - */ - public function install($options) { + public function install(array $options): array { $l = $this->l10n; $error = []; @@ -288,7 +286,7 @@ public function install($options) { // validate the data directory if ( - (!\is_dir($dataDir) and !\mkdir($dataDir)) or + (!\is_dir($dataDir) && !\mkdir($dataDir)) || !\is_writable($dataDir) ) { $error[] = $l->t("Can't create or write into the data directory %s", [$dataDir]); @@ -302,7 +300,7 @@ public function install($options) { // validate the apps-external directory if ( - (!\is_dir($appsExternalDir) and !\mkdir($appsExternalDir)) or + (!\is_dir($appsExternalDir) && !\mkdir($appsExternalDir)) || !\is_writable($appsExternalDir) ) { $htmlAppsExternalDir = \htmlspecialchars_decode($appsExternalDir); @@ -324,7 +322,7 @@ public function install($options) { } //use sqlite3 when available, otherwise sqlite2 will be used. - if ($dbType=='sqlite' and $this->IsClassExisting('SQLite3')) { + if ($dbType === 'sqlite' && $this->IsClassExisting('SQLite3')) { $dbType='sqlite3'; } @@ -399,9 +397,9 @@ public function install($options) { && \is_writable(self::pathToHtaccess()) ) { // Update .htaccess files - Setup::updateHtaccess(); + self::updateHtaccess(); } - Setup::protectDataDirectory(); + self::protectDataDirectory(); //try to write logtimezone if (\date_default_timezone_get()) { @@ -445,21 +443,21 @@ public function install($options) { return $error; } - public static function installBackgroundJobs() { + public static function installBackgroundJobs(): void { \OC::$server->getJobList()->add('\OC\Authentication\Token\DefaultTokenCleanupJob'); } /** * @return string Absolute path to htaccess */ - public static function pathToHtaccess() { + public static function pathToHtaccess(): string { return \OC::$SERVERROOT.'/.htaccess'; } /** * Append the correct ErrorDocument path for Apache hosts */ - public static function updateHtaccess() { + public static function updateHtaccess(): void { $config = \OC::$server->getConfig(); $il10n = \OC::$server->getL10N('lib'); @@ -470,7 +468,11 @@ public static function updateHtaccess() { return; } $webRoot = \parse_url($webRoot, PHP_URL_PATH); - $webRoot = \rtrim($webRoot, '/'); + if (\is_string($webRoot)) { + $webRoot = \rtrim($webRoot, '/'); + } else { + $webRoot = ''; + } } else { $webRoot = !empty(\OC::$WEBROOT) ? \OC::$WEBROOT : '/'; } @@ -543,7 +545,7 @@ public static function updateHtaccess() { } } - public static function protectDataDirectory() { + public static function protectDataDirectory(): void { //Require all denied $now = \date('Y-m-d H:i:s'); $content = "# Generated by ownCloud on $now\n"; diff --git a/lib/private/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php index 1b86f3024683..8e07d99d4000 100644 --- a/lib/private/Share20/DefaultShareProvider.php +++ b/lib/private/Share20/DefaultShareProvider.php @@ -552,7 +552,7 @@ public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offs * @inheritdoc */ public function getShareById($id, $recipientId = null) { - if (!ctype_digit($id)) { + if (!ctype_digit((string)$id)) { // share id is defined as a field of type integer // if someone calls the API asking for a share id like "abc" // then there is no point trying to query the database, diff --git a/lib/private/Template/JSResourceLocator.php b/lib/private/Template/JSResourceLocator.php index d73038c6c4e6..14925f9a4baa 100644 --- a/lib/private/Template/JSResourceLocator.php +++ b/lib/private/Template/JSResourceLocator.php @@ -39,7 +39,7 @@ public function doFind($script) { $webRoot = \substr($this->theme->getWebPath(), 0, -\strlen($themeDirectory)); } - if (\strpos($script, '/l10n/') !== false) { + if (str_contains($script, '/l10n/')) { $app = \substr($fullScript, 0, \strpos($fullScript, '/')); $appFolderLocation = \explode('/', $this->appManager->getAppWebPath($app))[1] ?? 'apps'; diff --git a/lib/private/User/User.php b/lib/private/User/User.php index a252016b2dd4..3739570ab5db 100644 --- a/lib/private/User/User.php +++ b/lib/private/User/User.php @@ -191,9 +191,9 @@ public function setUserName($userName) { * * @return string */ - public function getDisplayName() { - $displayName = $this->account->getDisplayName(); - if (\strlen($displayName) === 0) { + public function getDisplayName(): string { + $displayName = $this->account->getDisplayName() ?? ''; + if ($displayName === '') { $displayName = $this->getUID(); } return $displayName; diff --git a/lib/private/legacy/helper.php b/lib/private/legacy/helper.php index fdea49525aee..47b235ca88c8 100644 --- a/lib/private/legacy/helper.php +++ b/lib/private/legacy/helper.php @@ -195,11 +195,12 @@ public static function copyr($src, $dest) { /** * Recursive deletion of folders + * * @param string $dir path to the folder * @param bool $deleteSelf if set to false only the content of the folder will be deleted * @return bool */ - public static function rmdirr($dir, $deleteSelf = true) { + public static function rmdirr(string $dir, bool $deleteSelf = true): bool { if (\is_dir($dir)) { $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), @@ -556,7 +557,7 @@ public static function findBinaryPath($program) { * @param string $path * @return string|null */ - public static function getCleanedPath($path = '') { + public static function getCleanedPath(string $path = ''): ?string { $pattern = "((\/[\w\d]*)+)"; if (\preg_match_all($pattern, $path, $matches) > 0) { diff --git a/tests/lib/AppFramework/AppTest.php b/tests/lib/AppFramework/AppTest.php index 29fa24fe971b..9bfc660d9942 100644 --- a/tests/lib/AppFramework/AppTest.php +++ b/tests/lib/AppFramework/AppTest.php @@ -24,37 +24,39 @@ namespace Test\AppFramework; use OC\AppFramework\App; +use OC\AppFramework\DependencyInjection\DIContainer; use OCP\AppFramework\Http\Response; +use Test\TestCase; +use function file_put_contents; +use function mkdir; -function rrmdir($directory) { - $files = \array_diff(\scandir($directory), ['.','..']); - foreach ($files as $file) { - if (\is_dir($directory . '/' . $file)) { - rrmdir($directory . '/' . $file); - } else { - \unlink($directory . '/' . $file); - } - } - return \rmdir($directory); -} - -class AppTest extends \Test\TestCase { +class AppTest extends TestCase { private $container; private $io; - private $api; private $controller; private $dispatcher; - private $params; private $headers; private $output; private $controllerName; private $controllerMethod; private $appPath; + public static function rrmdir($directory): bool { + $files = \array_diff(\scandir($directory), ['.','..']); + foreach ($files as $file) { + if (\is_dir($directory . '/' . $file)) { + self::rrmdir($directory . '/' . $file); + } else { + \unlink($directory . '/' . $file); + } + } + return \rmdir($directory); + } + protected function setUp(): void { parent::setUp(); - $this->container = new \OC\AppFramework\DependencyInjection\DIContainer('test', []); + $this->container = new DIContainer('test', []); $this->controller = $this->getMockBuilder( 'OCP\AppFramework\Controller' ) @@ -80,14 +82,14 @@ protected function setUp(): void { $this->appPath = __DIR__ . '/../../../apps/namespacetestapp'; $infoXmlPath = $this->appPath . '/appinfo/info.xml'; - \mkdir($this->appPath . '/appinfo', 0777, true); + mkdir($this->appPath . '/appinfo', 0777, true); $xml = '' . '' . 'namespacetestapp' . 'NameSpaceTestApp' . ''; - \file_put_contents($infoXmlPath, $xml); + file_put_contents($infoXmlPath, $xml); } public function testControllerNameAndMethodAreBeingPassed() { @@ -126,7 +128,7 @@ public function testBuildAppNamespaceInfoXml() { } protected function tearDown(): void { - rrmdir($this->appPath); + self::rrmdir($this->appPath); parent::tearDown(); } diff --git a/tests/lib/AvatarManagerTest.php b/tests/lib/AvatarManagerTest.php index faf22b1f6194..eff21c570672 100644 --- a/tests/lib/AvatarManagerTest.php +++ b/tests/lib/AvatarManagerTest.php @@ -96,8 +96,9 @@ public function testGetAvatarInvalidUser() { $this->avatarManager->getAvatar('invalidUser'); } - public function testGetAvatarValidUser() { + public function testGetAvatarValidUser(): void { $user = $this->createMock(User::class); + $user->method('getUID')->willReturn(''); $this->userManager->expects($this->once()) ->method('get') ->with('valid-user') diff --git a/tests/lib/AvatarTest.php b/tests/lib/AvatarTest.php index f0d67874a4fb..7292387f6844 100644 --- a/tests/lib/AvatarTest.php +++ b/tests/lib/AvatarTest.php @@ -11,18 +11,22 @@ namespace Test; +use OC; use OC\Avatar; use OC\User\User; +use OC_Image; +use OCP\Files\NotFoundException; use OCP\Files\Storage\IStorage; use OCP\Files\StorageNotAvailableException; use OCP\IL10N; use OCP\ILogger; +use PHPUnit\Framework\MockObject\MockObject; -class AvatarTest extends \Test\TestCase { - /** @var IStorage | \PHPUnit\Framework\MockObject\MockObject */ +class AvatarTest extends TestCase { + /** @var IStorage | MockObject */ private $storage; - /** @var \OC\User\User | \PHPUnit\Framework\MockObject\MockObject $user */ + /** @var User | MockObject $user */ private $user; public function setUp(): void { @@ -54,6 +58,7 @@ public function providesUserIds(): array { } public function testGetNoAvatar(): void { + $this->user->method('getUID')->willReturn('user1'); $avatar = $this->mockAvatar(); $this->assertFalse($avatar->get()); } @@ -65,10 +70,11 @@ public function testGetAvatarSizeMatch(): void { ['avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.128.jpg', true], ]); - $expected = new \OC_Image(\OC::$SERVERROOT . '/tests/data/testavatar.png'); + $expected = new OC_Image(OC::$SERVERROOT . '/tests/data/testavatar.png'); $this->storage->method('file_get_contents')->with('avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.128.jpg')->willReturn($expected->data()); + $this->user->method('getUID')->willReturn(''); $avatar = $this->mockAvatar(); $this->assertEquals($expected->data(), $avatar->get(128)->data()); } @@ -79,10 +85,11 @@ public function testGetAvatarSizeMinusOne(): void { ['avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.jpg', true], ]); - $expected = new \OC_Image(\OC::$SERVERROOT . '/tests/data/testavatar.png'); + $expected = new OC_Image(OC::$SERVERROOT . '/tests/data/testavatar.png'); $this->storage->method('file_get_contents')->with('avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.jpg')->willReturn($expected->data()); + $this->user->method('getUID')->willReturn(''); $avatar = $this->mockAvatar(); $this->assertEquals($expected->data(), $avatar->get(-1)->data()); } @@ -94,8 +101,8 @@ public function testGetAvatarNoSizeMatch(): void { ['avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.32.png', false], ]); - $expected = new \OC_Image(\OC::$SERVERROOT . '/tests/data/testavatar.png'); - $expected2 = new \OC_Image(\OC::$SERVERROOT . '/tests/data/testavatar.png'); + $expected = new OC_Image(OC::$SERVERROOT . '/tests/data/testavatar.png'); + $expected2 = new OC_Image(OC::$SERVERROOT . '/tests/data/testavatar.png'); $expected2->resize(32); $this->storage->method('file_get_contents') @@ -106,18 +113,20 @@ public function testGetAvatarNoSizeMatch(): void { if ($path === 'avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.32.png') { return $expected2->data(); } - throw new \OCP\Files\NotFoundException(); + throw new NotFoundException(); }); $this->storage->expects($this->once()) ->method('file_put_contents') ->with('avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.32.png', $expected2->data()); + $this->user->method('getUID')->willReturn(''); $avatar = $this->mockAvatar(); $this->assertEquals($expected2->data(), $avatar->get(32)->data()); } public function testExistsNo(): void { + $this->user->method('getUID')->willReturn(''); $avatar = $this->mockAvatar(); $this->assertFalse($avatar->exists()); } @@ -128,6 +137,7 @@ public function testExistsJPG(): void { ['avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.jpg', true], ['avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.png', false], ]); + $this->user->method('getUID')->willReturn(''); $avatar = $this->mockAvatar(); $this->assertTrue($avatar->exists()); } @@ -138,6 +148,7 @@ public function testExistsPNG(): void { ['avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.jpg', false], ['avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.png', true], ]); + $this->user->method('getUID')->willReturn(''); $avatar = $this->mockAvatar(); $this->assertTrue($avatar->exists()); } @@ -145,6 +156,7 @@ public function testExistsPNG(): void { public function testExistsStorageNotAvailable(): void { $this->storage->method('file_exists') ->willThrowException(new StorageNotAvailableException()); + $this->user->method('getUID')->willReturn(''); $avatar = $this->mockAvatar(); $this->assertFalse($avatar->exists()); } @@ -153,21 +165,22 @@ public function testSetAvatar(): void { $this->storage->expects($this->once())->method('rmdir') ->with('avatars/d4/1d/8cd98f00b204e9800998ecf8427e'); - $image = new \OC_Image(\OC::$SERVERROOT . '/tests/data/testavatar.png'); + $image = new OC_Image(OC::$SERVERROOT . '/tests/data/testavatar.png'); $this->storage->method('file_put_contents') ->with('avatars/d4/1d/8cd98f00b204e9800998ecf8427e/avatar.png', $image->data()); // One on remove and once on setting the new avatar $this->user->expects($this->exactly(2))->method('triggerChange'); + $this->user->method('getUID')->willReturn(''); $avatar = $this->mockAvatar(); $avatar->set($image->data()); } private function mockAvatar(): Avatar { - /** @var \OCP\IL10N | \PHPUnit\Framework\MockObject\MockObject $l */ + /** @var IL10N | MockObject $l */ $l = $this->createMock(IL10N::class); $l->method('t')->will($this->returnArgument(0)); - return new \OC\Avatar($this->storage, $l, $this->user, $this->createMock(ILogger::class)); + return new Avatar($this->storage, $l, $this->user, $this->createMock(ILogger::class)); } } diff --git a/tests/lib/Files/External/ConfigAdapterTest.php b/tests/lib/Files/External/ConfigAdapterTest.php index b87dd4cc15a4..80352dd486c8 100644 --- a/tests/lib/Files/External/ConfigAdapterTest.php +++ b/tests/lib/Files/External/ConfigAdapterTest.php @@ -33,9 +33,11 @@ use OCP\IUser; use OCP\ISession; use OC\Files\External\StorageConfig; +use PHPUnit\Framework\MockObject\MockObject; +use Test\TestCase; -class ConfigAdapterTest extends \Test\TestCase { - /** @var \OCP\IConfig | \PHPUnit\Framework\MockObject\MockObject */ +class ConfigAdapterTest extends TestCase { + /** @var IConfig | MockObject */ private $config; /** @var IUserStoragesService */ @@ -44,10 +46,10 @@ class ConfigAdapterTest extends \Test\TestCase { /** @var IUserGlobalStoragesService */ private $userGlobalStoragesService; - /** @var IUser | \PHPUnit\Framework\MockObject\MockObject **/ + /** @var IUser | MockObject **/ private $user; - /** @var ISession | \PHPUnit\Framework\MockObject\MockObject **/ + /** @var ISession | MockObject **/ private $session; /** @var int */ @@ -59,9 +61,13 @@ protected function setUp(): void { $this->userGlobalStoragesService = $this->createMock(UserGlobalStoragesService::class); $this->session = $this->createMock(ISession::class); $this->user = $this->createMock(IUser::class); - $this->user->expects($this->any()) + $this->user->expects($this->atLeastOnce()) ->method('getUID') ->willReturn('user1'); + $this->user->expects($this->atLeastOnce()) + ->method('getUserName') + ->willReturn('altlogin1'); + $this->configId = 0; } @@ -126,7 +132,7 @@ private function getMountsForUser($globalStorages, $personalStorages) { return $configAdapter->getMountsForUser($this->user, $storageFactory); } - public function testGetPersonalMounts() { + public function testGetPersonalMounts(): void { $storage1 = $this->createStorageConfig('/mount1', ['test_value' => true], ['backend_option' => 'abc']); $storage2 = $this->createStorageConfig('/globalmount1', ['test_value2' => 'abc'], ['backend_option' => 'def']); @@ -179,24 +185,8 @@ public function testGetPersonalMountSharingOption($isSharingAllowed, $expectedVa $this->assertTrue($options['enable_sharing']); } - public function testGetPersonalMountsUserPlaceholder() { - $this->user->expects($this->any()) - ->method('getUserName') - ->willReturn('user1'); - - $storage1 = $this->createStorageConfig('/mount1', [], ['backend_option' => '$user']); - $storage2 = $this->createStorageConfig('/globalmount1', [], ['backend_option' => '$user']); - - $result = $this->getMountsForUser([$storage2], [$storage1]); - - $this->assertCount(2, $result); - - $this->assertEquals('user1', $storage1->getBackendOption('backend_option')); - $this->assertEquals('user1', $storage2->getBackendOption('backend_option')); - } - - public function testGetPersonalMountsUserPlaceholderAltLogin() { - $this->user->expects($this->any()) + public function testGetPersonalMountsUserPlaceholderAltLogin(): void { + $this->user ->method('getUserName') ->willReturn('altlogin1'); diff --git a/tests/lib/SetupTest.php b/tests/lib/SetupTest.php index 441cc50b6820..a98747cdefc3 100644 --- a/tests/lib/SetupTest.php +++ b/tests/lib/SetupTest.php @@ -9,6 +9,11 @@ namespace Test; use OCP\IConfig; +use bantu\IniGetWrapper\IniGetWrapper; +use OCP\IL10N; +use OCP\ILogger; +use OCP\Security\ISecureRandom; +use OC\Setup; class SetupTest extends \Test\TestCase { /** @var IConfig | \PHPUnit\Framework\MockObject\MockObject */ @@ -29,37 +34,35 @@ class SetupTest extends \Test\TestCase { protected function setUp(): void { parent::setUp(); - $this->config = $this->createMock('\OCP\IConfig'); - $this->iniWrapper = $this->createMock('\bantu\IniGetWrapper\IniGetWrapper'); - $this->l10n = $this->createMock('\OCP\IL10N'); - $this->defaults = $this->createMock('\OC_Defaults'); - $this->logger = $this->createMock('\OCP\ILogger'); - $this->random = $this->createMock('\OCP\Security\ISecureRandom'); - $this->setupClass = $this->getMockBuilder('\OC\Setup') + $this->config = $this->createMock(IConfig::class); + $this->iniWrapper = $this->createMock(IniGetWrapper::class); + $this->l10n = $this->createMock(IL10N::class); + $this->defaults = $this->createMock(\OC_Defaults::class); + $this->logger = $this->createMock(ILogger::class); + $this->random = $this->createMock(ISecureRandom::class); + $this->setupClass = $this->getMockBuilder(Setup::class) ->setMethods(['IsClassExisting', 'is_callable', 'getAvailableDbDriversForPdo']) ->setConstructorArgs([$this->config, $this->iniWrapper, $this->l10n, $this->defaults, $this->logger, $this->random]) ->getMock(); } - public function testGetSupportedDatabasesWithOneWorking() { + public function testGetSupportedDatabasesWithOneWorking(): void { $this->config ->expects($this->once()) ->method('getSystemValue') - ->will($this->returnValue( - ['sqlite', 'mysql', 'oci'] - )); + ->willReturn(['sqlite', 'mysql', 'oci']); $this->setupClass ->expects($this->once()) ->method('IsClassExisting') - ->will($this->returnValue(true)); + ->willReturn(true); $this->setupClass ->expects($this->once()) ->method('is_callable') - ->will($this->returnValue(false)); + ->willReturn(false); $this->setupClass ->expects($this->once()) ->method('getAvailableDbDriversForPdo') - ->will($this->returnValue([])); + ->willReturn([]); $result = $this->setupClass->getSupportedDatabases(); $expectedResult = [ 'sqlite' => 'SQLite' @@ -68,49 +71,45 @@ public function testGetSupportedDatabasesWithOneWorking() { $this->assertSame($expectedResult, $result); } - public function testGetSupportedDatabasesWithNoWorking() { + public function testGetSupportedDatabasesWithNoWorking(): void { $this->config ->expects($this->once()) ->method('getSystemValue') - ->will($this->returnValue( - ['sqlite', 'mysql', 'oci', 'pgsql'] - )); + ->willReturn(['sqlite', 'mysql', 'oci', 'pgsql']); $this->setupClass ->expects($this->once()) ->method('IsClassExisting') - ->will($this->returnValue(false)); + ->willReturn(false); $this->setupClass ->expects($this->exactly(2)) ->method('is_callable') - ->will($this->returnValue(false)); + ->willReturn(false); $this->setupClass ->expects($this->once()) ->method('getAvailableDbDriversForPdo') - ->will($this->returnValue([])); + ->willReturn([]); $result = $this->setupClass->getSupportedDatabases(); $this->assertSame([], $result); } - public function testGetSupportedDatabasesWithAllWorking() { + public function testGetSupportedDatabasesWithAllWorking(): void { $this->config ->expects($this->once()) ->method('getSystemValue') - ->will($this->returnValue( - ['sqlite', 'mysql', 'pgsql', 'oci'] - )); + ->willReturn(['sqlite', 'mysql', 'pgsql', 'oci']); $this->setupClass ->expects($this->once()) ->method('IsClassExisting') - ->will($this->returnValue(true)); + ->willReturn(true); $this->setupClass ->expects($this->exactly(2)) ->method('is_callable') - ->will($this->returnValue(true)); + ->willReturn(true); $this->setupClass ->expects($this->once()) ->method('getAvailableDbDriversForPdo') - ->will($this->returnValue(['mysql'])); + ->willReturn(['mysql']); $result = $this->setupClass->getSupportedDatabases(); $expectedResult = [ 'sqlite' => 'SQLite', @@ -121,22 +120,20 @@ public function testGetSupportedDatabasesWithAllWorking() { $this->assertSame($expectedResult, $result); } - /** - */ - public function testGetSupportedDatabaseException() { + public function testGetSupportedDatabaseException(): void { $this->expectException(\Exception::class); $this->expectExceptionMessage('Supported databases are not properly configured.'); $this->config ->expects($this->once()) ->method('getSystemValue') - ->will($this->returnValue('NotAnArray')); + ->willReturn('NotAnArray'); $this->setupClass->getSupportedDatabases(); } /** */ - public function testCannotUpdateHtaccess() { + public function testCannotUpdateHtaccess(): void { $this->expectException(\Exception::class); $this->expectExceptionMessage('Can\'t update'); @@ -162,7 +159,7 @@ public function testCannotUpdateHtaccess() { /** */ - public function testHtaccessIsFolder() { + public function testHtaccessIsFolder(): void { $this->expectException(\Exception::class); $this->expectExceptionMessage('Can\'t update'); @@ -182,7 +179,7 @@ public function testHtaccessIsFolder() { } } - public function testUpdateHtaccess() { + public function testUpdateHtaccess(): void { $origServerRoot = \OC::$SERVERROOT; $htaccessFile = \OC::$SERVERROOT . '/tests/data/.htaccess'; \touch($htaccessFile); diff --git a/tests/lib/Share20/DefaultShareProviderTest.php b/tests/lib/Share20/DefaultShareProviderTest.php index ef95d24ef13c..05e2b64a41c2 100644 --- a/tests/lib/Share20/DefaultShareProviderTest.php +++ b/tests/lib/Share20/DefaultShareProviderTest.php @@ -21,8 +21,11 @@ */ namespace Test\Share20; +use DateTime; +use OC; use OC\Authentication\Token\DefaultTokenMapper; use OC\Share20\DefaultShareProvider; +use OC\Share20\Exception\ProviderException; use OC\Share20\ShareAttributes; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Files\File; @@ -34,8 +37,15 @@ use OCP\IUser; use OCP\IUserManager; use OCP\Share; +use OCP\Share\Exceptions\ShareNotFound; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; use OCP\Share\IShare; +use function basename; +use function count; +use function md5; +use function range; +use function strval; /** * Class DefaultShareProviderTest @@ -47,20 +57,20 @@ class DefaultShareProviderTest extends TestCase { /** @var IDBConnection */ protected $dbConn; - /** @var IUserManager | \PHPUnit\Framework\MockObject\MockObject */ + /** @var IUserManager | MockObject */ protected $userManager; - /** @var IGroupManager | \PHPUnit\Framework\MockObject\MockObject */ + /** @var IGroupManager | MockObject */ protected $groupManager; - /** @var IRootFolder | \PHPUnit\Framework\MockObject\MockObject */ + /** @var IRootFolder | MockObject */ protected $rootFolder; /** @var DefaultShareProvider */ protected $provider; public function setUp(): void { - $this->dbConn = \OC::$server->getDatabaseConnection(); + $this->dbConn = OC::$server->getDatabaseConnection(); $this->userManager = $this->createMock(IUserManager::class); $this->groupManager = $this->createMock(IGroupManager::class); $this->rootFolder = $this->createMock(IRootFolder::class); @@ -173,8 +183,8 @@ public function getShareByIdNotExistProvider() { /** * @dataProvider getShareByIdNotExistProvider */ - public function testGetShareByIdNotExist($shareId) { - $this->expectException(\OCP\Share\Exceptions\ShareNotFound::class); + public function testGetShareByIdNotExist($shareId): void { + $this->expectException(ShareNotFound::class); $this->provider->getShareById($shareId); } @@ -456,7 +466,7 @@ public function testGetShareByIdLinkShare() { $this->assertEquals($ownerPath, $share->getNode()); $this->assertEquals(13, $share->getPermissions()); $this->assertEquals('token', $share->getToken()); - $this->assertEquals(\DateTime::createFromFormat('Y-m-d H:i:s', '2000-01-02 00:00:00'), $share->getExpirationDate()); + $this->assertEquals(DateTime::createFromFormat('Y-m-d H:i:s', '2000-01-02 00:00:00'), $share->getExpirationDate()); $this->assertEquals('myTarget', $share->getTarget()); $this->assertEquals('some_name', $share->getName()); } @@ -731,7 +741,7 @@ public function testCreateUserShare() { $this->assertSame('shareOwner', $share2->getShareOwner()); $this->assertSame(1, $share2->getPermissions()); $this->assertSame('/target', $share2->getTarget()); - $this->assertLessThanOrEqual(new \DateTime(), $share2->getShareTime()); + $this->assertLessThanOrEqual(new DateTime(), $share2->getShareTime()); $this->assertSame($path, $share2->getNode()); $this->assertSame( [ @@ -794,7 +804,7 @@ public function testCreateGroupShare() { $this->assertSame('shareOwner', $share2->getShareOwner()); $this->assertSame(1, $share2->getPermissions()); $this->assertSame('/target', $share2->getTarget()); - $this->assertLessThanOrEqual(new \DateTime(), $share2->getShareTime()); + $this->assertLessThanOrEqual(new DateTime(), $share2->getShareTime()); $this->assertSame($path, $share2->getNode()); $this->assertSame( [ @@ -842,7 +852,7 @@ public function testCreateLinkShare() { $share->setPassword('password'); $share->setToken('token'); $share->setName('some_name'); - $expireDate = new \DateTime(); + $expireDate = new DateTime(); $share->setExpirationDate($expireDate); $share->setTarget('/target'); @@ -855,12 +865,12 @@ public function testCreateLinkShare() { $this->assertSame('shareOwner', $share2->getShareOwner()); $this->assertSame(1, $share2->getPermissions()); $this->assertSame('/target', $share2->getTarget()); - $this->assertLessThanOrEqual(new \DateTime(), $share2->getShareTime()); + $this->assertLessThanOrEqual(new DateTime(), $share2->getShareTime()); $this->assertSame($path, $share2->getNode()); $this->assertSame('password', $share2->getPassword()); $this->assertSame('token', $share2->getToken()); $this->assertSame('some_name', $share2->getName()); - $this->assertEquals($expireDate->format(\DateTime::ISO8601), $share2->getExpirationDate()->format(\DateTime::ISO8601)); + $this->assertEquals($expireDate->format(DateTime::ISO8601), $share2->getExpirationDate()->format(DateTime::ISO8601)); $share->setName(null); $share2 = $this->provider->create($share); @@ -904,7 +914,7 @@ public function testGetShareByToken() { /** */ public function testGetShareByTokenNotFound() { - $this->expectException(\OCP\Share\Exceptions\ShareNotFound::class); + $this->expectException(ShareNotFound::class); $this->provider->getShareByToken('invalidtoken'); } @@ -925,8 +935,8 @@ private function createTestFileEntry($path, $storage = 1) { ->values([ 'storage' => $qb->expr()->literal($storage), 'path' => $qb->expr()->literal($path), - 'path_hash' => $qb->expr()->literal(\md5($path)), - 'name' => $qb->expr()->literal(\basename($path)), + 'path_hash' => $qb->expr()->literal(md5($path)), + 'name' => $qb->expr()->literal(basename($path)), ]); $this->assertEquals(1, $qb->execute()); return $qb->getLastInsertId(); @@ -1031,7 +1041,7 @@ public function testGetSharedWithGroup($storageStringId, $fileName1, $fileName2) $id = $qb->getLastInsertId(); $groups = []; - foreach (\range(0, 100) as $i) { + foreach (range(0, 100) as $i) { $group = $this->createMock(IGroup::class); $group->method('getGID')->willReturn('group'.$i); $groups[] = $group; @@ -1088,7 +1098,7 @@ public function testGetSharedWithGroupUserModified($storageStringId, $fileName1, 'file_source' => $qb->expr()->literal($fileId), 'file_target' => $qb->expr()->literal('myTarget'), 'permissions' => $qb->expr()->literal(7), - 'accepted' => $qb->expr()->literal(\OCP\Share::STATE_ACCEPTED), + 'accepted' => $qb->expr()->literal(Share::STATE_ACCEPTED), ]); $this->assertEquals(1, $qb->execute()); $id = $qb->getLastInsertId(); @@ -1107,7 +1117,7 @@ public function testGetSharedWithGroupUserModified($storageStringId, $fileName1, 'file_source' => $qb->expr()->literal($fileId), 'file_target' => $qb->expr()->literal('wrongTarget'), 'permissions' => $qb->expr()->literal(31), - 'accepted' => $qb->expr()->literal(\OCP\Share::STATE_ACCEPTED), + 'accepted' => $qb->expr()->literal(Share::STATE_ACCEPTED), 'parent' => $qb->expr()->literal($id), ]); $this->assertEquals(1, $qb->execute()); @@ -1126,7 +1136,7 @@ public function testGetSharedWithGroupUserModified($storageStringId, $fileName1, 'file_source' => $qb->expr()->literal($fileId), 'file_target' => $qb->expr()->literal('userTarget'), 'permissions' => $qb->expr()->literal(31), - 'accepted' => $qb->expr()->literal(\OCP\Share::STATE_REJECTED), + 'accepted' => $qb->expr()->literal(Share::STATE_REJECTED), 'parent' => $qb->expr()->literal($id), ]); $this->assertEquals(1, $qb->execute()); @@ -1164,7 +1174,7 @@ public function testGetSharedWithGroupUserModified($storageStringId, $fileName1, $this->assertSame('sharedBy', $share->getSharedBy()); $this->assertSame(Share::SHARE_TYPE_GROUP, $share->getShareType()); $this->assertSame(7, $share->getPermissions(), 'resolved group share takes permission of parent'); - $this->assertSame(\OCP\Share::STATE_REJECTED, $share->getState()); + $this->assertSame(Share::STATE_REJECTED, $share->getState()); $this->assertSame('userTarget', $share->getTarget()); } @@ -1725,7 +1735,7 @@ public function testGetSharedWithWithDeletedFile($shareType, $trashed) { $this->rootFolder->method('getById')->with($deletedFileId)->willReturn([$file]); $groups = []; - foreach (\range(0, 100) as $i) { + foreach (range(0, 100) as $i) { $group = $this->createMock(IGroup::class); $group->method('getGID')->willReturn('group'.$i); $groups[] = $group; @@ -2172,7 +2182,7 @@ public function testDeleteFromSelfGroupAlreadyCustomShare() { /** */ public function testDeleteFromSelfGroupUserNotInGroup() { - $this->expectException(\OC\Share20\Exception\ProviderException::class); + $this->expectException(ProviderException::class); $this->expectExceptionMessage('Recipient not in receiving group'); $qb = $this->dbConn->getQueryBuilder(); @@ -2218,13 +2228,13 @@ public function testDeleteFromSelfGroupUserNotInGroup() { /** */ public function testDeleteFromSelfGroupDoesNotExist() { - $this->expectException(\OC\Share20\Exception\ProviderException::class); + $this->expectException(ProviderException::class); $this->expectExceptionMessage('Group "group" does not exist'); $qb = $this->dbConn->getQueryBuilder(); $stmt = $qb->insert('share') ->values([ - 'share_type' => $qb->expr()->literal(\OCP\Share::SHARE_TYPE_GROUP), + 'share_type' => $qb->expr()->literal(Share::SHARE_TYPE_GROUP), 'share_with' => $qb->expr()->literal('group'), 'uid_owner' => $qb->expr()->literal('user1'), 'uid_initiator' => $qb->expr()->literal('user1'), @@ -2247,7 +2257,7 @@ public function testDeleteFromSelfGroupDoesNotExist() { $this->groupManager->method('get')->with('group')->willReturn(null); - $file = $this->createMock(\OCP\Files\File::class); + $file = $this->createMock(File::class); $file->method('getId')->willReturn(1); $this->rootFolder->method('getUserFolder')->with('user1')->will($this->returnSelf()); @@ -2310,7 +2320,7 @@ public function testDeleteFromSelfUser() { /** */ public function testDeleteFromSelfUserNotRecipient() { - $this->expectException(\OC\Share20\Exception\ProviderException::class); + $this->expectException(ProviderException::class); $this->expectExceptionMessage('Recipient does not match'); $qb = $this->dbConn->getQueryBuilder(); @@ -2352,7 +2362,7 @@ public function testDeleteFromSelfUserNotRecipient() { /** */ public function testDeleteFromSelfLink() { - $this->expectException(\OC\Share20\Exception\ProviderException::class); + $this->expectException(ProviderException::class); $this->expectExceptionMessage('Invalid share type 3'); $qb = $this->dbConn->getQueryBuilder(); @@ -2390,11 +2400,11 @@ public function testDeleteFromSelfLink() { /** */ public function testUpdateForRecipientWrongType() { - $this->expectException(\OC\Share20\Exception\ProviderException::class); + $this->expectException(ProviderException::class); $this->expectExceptionMessage('Can\'t update share of recipient for share type 3'); $share = $this->createMock(IShare::class); - $share->method('getShareType')->willReturn(\OCP\Share::SHARE_TYPE_LINK); + $share->method('getShareType')->willReturn(Share::SHARE_TYPE_LINK); $this->provider->updateForRecipient($share, 'recipient1'); } @@ -2674,7 +2684,7 @@ public function testUpdateGroupSubShares() { null, null, null, - \OCP\Share::STATE_PENDING + Share::STATE_PENDING ); $id2 = $this->addShareToDB( @@ -2690,7 +2700,7 @@ public function testUpdateGroupSubShares() { null, $id, null, - \OCP\Share::STATE_ACCEPTED + Share::STATE_ACCEPTED ); $id3 = $this->addShareToDB( @@ -2706,7 +2716,7 @@ public function testUpdateGroupSubShares() { null, $id, null, - \OCP\Share::STATE_REJECTED + Share::STATE_REJECTED ); $users = []; @@ -2780,13 +2790,13 @@ public function testUpdateGroupSubShares() { $this->assertSame('user4', $shares[0]['uid_initiator']); $this->assertSame('user5', $shares[0]['uid_owner']); $this->assertSame(1, (int)$shares[0]['permissions']); - $this->assertSame(\OCP\Share::STATE_ACCEPTED, (int)$shares[0]['accepted']); + $this->assertSame(Share::STATE_ACCEPTED, (int)$shares[0]['accepted']); $this->assertSame('user3', $shares[1]['share_with']); $this->assertSame('user4', $shares[1]['uid_initiator']); $this->assertSame('user5', $shares[1]['uid_owner']); $this->assertSame(1, (int)$shares[1]['permissions'], 'permissions adjusted from 0 to updated value from parent'); - $this->assertSame(\OCP\Share::STATE_REJECTED, (int)$shares[1]['accepted']); + $this->assertSame(Share::STATE_REJECTED, (int)$shares[1]['accepted']); $stmt->free(); } @@ -2901,10 +2911,10 @@ public function testMoveGroupShare() { public function providesShareStateChanges() { return [ - [\OCP\Share::STATE_PENDING, \OCP\Share::STATE_ACCEPTED], - [\OCP\Share::STATE_PENDING, \OCP\Share::STATE_REJECTED], - [\OCP\Share::STATE_ACCEPTED, \OCP\Share::STATE_REJECTED], - [\OCP\Share::STATE_REJECTED, \OCP\Share::STATE_ACCEPTED], + [Share::STATE_PENDING, Share::STATE_ACCEPTED], + [Share::STATE_PENDING, Share::STATE_REJECTED], + [Share::STATE_ACCEPTED, Share::STATE_REJECTED], + [Share::STATE_REJECTED, Share::STATE_ACCEPTED], ]; } @@ -2928,7 +2938,7 @@ public function testUpdateShareState($sourceState, $targetState) { null, null, null, - \OCP\Share::STATE_PENDING + Share::STATE_PENDING ); $id2 = $this->addShareToDB( diff --git a/tests/lib/TempManagerTest.php b/tests/lib/TempManagerTest.php index c11876544217..3280a552402a 100644 --- a/tests/lib/TempManagerTest.php +++ b/tests/lib/TempManagerTest.php @@ -35,8 +35,10 @@ protected function setUp(): void { } protected function tearDown(): void { - \OC_Helper::rmdirr($this->baseDir); - $this->baseDir = null; + if ($this->baseDir) { + \OC_Helper::rmdirr($this->baseDir); + $this->baseDir = null; + } parent::tearDown(); } diff --git a/tests/lib/Template/CSSResourceLocatorTest.php b/tests/lib/Template/CSSResourceLocatorTest.php index f5e4ee957b58..227962b6621f 100644 --- a/tests/lib/Template/CSSResourceLocatorTest.php +++ b/tests/lib/Template/CSSResourceLocatorTest.php @@ -39,6 +39,7 @@ public function getResourceLocator($theme, $core_map, $appsRoots) { $themeInstance->method('getName')->willReturn($theme); $themeInstance->method('getBaseDirectory')->willReturn($this->appRoot); $themeInstance->method('getDirectory')->willReturn($this->themeAppDir); + $themeInstance->method('getWebPath')->willReturn(''); $this->appManager = $this->getMockBuilder(AppManager::class) ->disableOriginalConstructor() @@ -50,7 +51,7 @@ public function getResourceLocator($theme, $core_map, $appsRoots) { ->getMock(); } - public function testFindCoreStyle() { + public function testFindCoreStyle(): void { /** @var \OC\Template\CSSResourceLocator $locator */ $locator = $this->getResourceLocator( 'theme', diff --git a/tests/lib/Template/JSResourceLocatorTest.php b/tests/lib/Template/JSResourceLocatorTest.php index e03599244eb4..adae95785407 100644 --- a/tests/lib/Template/JSResourceLocatorTest.php +++ b/tests/lib/Template/JSResourceLocatorTest.php @@ -39,6 +39,7 @@ public function getResourceLocator($theme, $core_map, $appsRoots) { $themeInstance->method('getName')->willReturn($theme); $themeInstance->method('getBaseDirectory')->willReturn($this->appRoot); $themeInstance->method('getDirectory')->willReturn($this->themeAppDir); + $themeInstance->method('getWebPath')->willReturn(''); $this->appManager = $this->getMockBuilder(AppManager::class) ->disableOriginalConstructor() @@ -127,7 +128,7 @@ public function testFindAppScriptWithNoThemeActive() { $locator->find(['randomapp/js/script']); } - public function testFindL10nScript() { + public function testFindL10nScript(): void { /** @var \OC\Template\JSResourceLocator $locator */ $locator = $this->getResourceLocator( 'theme', @@ -138,6 +139,10 @@ public function testFindL10nScript() { ->method('getAppPath') ->with('randomapp') ->willReturn('/var/www/apps/randomapp'); + $this->appManager->expects($this->any()) + ->method('getAppWebPath') + ->with('randomapp') + ->willReturn(''); $locator->expects($this->exactly(7)) ->method('appendOnceIfExist') diff --git a/tests/lib/legacy/AppTest.php b/tests/lib/legacy/AppTest.php index b3cdbe2c1b50..27b1695e2882 100644 --- a/tests/lib/legacy/AppTest.php +++ b/tests/lib/legacy/AppTest.php @@ -22,7 +22,6 @@ use OC\NavigationManager; use OCP\App\AppNotFoundException; -use function Test\AppFramework\rrmdir; use Test\TestCase; class AppTest extends TestCase { @@ -47,7 +46,7 @@ protected function tearDown(): void { $this->restoreService('NavigationManager'); \OC::$server->getAppManager()->clearAppsCache(); if (\is_dir($this->appPath)) { - rrmdir($this->appPath); + \Test\AppFramework\AppTest::rrmdir($this->appPath); } parent::tearDown(); } @@ -217,7 +216,7 @@ public function testGetAppInfoDeleted() { $info = \OC_App::getAppInfo('appinfotestapp'); $this->assertEqualsAppInfo($info); - rrmdir($this->appPath); + \Test\AppFramework\AppTest::rrmdir($this->appPath); try { \OC_App::getAppInfo('appinfotestapp'); diff --git a/tests/lib/legacy/HelperTest.php b/tests/lib/legacy/HelperTest.php index 90580933567b..f8df8f380033 100644 --- a/tests/lib/legacy/HelperTest.php +++ b/tests/lib/legacy/HelperTest.php @@ -7,10 +7,8 @@ class HelperTest extends TestCase { /** * @dataProvider getCleanedPathProvider - * @param string $original - * @param string $expected */ - public function testGetCleanedPath($original, $expected) { + public function testGetCleanedPath(string $original, string $expected): void { $this->assertSame($expected, \OC_Helper::getCleanedPath($original), 'Returned system path is not what was expected.'); } @@ -28,18 +26,6 @@ public function getCleanedPathProvider() { "", "/usr/local/bin /usr/bin /opt/bin /bin", ], - [ - null, - "/usr/local/bin /usr/bin /opt/bin /bin", - ], - [ - false, - "/usr/local/bin /usr/bin /opt/bin /bin", - ], - [ - false, - "/usr/local/bin /usr/bin /opt/bin /bin", - ], [ "-exec:whoami", "/usr/local/bin /usr/bin /opt/bin /bin", From c51c85011344285f81324c7eb81f362fb9911dbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Wed, 4 Oct 2023 12:53:45 +0200 Subject: [PATCH 24/42] fix: UserControllerTest --- lib/private/URLGenerator.php | 4 ++-- lib/public/IURLGenerator.php | 2 +- settings/Controller/UsersController.php | 2 +- tests/Settings/Controller/UsersControllerTest.php | 5 +++++ 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/private/URLGenerator.php b/lib/private/URLGenerator.php index a6367adea3e0..34abd493fc9e 100644 --- a/lib/private/URLGenerator.php +++ b/lib/private/URLGenerator.php @@ -96,7 +96,7 @@ public function linkToRoute($route, $parameters = []) { * * Returns an absolute url to the given route. */ - public function linkToRouteAbsolute($routeName, $arguments = []) { + public function linkToRouteAbsolute($routeName, $arguments = []): string { return $this->getAbsoluteURL($this->linkToRoute($routeName, $arguments)); } @@ -237,7 +237,7 @@ private function getImagePathOrFallback($file) { * @param string $url the url in the ownCloud host * @return string the absolute version of the url */ - public function getAbsoluteURL($url) { + public function getAbsoluteURL($url): string { $webRoot = $this->environmentHelper->getWebRoot(); $separator = $url[0] === '/' ? '' : '/'; diff --git a/lib/public/IURLGenerator.php b/lib/public/IURLGenerator.php index 481e5f60d1e2..76798c5aff6b 100644 --- a/lib/public/IURLGenerator.php +++ b/lib/public/IURLGenerator.php @@ -53,7 +53,7 @@ public function linkToRoute($routeName, $arguments = []); * @return string the absolute url * @since 8.0.0 */ - public function linkToRouteAbsolute($routeName, $arguments = []); + public function linkToRouteAbsolute($routeName, $arguments = []): string; /** * Returns an URL for an image or file diff --git a/settings/Controller/UsersController.php b/settings/Controller/UsersController.php index 76cfca776cf4..955c9e5ec07e 100644 --- a/settings/Controller/UsersController.php +++ b/settings/Controller/UsersController.php @@ -268,7 +268,7 @@ private function checkEmailChangeToken($token, $userId) { throw new \Exception($this->l10n->t('Couldn\'t change the email address because the user does not exist')); } - $splittedToken = \explode(':', $this->config->getUserValue($userId, 'owncloud', 'changeMail', null)); + $splittedToken = \explode(':', $this->config->getUserValue($userId, 'owncloud', 'changeMail', null) ?? ''); if (\count($splittedToken) !== 3) { $this->config->deleteUserValue($userId, 'owncloud', 'changeMail'); throw new \Exception($this->l10n->t('Couldn\'t change the email address because the token is invalid')); diff --git a/tests/Settings/Controller/UsersControllerTest.php b/tests/Settings/Controller/UsersControllerTest.php index ebe2b64b1462..bacb39f8231f 100644 --- a/tests/Settings/Controller/UsersControllerTest.php +++ b/tests/Settings/Controller/UsersControllerTest.php @@ -2144,6 +2144,11 @@ public function testSetEmailAddressSendEmail($id, $mailaddress): void { $iL10->expects($this->atLeastOnce()) ->method('t') ->willReturn('An email has been sent to this address for confirmation. Until the email is verified this address will not be set.'); + $iConfig + ->method('getUserValue') + ->with($id, 'owncloud', 'changeMail') + ->willReturn('12000:AVerySecretToken'); + $expectedResponse = new DataResponse( [ 'status' => 'success', From e6904259e1a1a8bad7daa3e87b1acda755b81b2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Wed, 4 Oct 2023 14:21:41 +0200 Subject: [PATCH 25/42] fix: owncloud log command tests --- tests/Core/Command/Log/OwnCloudTest.php | 40 +++++++++++++++---------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/tests/Core/Command/Log/OwnCloudTest.php b/tests/Core/Command/Log/OwnCloudTest.php index 91d988a7b05d..ed418bd01066 100644 --- a/tests/Core/Command/Log/OwnCloudTest.php +++ b/tests/Core/Command/Log/OwnCloudTest.php @@ -23,6 +23,7 @@ use OC\Core\Command\Log\OwnCloud; use Test\TestCase; +use OCP\IConfig; class OwnCloudTest extends TestCase { /** @var \PHPUnit\Framework\MockObject\MockObject */ @@ -38,7 +39,7 @@ class OwnCloudTest extends TestCase { protected function setUp(): void { parent::setUp(); - $config = $this->config = $this->getMockBuilder('OCP\IConfig') + $config = $this->config = $this->getMockBuilder(IConfig::class) ->disableOriginalConstructor() ->getMock(); $this->consoleInput = $this->createMock('Symfony\Component\Console\Input\InputInterface'); @@ -47,11 +48,14 @@ protected function setUp(): void { $this->command = new OwnCloud($config); } - public function testEnable() { + public function testEnable(): void { $this->consoleInput->method('getOption') - ->will($this->returnValueMap([ + ->willReturnMap([ ['enable', 'true'] - ])); + ]); + $this->config->expects(self::atLeastOnce()) + ->method('getSystemValue') + ->willReturnArgument(1); $this->config->expects($this->once()) ->method('setSystemValue') ->with('log_type', 'owncloud'); @@ -59,11 +63,14 @@ public function testEnable() { self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); } - public function testChangeFile() { + public function testChangeFile(): void { $this->consoleInput->method('getOption') - ->will($this->returnValueMap([ + ->willReturnMap([ ['file', '/foo/bar/file.log'] - ])); + ]); + $this->config->expects(self::atLeastOnce()) + ->method('getSystemValue') + ->willReturnArgument(1); $this->config->expects($this->once()) ->method('setSystemValue') ->with('logfile', '/foo/bar/file.log'); @@ -71,7 +78,7 @@ public function testChangeFile() { self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); } - public function changeRotateSizeProvider() { + public function changeRotateSizeProvider(): array { return [ ['42', 42], ['0', 0], @@ -83,11 +90,14 @@ public function changeRotateSizeProvider() { /** * @dataProvider changeRotateSizeProvider */ - public function testChangeRotateSize($optionValue, $configValue) { + public function testChangeRotateSize($optionValue, $configValue): void { $this->consoleInput->method('getOption') - ->will($this->returnValueMap([ + ->willReturnMap([ ['rotate-size', $optionValue] - ])); + ]); + $this->config->expects(self::atLeastOnce()) + ->method('getSystemValue') + ->willReturnArgument(1); $this->config->expects($this->once()) ->method('setSystemValue') ->with('log_rotate_size', $configValue); @@ -95,14 +105,14 @@ public function testChangeRotateSize($optionValue, $configValue) { self::invokePrivate($this->command, 'execute', [$this->consoleInput, $this->consoleOutput]); } - public function testGetConfiguration() { + public function testGetConfiguration(): void { $this->config->method('getSystemValue') - ->will($this->returnValueMap([ + ->willReturnMap([ ['log_type', 'owncloud', 'log_type_value'], - ['datadirectory', \OC::$SERVERROOT.'/data', '/data/directory/'], + ['datadirectory', \OC::$SERVERROOT . '/data', '/data/directory/'], ['logfile', '/data/directory/owncloud.log', '/var/log/owncloud.log'], ['log_rotate_size', 0, 5 * 1024 * 1024], - ])); + ]); $this->consoleOutput ->expects($this->exactly(3)) From 10826c8715defd7d1af2e10ffc82ef35a5041756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Wed, 4 Oct 2023 15:17:33 +0200 Subject: [PATCH 26/42] fix: more deprecation warnings --- apps/dav/lib/Connector/Sabre/Principal.php | 10 +- .../tests/unit/Connector/Sabre/FileTest.php | 14 +- .../unit/Connector/Sabre/PrincipalTest.php | 134 ++++++++++-------- apps/files_trashbin/tests/TestCase.php | 30 ++-- core/Command/User/ListUsers.php | 2 +- lib/private/AppFramework/Db/Db.php | 2 +- lib/private/DB/Connection.php | 2 +- lib/private/Files/View.php | 8 +- lib/public/IDBConnection.php | 2 +- .../Settings/Panels/Admin/FileSharingTest.php | 4 + 10 files changed, 112 insertions(+), 96 deletions(-) diff --git a/apps/dav/lib/Connector/Sabre/Principal.php b/apps/dav/lib/Connector/Sabre/Principal.php index c9022719a894..4942052e8103 100644 --- a/apps/dav/lib/Connector/Sabre/Principal.php +++ b/apps/dav/lib/Connector/Sabre/Principal.php @@ -97,10 +97,10 @@ public function getPrincipalsByPrefix($prefixPath) { * getPrincipalsByPrefix. * * @param string $path - * @return array + * @return array|null */ - public function getPrincipalByPath($path) { - list($prefix, $name) = \Sabre\Uri\split($path); + public function getPrincipalByPath($path): ?array { + [$prefix, $name] = \Sabre\Uri\split($path); if ($prefix === $this->principalPrefix) { $user = $this->userManager->get($name); @@ -219,12 +219,12 @@ public function findByUri($uri, $principalPrefix) { * @param IUser $user * @return array */ - protected function userToPrincipal($user) { + protected function userToPrincipal(IUser $user): array { $userId = $user->getUID(); $displayName = $user->getDisplayName(); $principal = [ 'uri' => $this->principalPrefix . '/' . $userId, - '{DAV:}displayname' => $displayName === null ? $userId : $displayName, + '{DAV:}displayname' => $displayName, ]; $email = $user->getEMailAddress(); diff --git a/apps/dav/tests/unit/Connector/Sabre/FileTest.php b/apps/dav/tests/unit/Connector/Sabre/FileTest.php index ef36896dd5cd..29c9d923201e 100644 --- a/apps/dav/tests/unit/Connector/Sabre/FileTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/FileTest.php @@ -348,11 +348,11 @@ public function testChunkedPutFails($thrownException, $expectedException, $check $view = $this->createMock(View::class, ['getRelativePath', 'resolvePath'], []); $view->expects($this->atLeastOnce()) ->method('resolvePath') - ->will($this->returnCallback( - function ($path) use ($storage) { - return [$storage, $path]; - } - )); + ->willReturnCallback(function ($path) use ($storage) { + return [$storage, $path]; + }); + $view->method('getAbsolutePath')->willReturnArgument(0); + $view->method('getRelativePath')->willReturnArgument(0); if ($thrownException !== null) { $storage->expects($this->once()) @@ -361,7 +361,7 @@ function ($path) use ($storage) { } else { $storage->expects($this->once()) ->method('fopen') - ->will($this->returnValue(false)); + ->willReturn(false); } $view->expects($this->any()) @@ -773,7 +773,7 @@ public function testPutOverwriteFileTriggersHooks() { * if the passed view was chrooted (can happen with public webdav * where the root is the share root) */ - public function testPutSingleFileTriggersHooksDifferentRoot() { + public function testPutSingleFileTriggersHooksDifferentRoot(): void { $view = Filesystem::getView(); $view->mkdir('noderoot'); diff --git a/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php b/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php index 7374792ac287..ecedf884193d 100644 --- a/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php @@ -24,67 +24,75 @@ namespace OCA\DAV\Tests\unit\Connector\Sabre; +use OCA\DAV\Connector\Sabre\Principal; use OCP\IGroupManager; use OCP\IUserManager; +use Sabre\DAV\Exception; use Sabre\DAV\PropPatch; use Test\TestCase; +use OC\User\User; +use OCP\IGroup; class PrincipalTest extends TestCase { /** @var IUserManager | \PHPUnit\Framework\MockObject\MockObject */ private $userManager; - /** @var \OCA\DAV\Connector\Sabre\Principal */ + /** @var Principal */ private $connector; /** @var IGroupManager | \PHPUnit\Framework\MockObject\MockObject */ private $groupManager; public function setUp(): void { - $this->userManager = $this->getMockBuilder('\OCP\IUserManager') + $this->userManager = $this->getMockBuilder(IUserManager::class) ->disableOriginalConstructor()->getMock(); - $this->groupManager = $this->getMockBuilder('\OCP\IGroupManager') + $this->groupManager = $this->getMockBuilder(IGroupManager::class) ->disableOriginalConstructor()->getMock(); - $this->connector = new \OCA\DAV\Connector\Sabre\Principal( + $this->connector = new Principal( $this->userManager, $this->groupManager ); parent::setUp(); } - public function testGetPrincipalsByPrefixWithoutPrefix() { + public function testGetPrincipalsByPrefixWithoutPrefix(): void { $response = $this->connector->getPrincipalsByPrefix(''); $this->assertSame([], $response); } - public function testGetPrincipalsByPrefixWithUsers() { - $fooUser = $this->getMockBuilder('\OC\User\User') + public function testGetPrincipalsByPrefixWithUsers(): void { + $fooUser = $this->getMockBuilder(User::class) ->disableOriginalConstructor()->getMock(); $fooUser - ->expects($this->exactly(1)) + ->expects($this->once()) ->method('getUID') - ->will($this->returnValue('foo')); + ->willReturn('foo'); $fooUser - ->expects($this->exactly(1)) + ->expects($this->once()) ->method('getDisplayName') - ->will($this->returnValue('Dr. Foo-Bar')); + ->willReturn('Dr. Foo-Bar'); $fooUser - ->expects($this->exactly(1)) + ->expects($this->once()) ->method('getEMailAddress') - ->will($this->returnValue('')); - $barUser = $this->getMockBuilder('\OC\User\User') + ->willReturn(''); + $barUser = $this->getMockBuilder(User::class) ->disableOriginalConstructor()->getMock(); $barUser - ->expects($this->exactly(1)) + ->expects($this->once()) ->method('getUID') - ->will($this->returnValue('bar')); + ->willReturn('bar'); + $barUser + ->expects($this->once()) + ->method('getDisplayName') + ->willReturn('bar'); $barUser - ->expects($this->exactly(1)) + ->expects($this->once()) ->method('getEMailAddress') - ->will($this->returnValue('bar@owncloud.com')); + ->willReturn('bar@owncloud.com'); $this->userManager ->expects($this->once()) ->method('search') ->with('') - ->will($this->returnValue([$fooUser, $barUser])); + ->willReturn([$fooUser, $barUser]); $expectedResponse = [ 0 => [ @@ -101,29 +109,33 @@ public function testGetPrincipalsByPrefixWithUsers() { $this->assertSame($expectedResponse, $response); } - public function testGetPrincipalsByPrefixEmpty() { + public function testGetPrincipalsByPrefixEmpty(): void { $this->userManager ->expects($this->once()) ->method('search') ->with('') - ->will($this->returnValue([])); + ->willReturn([]); $response = $this->connector->getPrincipalsByPrefix('principals/users'); $this->assertSame([], $response); } - public function testGetPrincipalsByPathWithoutMail() { - $fooUser = $this->getMockBuilder('\OC\User\User') + public function testGetPrincipalsByPathWithoutMail(): void { + $fooUser = $this->getMockBuilder(User::class) ->disableOriginalConstructor()->getMock(); $fooUser - ->expects($this->exactly(1)) + ->expects($this->once()) ->method('getUID') - ->will($this->returnValue('foo')); + ->willReturn('foo'); + $fooUser + ->expects($this->once()) + ->method('getDisplayname') + ->willReturn('foo'); $this->userManager ->expects($this->once()) ->method('get') ->with('foo') - ->will($this->returnValue($fooUser)); + ->willReturn($fooUser); $expectedResponse = [ 'uri' => 'principals/users/foo', @@ -133,22 +145,26 @@ public function testGetPrincipalsByPathWithoutMail() { $this->assertSame($expectedResponse, $response); } - public function testGetPrincipalsByPathWithMail() { - $fooUser = $this->getMockBuilder('\OC\User\User') + public function testGetPrincipalsByPathWithMail(): void { + $fooUser = $this->getMockBuilder(User::class) ->disableOriginalConstructor()->getMock(); $fooUser - ->expects($this->exactly(1)) + ->expects($this->once()) ->method('getEMailAddress') - ->will($this->returnValue('foo@owncloud.com')); + ->willReturn('foo@owncloud.com'); $fooUser - ->expects($this->exactly(1)) - ->method('getUID') - ->will($this->returnValue('foo')); + ->expects($this->once()) + ->method('getUID') + ->willReturn('foo'); + $fooUser + ->expects($this->once()) + ->method('getDisplayName') + ->willReturn('foo'); $this->userManager ->expects($this->once()) ->method('get') ->with('foo') - ->will($this->returnValue($fooUser)); + ->willReturn($fooUser); $expectedResponse = [ 'uri' => 'principals/users/foo', @@ -159,29 +175,29 @@ public function testGetPrincipalsByPathWithMail() { $this->assertSame($expectedResponse, $response); } - public function testGetPrincipalsByPathEmpty() { + public function testGetPrincipalsByPathEmpty(): void { $this->userManager ->expects($this->once()) ->method('get') ->with('foo') - ->will($this->returnValue(null)); + ->willReturn(null); $response = $this->connector->getPrincipalByPath('principals/users/foo'); $this->assertNull($response); } - public function testGetGroupMemberSet() { - $fooUser = $this->getMockBuilder('\OC\User\User') + public function testGetGroupMemberSet(): void { + $fooUser = $this->getMockBuilder(User::class) ->disableOriginalConstructor()->getMock(); $fooUser - ->expects($this->exactly(1)) + ->expects($this->once()) ->method('getUID') - ->will($this->returnValue('foo')); + ->willReturn('foo'); $this->userManager ->expects($this->once()) ->method('get') ->with('foo') - ->will($this->returnValue($fooUser)); + ->willReturn($fooUser); $response = $this->connector->getGroupMemberSet('principals/users/foo'); $this->assertSame(['principals/users/foo'], $response); @@ -189,23 +205,23 @@ public function testGetGroupMemberSet() { /** */ - public function testGetGroupMemberSetEmpty() { - $this->expectException(\Sabre\DAV\Exception::class); + public function testGetGroupMemberSetEmpty(): void { + $this->expectException(Exception::class); $this->expectExceptionMessage('Principal not found'); $this->userManager ->expects($this->once()) ->method('get') ->with('foo') - ->will($this->returnValue(null)); + ->willReturn(null); $this->connector->getGroupMemberSet('principals/users/foo'); } - public function testGetGroupMembership() { - $fooUser = $this->getMockBuilder('\OC\User\User') + public function testGetGroupMembership(): void { + $fooUser = $this->getMockBuilder(User::class) ->disableOriginalConstructor()->getMock(); - $group = $this->getMockBuilder('\OCP\IGroup') + $group = $this->getMockBuilder(IGroup::class) ->disableOriginalConstructor()->getMock(); $group->expects($this->once()) ->method('getGID') @@ -229,45 +245,43 @@ public function testGetGroupMembership() { $this->assertSame($expectedResponse, $response); } - /** - */ - public function testGetGroupMembershipEmpty() { - $this->expectException(\Sabre\DAV\Exception::class); + public function testGetGroupMembershipEmpty(): void { + $this->expectException(Exception::class); $this->expectExceptionMessage('Principal not found'); $this->userManager ->expects($this->once()) ->method('get') ->with('foo') - ->will($this->returnValue(null)); + ->willReturn(null); $this->connector->getGroupMembership('principals/users/foo'); } /** */ - public function testSetGroupMembership() { - $this->expectException(\Sabre\DAV\Exception::class); + public function testSetGroupMembership(): void { + $this->expectException(Exception::class); $this->expectExceptionMessage('Setting members of the group is not supported yet'); $this->connector->setGroupMemberSet('principals/users/foo', ['foo']); } - public function testUpdatePrincipal() { + public function testUpdatePrincipal(): void { $this->assertSame(0, $this->connector->updatePrincipal('foo', new PropPatch([]))); } - public function testSearchPrincipals() { + public function testSearchPrincipals(): void { $this->assertSame([], $this->connector->searchPrincipals('principals/users', [])); } - public function testFindByUri() { - $fooUser = $this->getMockBuilder('\OC\User\User') + public function testFindByUri(): void { + $fooUser = $this->getMockBuilder(User::class) ->disableOriginalConstructor()->getMock(); $fooUser - ->expects($this->exactly(1)) + ->expects($this->once()) ->method('getUID') - ->will($this->returnValue('foo')); + ->willReturn('foo'); $this->userManager->expects($this->once())->method('getByEmail')->willReturn([ $fooUser diff --git a/apps/files_trashbin/tests/TestCase.php b/apps/files_trashbin/tests/TestCase.php index 08ac9738006b..a2d6bac970f9 100644 --- a/apps/files_trashbin/tests/TestCase.php +++ b/apps/files_trashbin/tests/TestCase.php @@ -21,12 +21,13 @@ namespace OCA\Files_Trashbin\Tests; -use OC\Files\Cache\Watcher; use OC\Files\Filesystem; use OC\Files\View; use OCA\Files_Sharing\AppInfo\Application; use OCA\Files_Trashbin\Expiration; use OCA\Files_Trashbin\Trashbin; +use OCP\Files\Cache\IWatcher; +use OCP\IConfig; /** * Class TrashbinTestCase @@ -83,13 +84,9 @@ public static function setUpBeforeClass(): void { public static function tearDownAfterClass(): void { // cleanup test user $user = \OC::$server->getUserManager()->get(self::TEST_TRASHBIN_USER1); - if ($user !== null) { - $user->delete(); - } + $user?->delete(); $user = \OC::$server->getUserManager()->get(self::TEST_TRASHBIN_USER2); - if ($user !== null) { - $user->delete(); - } + $user?->delete(); \OC::$server->getConfig()->setSystemValue('trashbin_retention_obligation', self::$rememberRetentionObligation); @@ -109,16 +106,17 @@ protected function setUp(): void { \OC::$server->getAppManager()->enableApp('files_trashbin'); $config = \OC::$server->getConfig(); - $mockConfig = $this->createMock('\OCP\IConfig'); - $mockConfig->expects($this->any()) + $mockConfig = $this->createMock(IConfig::class); + $mockConfig ->method('getSystemValue') - ->will($this->returnCallback(function ($key, $default) use ($config) { + ->willReturnCallback(function ($key, $default) use ($config) { if ($key === 'filesystem_check_changes') { - return Watcher::CHECK_ONCE; - } else { - return $config->getSystemValue($key, $default); + return IWatcher::CHECK_ONCE; } - })); + + return $config->getSystemValue($key, $default); + }); + $mockConfig->method('getUserValue')->willReturnArgument(2); $this->overwriteService('AllConfig', $mockConfig); $this->trashRoot1 = '/' . self::TEST_TRASHBIN_USER1 . '/files_trashbin'; @@ -138,7 +136,7 @@ protected function tearDown(): void { // clear trash table $connection = \OC::$server->getDatabaseConnection(); - $connection->executeUpdate('DELETE FROM `*PREFIX*files_trash`'); + $connection->executeStatement('DELETE FROM `*PREFIX*files_trash`'); parent::tearDown(); } @@ -147,7 +145,7 @@ protected function tearDown(): void { * @param string $user * @param bool $create */ - protected static function loginHelper($user, $create = false) { + protected static function loginHelper($user, $create = false): void { if ($create) { try { \OC::$server->getUserManager()->createUser($user, $user); diff --git a/core/Command/User/ListUsers.php b/core/Command/User/ListUsers.php index ac1277f74623..7412271cfdc9 100644 --- a/core/Command/User/ListUsers.php +++ b/core/Command/User/ListUsers.php @@ -99,7 +99,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $showAllAttributes = $input->getOption('show-all-attributes'); $useKey = \count($attributes) > 1 || $input->getOption('output') !== self::OUTPUT_FORMAT_PLAIN; - $users = $this->userManager->search($userNameSubString, null, null, true); + $users = $this->userManager->search($userNameSubString ?? '', null, null, true); $users = \array_map(function (IUser $user) use ($output, $attributes, $useKey, $showAllAttributes) { if ($output->isVerbose() || $showAllAttributes) { // include all attributes diff --git a/lib/private/AppFramework/Db/Db.php b/lib/private/AppFramework/Db/Db.php index 296e0c3e1ccd..5ba050722443 100644 --- a/lib/private/AppFramework/Db/Db.php +++ b/lib/private/AppFramework/Db/Db.php @@ -234,7 +234,7 @@ public function tableExists($table) { /** * @inheritdoc */ - public function escapeLikeParameter($param) { + public function escapeLikeParameter(string $param): string { return $this->connection->escapeLikeParameter($param); } diff --git a/lib/private/DB/Connection.php b/lib/private/DB/Connection.php index 3d2c74666473..c1a64f1a91ca 100644 --- a/lib/private/DB/Connection.php +++ b/lib/private/DB/Connection.php @@ -440,7 +440,7 @@ public function inTransaction() { * @param string $param * @return string */ - public function escapeLikeParameter($param) { + public function escapeLikeParameter(string $param): string { return \addcslashes($param, '\\_%'); } diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php index 4aa5581c3939..f46b2213023d 100644 --- a/lib/private/Files/View.php +++ b/lib/private/Files/View.php @@ -388,15 +388,15 @@ public function rmdir($path) { * * @return bool */ - protected function isShareFolderOrShareFolderParent($path) { - $shareFolder = \trim($this->config->getSystemValue('share_folder', '/'), '/'); + protected function isShareFolderOrShareFolderParent($path): bool { + $shareFolder = \trim($this->config->getSystemValue('share_folder', '/') ?? '', '/'); if ($shareFolder === '') { return false; } $user = \OC_User::getUser(); $shareFolderAbsolutePath = "/$user/files/$shareFolder"; $trimmedAbsolutePath = $this->getAbsolutePath(\trim($path, '/')); - return $shareFolderAbsolutePath === $trimmedAbsolutePath || \strpos($shareFolderAbsolutePath, "$trimmedAbsolutePath/") === 0; + return $shareFolderAbsolutePath === $trimmedAbsolutePath || \str_starts_with($shareFolderAbsolutePath, "$trimmedAbsolutePath/"); } /** @@ -1439,7 +1439,7 @@ public function getFileInfo($path, $includeMountPoints = true) { if (!Filesystem::isValidPath($path)) { return false; } - if (Cache\Scanner::isPartialFile($path)) { + if (Cache\Scanner::isPartialFile($path ?? '')) { return $this->getPartFileInfo($path); } $relativePath = $path; diff --git a/lib/public/IDBConnection.php b/lib/public/IDBConnection.php index c5fe1c32f5a6..6b24df991d56 100644 --- a/lib/public/IDBConnection.php +++ b/lib/public/IDBConnection.php @@ -299,7 +299,7 @@ public function tableExists($table); * @return string * @since 9.0.0 */ - public function escapeLikeParameter($param); + public function escapeLikeParameter(string $param): string; /** * Create the schema of the connected database diff --git a/tests/Settings/Panels/Admin/FileSharingTest.php b/tests/Settings/Panels/Admin/FileSharingTest.php index e08644c8860f..efd642c5cbf5 100644 --- a/tests/Settings/Panels/Admin/FileSharingTest.php +++ b/tests/Settings/Panels/Admin/FileSharingTest.php @@ -30,6 +30,10 @@ class FileSharingTest extends \Test\TestCase { public function setUp(): void { parent::setUp(); $this->config = $this->getMockBuilder(IConfig::class)->getMock(); + # $excludedGroupsList = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '') ?? ''; + + $this->config->method('getAppValue')->willReturnArgument(2); + $this->helper = $this->getMockBuilder(Helper::class)->getMock(); $l = $this->createMock(IL10N::class); From 28280ca76b83c13fbc5897fb0f75dae7b88892af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Wed, 4 Oct 2023 17:17:28 +0200 Subject: [PATCH 27/42] chore: :zzz: --- apps/files_versions/tests/VersioningTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/files_versions/tests/VersioningTest.php b/apps/files_versions/tests/VersioningTest.php index eb4b0ea67524..037f707fe79f 100644 --- a/apps/files_versions/tests/VersioningTest.php +++ b/apps/files_versions/tests/VersioningTest.php @@ -54,7 +54,6 @@ use OCP\IConfig; use OCP\Util; use PHPUnit\Framework\MockObject\MockObject; -use ReflectionClass; use Test\TestCase; use function basename; use function file_exists; @@ -178,6 +177,7 @@ private function overwriteConfig($saveVersionMetadata=false, $versionsRetentionO return $config->getSystemValue($key, $default); }); + $this->mockConfig->method('getAppValue')->willReturnArgument(2); $this->overwriteService('AllConfig', $this->mockConfig); @@ -1407,7 +1407,7 @@ private function createAndCheckVersions(View $view, $path): void { * @param string $user * @param bool $create */ - public static function loginHelper($user, $create = false): void { + public static function loginHelper(string $user, bool $create = false): void { if ($create) { OC::$server->getUserManager()->createUser($user, $user); } From c7ecd66aec8e5de213cedff337ab1fd47b26f4ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Fri, 20 Jan 2023 10:43:32 +0100 Subject: [PATCH 28/42] chore: migrate to symfony/mailer chore: remove email config `mail_smtpauthtype` - this is now auto detected fix: Mailer::send will always thrown an exception in case of errors during delivery --- apps/dav/lib/CalDAV/Schedule/IMipPlugin.php | 23 +- .../unit/CalDAV/Schedule/IMipPluginTest.php | 155 ++++------ composer.json | 4 +- composer.lock | 290 ++++++++++++------ composer.phar | Bin 0 -> 2192976 bytes config/config.sample.php | 9 +- lib/private/Mail/Logger.php | 20 ++ lib/private/Mail/Mailer.php | 154 +++++----- lib/private/Mail/Message.php | 171 ++++------- lib/public/Mail/IMailer.php | 6 +- .../Controller/MailSettingsController.php | 47 ++- settings/Panels/Admin/Mail.php | 1 - settings/templates/panels/admin/mail.php | 18 -- .../Controller/MailSettingsControllerTest.php | 130 +++----- .../Controller/UsersControllerTest.php | 4 + .../features/lib/AdminGeneralSettingsPage.php | 3 - tests/lib/Mail/MailerTest.php | 58 ++-- tests/lib/Mail/MessageTest.php | 160 ++-------- 18 files changed, 543 insertions(+), 710 deletions(-) create mode 100755 composer.phar create mode 100644 lib/private/Mail/Logger.php diff --git a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php index ffe6930fdb4a..4fcbc95e535e 100644 --- a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php +++ b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php @@ -41,21 +41,12 @@ * @license http://sabre.io/license/ Modified BSD License */ class IMipPlugin extends SabreIMipPlugin { - /** @var IMailer */ - private $mailer; - - /** @var ILogger */ - private $logger; - - /** @var IRequest */ - private $request; + private IMailer $mailer; + private ILogger $logger; + private IRequest $request; /** * Creates the email handler. - * - * @param IMailer $mailer - * @param ILogger $logger - * @param IRequest $request */ public function __construct(IMailer $mailer, ILogger $logger, IRequest $request) { parent::__construct(''); @@ -123,14 +114,10 @@ public function schedule(ITip\Message $iTipMessage) { ->setFrom([$sender => $senderName]) ->setTo([$recipient => $recipientName]) ->setSubject($subject) - ->setBody($iTipMessage->message->serialize(), $contentType); + ->attach($iTipMessage->message->serialize(), "event.ics", $contentType); try { - $failed = $this->mailer->send($message); + $this->mailer->send($message); $iTipMessage->scheduleStatus = '1.1; Scheduling message is sent via iMip'; - if ($failed) { - $this->logger->error('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' => \implode(', ', $failed)]); - $iTipMessage->scheduleStatus = '5.0; EMail delivery failed'; - } } catch (\Exception $ex) { $this->logger->logException($ex, ['app' => 'dav']); $iTipMessage->scheduleStatus = '5.0; EMail delivery failed'; diff --git a/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php b/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php index c1902456caab..534cd97253e0 100644 --- a/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php +++ b/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php @@ -22,124 +22,78 @@ namespace OCA\DAV\Tests\unit\CalDAV\Schedule; +use Exception; use OC\Mail\Mailer; use OCA\DAV\CalDAV\Schedule\IMipPlugin; use OCP\ILogger; use OCP\IRequest; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\VObject\Component\VCalendar; use Sabre\VObject\ITip\Message; +use Symfony\Component\Mime\Email; use Test\TestCase; use OC\Log; class IMipPluginTest extends TestCase { - public function testDelivery() { - $mailMessage = new \OC\Mail\Message(new \Swift_Message()); - /** @var Mailer | \PHPUnit\Framework\MockObject\MockObject $mailer */ - $mailer = $this->createMock(Mailer::class); - $mailer->method('createMessage')->willReturn($mailMessage); - $mailer->expects($this->once())->method('send'); - /** @var ILogger | \PHPUnit\Framework\MockObject\MockObject $logger */ - $logger = $this->createMock(Log::class); - /** @var IRequest| \PHPUnit\Framework\MockObject\MockObject $request */ + private \OC\Mail\Message $mailMessage; + /** + * @var Mailer|MockObject + */ + private $mailer; + private IMipPlugin $plugin; + /** @var ILogger|MockObject */ + private $logger; + + protected function setUp(): void { + parent::setUp(); + + $this->mailMessage = new \OC\Mail\Message(new Email()); + $this->mailer = $this->createMock(Mailer::class); + $this->mailer->method('createMessage')->willReturn($this->mailMessage); + + $this->logger = $this->createMock(Log::class); + /** @var IRequest| MockObject $request */ $request = $this->createMock(IRequest::class); - $plugin = new IMipPlugin($mailer, $logger, $request); - $message = new Message(); - $message->method = 'REQUEST'; - $message->message = new VCalendar(); - $message->message->add('VEVENT', [ - 'UID' => $message->uid, - 'SEQUENCE' => $message->sequence, - 'SUMMARY' => 'Fellowship meeting', - ]); - $message->sender = 'mailto:gandalf@wiz.ard'; - $message->recipient = 'mailto:frodo@hobb.it'; + $this->plugin = new IMipPlugin($this->mailer, $this->logger, $request); + } + + public function testDelivery(): void { + $this->mailer->expects($this->once())->method('send'); + + $message = $this->buildIMIPMessage('REQUEST'); - $plugin->schedule($message); + $this->plugin->schedule($message); $this->assertEquals('1.1', $message->getScheduleStatus()); - $this->assertEquals('Fellowship meeting', $mailMessage->getSubject()); - $this->assertEquals(['frodo@hobb.it' => null], $mailMessage->getTo()); - $this->assertEquals(['gandalf@wiz.ard' => null], $mailMessage->getReplyTo()); - $this->assertEquals('text/calendar; charset=UTF-8; method=REQUEST', $mailMessage->getSwiftMessage()->getContentType()); + $this->assertEquals('Fellowship meeting', $this->mailMessage->getSubject()); + $this->assertEquals(['frodo@hobb.it' => null], $this->mailMessage->getTo()); + $this->assertEquals(['gandalf@wiz.ard' => null], $this->mailMessage->getReplyTo()); + $this->assertStringContainsString('text/calendar; charset=UTF-8; method=REQUEST', $this->mailMessage->getMessage()->getBody()->bodyToString()); } - public function testFailedDeliveryWithException() { - $mailMessage = new \OC\Mail\Message(new \Swift_Message()); - /** @var Mailer | \PHPUnit\Framework\MockObject\MockObject $mailer */ - $mailer = $this->createMock(Mailer::class); - $mailer->method('createMessage')->willReturn($mailMessage); - $mailer->method('send')->willThrowException(new \Exception()); - /** @var ILogger | \PHPUnit\Framework\MockObject\MockObject $logger */ - $logger = $this->createMock(Log::class); - /** @var IRequest| \PHPUnit\Framework\MockObject\MockObject $request */ - $request = $this->createMock(IRequest::class); + public function testFailedDeliveryWithException(): void { + $ex = new Exception(); + $this->mailer->method('send')->willThrowException($ex); + $this->logger->expects(self::once())->method('logException')->with($ex, ['app' => 'dav']); - $plugin = new IMipPlugin($mailer, $logger, $request); - $message = new Message(); - $message->method = 'REQUEST'; - $message->message = new VCalendar(); - $message->message->add('VEVENT', [ - 'UID' => $message->uid, - 'SEQUENCE' => $message->sequence, - 'SUMMARY' => 'Fellowship meeting', - ]); - $message->sender = 'mailto:gandalf@wiz.ard'; - $message->recipient = 'mailto:frodo@hobb.it'; + $message = $this->buildIMIPMessage('REQUEST'); - $plugin->schedule($message); - $this->assertEquals('5.0', $message->getScheduleStatus()); - $this->assertEquals('Fellowship meeting', $mailMessage->getSubject()); - $this->assertEquals(['frodo@hobb.it' => null], $mailMessage->getTo()); - $this->assertEquals(['gandalf@wiz.ard' => null], $mailMessage->getReplyTo()); - $this->assertEquals('text/calendar; charset=UTF-8; method=REQUEST', $mailMessage->getSwiftMessage()->getContentType()); + $this->plugin->schedule($message); + $this->assertIMipState($message, '5.0', 'REQUEST', 'Fellowship meeting'); } - public function testFailedDelivery() { - $mailMessage = new \OC\Mail\Message(new \Swift_Message()); - /** @var Mailer | \PHPUnit\Framework\MockObject\MockObject $mailer */ - $mailer = $this->createMock(Mailer::class); - $mailer->method('createMessage')->willReturn($mailMessage); - $mailer->method('send')->willReturn(['foo@example.net']); - /** @var ILogger | \PHPUnit\Framework\MockObject\MockObject $logger */ - $logger = $this->createMock(Log::class); - $logger->expects(self::once())->method('error')->with('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' => 'foo@example.net']); - /** @var IRequest| \PHPUnit\Framework\MockObject\MockObject $request */ - $request = $this->createMock(IRequest::class); + public function testDeliveryOfCancel(): void { + $this->mailer->expects($this->once())->method('send'); - $plugin = new IMipPlugin($mailer, $logger, $request); - $message = new Message(); - $message->method = 'REQUEST'; - $message->message = new VCalendar(); - $message->message->add('VEVENT', [ - 'UID' => $message->uid, - 'SEQUENCE' => $message->sequence, - 'SUMMARY' => 'Fellowship meeting', - ]); - $message->sender = 'mailto:gandalf@wiz.ard'; - $message->recipient = 'mailto:frodo@hobb.it'; + $message = $this->buildIMIPMessage('CANCEL'); - $plugin->schedule($message); - $this->assertEquals('5.0', $message->getScheduleStatus()); - $this->assertEquals('Fellowship meeting', $mailMessage->getSubject()); - $this->assertEquals(['frodo@hobb.it' => null], $mailMessage->getTo()); - $this->assertEquals(['gandalf@wiz.ard' => null], $mailMessage->getReplyTo()); - $this->assertEquals('text/calendar; charset=UTF-8; method=REQUEST', $mailMessage->getSwiftMessage()->getContentType()); + $this->plugin->schedule($message); + $this->assertIMipState($message, '1.1', 'CANCEL', 'Cancelled: Fellowship meeting'); } - public function testDeliveryOfCancel() { - $mailMessage = new \OC\Mail\Message(new \Swift_Message()); - /** @var Mailer | \PHPUnit\Framework\MockObject\MockObject $mailer */ - $mailer = $this->createMock(Mailer::class); - $mailer->method('createMessage')->willReturn($mailMessage); - $mailer->expects($this->once())->method('send'); - /** @var ILogger | \PHPUnit\Framework\MockObject\MockObject $logger */ - $logger = $this->createMock(Log::class); - /** @var IRequest| \PHPUnit\Framework\MockObject\MockObject $request */ - $request = $this->createMock(IRequest::class); - - $plugin = new IMipPlugin($mailer, $logger, $request); + private function buildIMIPMessage(string $method): Message { $message = new Message(); - $message->method = 'CANCEL'; + $message->method = $method; $message->message = new VCalendar(); $message->message->add('VEVENT', [ 'UID' => $message->uid, @@ -148,13 +102,14 @@ public function testDeliveryOfCancel() { ]); $message->sender = 'mailto:gandalf@wiz.ard'; $message->recipient = 'mailto:frodo@hobb.it'; + return $message; + } - $plugin->schedule($message); - $this->assertEquals('1.1', $message->getScheduleStatus()); - $this->assertEquals('Cancelled: Fellowship meeting', $mailMessage->getSubject()); - $this->assertEquals(['frodo@hobb.it' => null], $mailMessage->getTo()); - $this->assertEquals(['gandalf@wiz.ard' => null], $mailMessage->getReplyTo()); - $this->assertEquals('text/calendar; charset=UTF-8; method=CANCEL', $mailMessage->getSwiftMessage()->getContentType()); - $this->assertEquals('CANCELLED', $message->message->VEVENT->STATUS->getValue()); + private function assertIMipState(Message $message, string $scheduleStatus, string $method, string $mailSubject): void { + $this->assertEquals($scheduleStatus, $message->getScheduleStatus()); + $this->assertEquals($mailSubject, $this->mailMessage->getSubject()); + $this->assertEquals(['frodo@hobb.it' => null], $this->mailMessage->getTo()); + $this->assertEquals(['gandalf@wiz.ard' => null], $this->mailMessage->getReplyTo()); + $this->assertStringContainsString("text/calendar; charset=UTF-8; method=$method", $this->mailMessage->getMessage()->getBody()->bodyToString()); } } diff --git a/composer.json b/composer.json index 12f76fad6074..34cc19a45ed3 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,7 @@ "roave/security-advisories": "dev-latest" }, "require": { - "php": ">=8.2", + "php": ">=8.2", "ext-apcu": "*", "ext-ctype": "*", "ext-curl": "*", @@ -91,9 +91,9 @@ "sabre/dav": "^4.4", "sabre/http": "^5.1", "sabre/vobject": "^4.5", - "swiftmailer/swiftmailer": "^6.3", "symfony/console": "^5.4", "symfony/event-dispatcher": "^5.4", + "symfony/mailer": "^5.4", "symfony/process": "^5.4", "symfony/routing": "^5.4", "symfony/translation": "^5.4" diff --git a/composer.lock b/composer.lock index 1cdc0ef95382..8eae6e0c5f2b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9a78206698bfb4ba4dcf1c35c9509e5d", + "content-hash": "1d84a0f8b8dd3428e4a98a523b191464", "packages": [ { "name": "bantu/ini-get-wrapper", @@ -547,28 +547,27 @@ }, { "name": "doctrine/lexer", - "version": "2.1.1", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6" + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6", - "reference": "861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", "shasum": "" }, "require": { - "doctrine/deprecations": "^1.0", - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^12", - "phpstan/phpstan": "^1.3", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6", + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", "psalm/plugin-phpunit": "^0.18.3", - "vimeo/psalm": "^4.11 || ^5.21" + "vimeo/psalm": "^5.21" }, "type": "library", "autoload": { @@ -605,7 +604,7 @@ ], "support": { "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/2.1.1" + "source": "https://github.com/doctrine/lexer/tree/3.0.1" }, "funding": [ { @@ -621,30 +620,30 @@ "type": "tidelift" } ], - "time": "2024-02-05T11:35:39+00:00" + "time": "2024-02-05T11:56:58+00:00" }, { "name": "egulias/email-validator", - "version": "3.2.6", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "e5997fa97e8790cdae03a9cbd5e78e45e3c7bda7" + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/e5997fa97e8790cdae03a9cbd5e78e45e3c7bda7", - "reference": "e5997fa97e8790cdae03a9cbd5e78e45e3c7bda7", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", "shasum": "" }, "require": { - "doctrine/lexer": "^1.2|^2", - "php": ">=7.2", - "symfony/polyfill-intl-idn": "^1.15" + "doctrine/lexer": "^2.0 || ^3.0", + "php": ">=8.1", + "symfony/polyfill-intl-idn": "^1.26" }, "require-dev": { - "phpunit/phpunit": "^8.5.8|^9.3.3", - "vimeo/psalm": "^4" + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" }, "suggest": { "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" @@ -652,7 +651,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "4.0.x-dev" } }, "autoload": { @@ -680,7 +679,7 @@ ], "support": { "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/3.2.6" + "source": "https://github.com/egulias/EmailValidator/tree/4.0.4" }, "funding": [ { @@ -688,7 +687,7 @@ "type": "github" } ], - "time": "2023-06-01T07:04:22+00:00" + "time": "2025-03-06T22:45:56+00:00" }, { "name": "firebase/php-jwt", @@ -3681,82 +3680,6 @@ }, "time": "2024-09-06T07:37:46+00:00" }, - { - "name": "swiftmailer/swiftmailer", - "version": "v6.3.0", - "source": { - "type": "git", - "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "8a5d5072dca8f48460fce2f4131fcc495eec654c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/8a5d5072dca8f48460fce2f4131fcc495eec654c", - "reference": "8a5d5072dca8f48460fce2f4131fcc495eec654c", - "shasum": "" - }, - "require": { - "egulias/email-validator": "^2.0|^3.1", - "php": ">=7.0.0", - "symfony/polyfill-iconv": "^1.0", - "symfony/polyfill-intl-idn": "^1.10", - "symfony/polyfill-mbstring": "^1.0" - }, - "require-dev": { - "mockery/mockery": "^1.0", - "symfony/phpunit-bridge": "^4.4|^5.4" - }, - "suggest": { - "ext-intl": "Needed to support internationalized email addresses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "6.2-dev" - } - }, - "autoload": { - "files": [ - "lib/swift_required.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Chris Corbyn" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Swiftmailer, free feature-rich PHP mailer", - "homepage": "https://swiftmailer.symfony.com", - "keywords": [ - "email", - "mail", - "mailer" - ], - "support": { - "issues": "https://github.com/swiftmailer/swiftmailer/issues", - "source": "https://github.com/swiftmailer/swiftmailer/tree/v6.3.0" - }, - "funding": [ - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/swiftmailer/swiftmailer", - "type": "tidelift" - } - ], - "abandoned": "symfony/mailer", - "time": "2021-10-18T15:26:12+00:00" - }, { "name": "symfony/console", "version": "v5.4.47", @@ -4087,6 +4010,171 @@ ], "time": "2024-09-25T14:11:13+00:00" }, + { + "name": "symfony/mailer", + "version": "v5.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/mailer.git", + "reference": "f732e1fafdf0f4a2d865e91f1018aaca174aeed9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mailer/zipball/f732e1fafdf0f4a2d865e91f1018aaca174aeed9", + "reference": "f732e1fafdf0f4a2d865e91f1018aaca174aeed9", + "shasum": "" + }, + "require": { + "egulias/email-validator": "^2.1.10|^3|^4", + "php": ">=7.2.5", + "psr/event-dispatcher": "^1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/mime": "^5.2.6|^6.0", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3" + }, + "conflict": { + "symfony/http-kernel": "<4.4" + }, + "require-dev": { + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/messenger": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mailer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps sending emails", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/mailer/tree/v5.4.45" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:11:13+00:00" + }, + { + "name": "symfony/mime", + "version": "v6.4.32", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "7409686879ca36c09fc970a5fa8ff6e93504dba4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/7409686879ca36c09fc970a5fa8ff6e93504dba4", + "reference": "7409686879ca36c09fc970a5fa8ff6e93504dba4", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/mailer": "<5.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.4|^7.0", + "symfony/property-access": "^5.4|^6.0|^7.0", + "symfony/property-info": "^5.4|^6.0|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v6.4.32" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-04T11:53:14+00:00" + }, { "name": "symfony/polyfill-php80", "version": "v1.33.0", diff --git a/composer.phar b/composer.phar new file mode 100755 index 0000000000000000000000000000000000000000..553efcce527c28adee3c95b3ad3a0f286062af42 GIT binary patch literal 2192976 zcmdqK31H+^kw1A~2t7$BX-K+5cV}h- zGp-k2tm1mDg08rD;I&?Oi;K&8{Jd{jWp{O5@5NQuTUP&{Pra)5ecz)ynStH^@Bbe$ z)!*aQtEyM8UcGwt-uJX6#koekxTjJrmaBVnvs1GjtJm>wad}5>d2V>B(#TCzrpr0N z*;2ikt4-wA*JfsGjdHyQZ}6qUSXXXaWxA2;8!y*qO4Vv^b+zPvUSL0Z#>;E@F8@8X zRv)kAF04&XR!Y@cZuM0?d-V4Ojq>z(&mMV|Z*Q#CbIqx8ZenhFIyY9Eov&9Wr<%D^ zbv!p+87o&CpsPAjL#541t-3NdJ6$f}-rh=iAB(f7z1N&7HOa(uZLHKRkLPg3$9rnc zW^IOCHH}rWRqW^}7IS@jYnAa_-{AV(SZS_NsZQqVs2Zg57dL~kkC zD|7j?dRFzEab_OB)~?BSS!uxlCK|>>iiDN9RbA&xO$T%3X|$iKmIWvF)ho?%VQ6?m z|Bf9%d}*m(MLYCLqpV(&b5-VMO7qf0(3G%c_Dz+mx%ym{MW8;eJ@^AkI^O8X_Y7_x z9PQt}MQhKhaOdne)P>cp%t0B!L=LSKGz9PRPLiu!vzA*0?aa;B=IZQMJ!r{Pl?OWx zc61C*mFm5{Go@MnDCCXMo>^SxL0i3i&0M8k&gFRnN)~%Tx~|>HpWo3jI=Z=U%kb#> z?OO*2w)F2Pbe*4DxAx&3KmD5{jyUDPM;!6UBaS%YK}S3sf31+zgX<$!;Qx*KSaH2+ zZr@z9M%^shk4@!jxn644>L@e&*n>wL@x-||jS@cZua=+uh6fQzJ2dO3OO3|X(rgUP z&mQN{5Z?RIo2CuTV}L3~&3dXXEB?u&T?xY9{r#G!Sczq+60W6-eS2ziO>o6#CxW{n z2U1S>o8O-QJVTIgiJ(3S|PnE~69-J;UX*hS3uW1CxzW*qP zjPN6ue`wN>EpLeo7Pl@f6JYtTqZ}5(=NFcL&9I!_5=*T*QJD-d9DkX^K=|!rZ-201 z=x&LjUPeD1tk! zgqN*b^0EcXZ=9=+2j!=ZbL9yid%}{Nt$fRt-85a>Q<@Gid`2lCe9>$AA88m`a|=gt7+OjcHizR`pcMalu0uh1 z?V6sW3`I*$cFa|W#_E;Xuw^%E-ymE$`q9rClGL!K6&D?bzUfLS6v#W(coD8!^N24S znwA0?f(0C#3b@&MtmB68`hPv)1jEqMhz^yfC!C`ZV0qbkhlTLN<(E9pu(a$bLsPYV zLHXtlt~}u>FMZbETKU#|G)v97P!hk?F^=#{H~#8fhM}dN56w+ZmK!nkddEGM9R|XSp1b;Z!_bn7OG?ufc9lRDTa^>S*?nJqso^*!qo=s34ZMU? z)z%SqZFyO@6+*91I3_ZB(Xp6Y=4d7S;`6S0x)sS7wm9Iy(;^sxhd5RqsKVu+D2<7W z^CTT}2v2>{$$vCN8B7bJr#5QU5E+Sik}^$r`-5M6x8cZ8plYKwT`u;`&SJJCQz@4J z!ugIF!lPz;?y>S2YV68ylF3is80TXBoEs6#mC6a>e|~fR7Q?bKLyAJh=H|>ajNR;9 z6SF@|>>w55IqRX08p3ZqvwzO;q(mDzNju6wh*?T;LwT&i*-L|nR;%YlINJFaZ!ttE ze<>4@OqvF3kkdSo-KWS1uQ{pPo8V%Sy9{LF1y{y)%uP#^|E(Hg!UOx?K5BG5c@e0# zU{X81qdZ$1#ssxdk`^F&FIV9c9(z)uW;oAU9L}o~=pLb+negtyuU}y3&RGPyt(a_9 zsG<;E(|GPyTT6KR?KgQhZ|x!wZpXY^bhW4>w7)^lKcJiwp7xb@_#QuSNSL>l>Q~G7 zpjDK|W0gEqn~ZSB-5b7d^0?^mD%pv7s`wg-YL3%t2tU<#{l`5M>&8!;OsV!~seeM7?#UvNl>y+~rXU#E>rSj)Y;V7L#B zXgAWt5N|r#5l#5Y=O5Q$h!@jeoKc7g_gs|$;d^Jk_Y6aqvf>%mVX#)4rW~KFUJc>* zE z1`S<5cnC!EXm#WXulvB4z0bE8;h6onQ^#Av6OKRQRO4tdwwyT5+=A#*y)-QY!%=FC z2;chKFFw?eEs8H78!8K5&4)QD6Rw}H{hOgVdl6D5O+XWik6fa&4Zw4NpzQ%ns9cfN|h=bv7#DIIQ5@59Uit#_0sGV zv8`6enee73|NXT_+hUtjXxmmBm+|zhhq!hiyy@K~uk9)KC4*6C&q&f07=CHS2pi{I z>|;&P{RPG4uv|Dd*}s3RJWH>L7+80P=sagJxgV;}zCA2L*FSF5(KDzWQ&vCl=wHuyVaHK#NYPE5Sf4_xW?^u>x$ zt?Fc%zD&ItGp3(|eAp6%@BGefe{WQ!yJv*V$k|$|mL{DMJpV}!4dI#Byl2qRq~(-G zQ?AD-I$FnN!qq=H_Gm+u?wJ9qy<-idDuM3H${^wCAAgN!Fs-l=x{X+xiO^BzFT(2- z6X9Q;dZ!O8wBwIh8uN{2c_yY(9jd8>J>5^b!T3wL?hysWjkQLjG9BF@N#9V5NOTr$>JUz+9mU&LC>9DNOi+4BjneDkk-ShAh8 zfi%L#Zq}!^pK#mE%)^Ybb{*uhYvVY6sN50mf96>|hNB%JmnVDb+aB+@A^iGDzib^i z>fc+gHaAonvvf=_*_Ugwa*L8d`0iI<^`Axtyx+Ll$;1$0B(5Ubn6f3j=$>bICoI)q znYf1L_B0T!+>4vR$%p0Yt7WJiy+iF9NX@5x>PAO%C&maHAR}IO@xQ-128xg>$ z)g`|2%|0i0fsc9iPpk~IGhSw3dyy+_1KU~C(T{Sa2yg$d6AoCZCuEhHo0vemNVyNF zsUgf&-s6@3i5cY(%0P5UEBx>&u0q26eQ)@kRd{Mf!GZ0uUVFFJNx1Qs@A|nF&Ejwy z(jXkAcdBzsxbe!yXROrHRD~8%xWp~s%S@+R2V}z2U-nBse@@q_rDgi~+JxcP!b6dN=-#q*{;ZxB;d`F@k*`?E(^{7d!~9oh;}SmW)}EhP@ntQG zqYqbR=aA0e)*If7nK}D0;S0Y#e5)ZybskT-t`~99!iGdLGNSEE*mK!``7l?C5|v+3 z0>yek1b4ukk`ltF{r1l#qvX6KD;|NHL)&DUqp{4)@JAOxj#Zb2aQYJM#dB}W}#^~M>J%e%8l^v{`%O~ zD{m=GaOy)uZ%~p5mptv4FENrjTk*$cRHW=skAH+yFT#K6dg*7ae9Gb)A#67!E7m2W zjX$JYs;RsK@bwcH9W*S3R#XtnbQ#G-ZV(}kH|sD)c+1o=FEbo1TPlGg<^hIly=N|a zj)LNg%*Rp#lnt|$GF4!wo(Bmjn+OOu0aQ~i1eau*D z+2D$jradC;U5bV9lGlvfU|3S_ix-UJ&tZr))q9{LH0gwII{1e#7^;@-t_1C<)tZVu zqU#kpJ`moy^TmFaa!xBnRAe?@RVPIQB7|@2bJP+3@TWI?*QiU)LLx@Tu6CW`am!p$ z!gqe=N}q3#YekEqV8frH5mmx>zUv2*R&C02wrUl|B>R$M9RY-!ueivsez!zIktRpn z&5DHZrUt;A`zJqZRl#g-qA+_X$OJ_S3<+32%POm#d4xME{OkqT-ltd!EBYc*<-3{yM{yGTl)z-F$(xgy{w2H$C@&QI!eR7|rA=OnBf-Hr`Z*P}Q!o$%eS8-0SIP1*6Ne36>&Rtuw>B=KTx z0m6TM;ybzwS4vHzV#I}x8{HtD$6@wJnh3u+|IWWPJShc>iuw50xVS{o1L8YNyAR>@ zZ|w5znTm)<#UgyIm_0+063(Cglg}HG?V8qNFXK@~r0-{sar_d#`kVvD8LHHJVpJ@m zFS-|N*Eedr5J+9~uCHEC|vk1Tcl~czJQOcl2#ek?e6o%~dxo z3JY?+<5(_MQ%^W{>hF#>EIn-`9&N%g%SL)$zuD14c;jsYe>P0*#1qjHES|jSQ4SH| z>z;Y~YD3hn$s$BCeL7zyP58)LM*SRWaWn-(>rrZ432%SQn*B!8d2PfL(KLh=FKh^$ zk7;I2HIs1m>u>YkUux_DH0FjR2fwL_$Wse;Z-00UfHD8PUMO$V(nK-65+iA&-9M>Lac$L&2xJq zJojN>VE-lj;Fp>{3wIHCwv+-qPh8>f5Z*rciq_d@2@!3c%#hIcR6>MrKlqCiO+u+C zQwmwc*oh}Pf(S>S{PuSkj&>%6QuZkwzV$z*6Jx^XpZHzx`7P95%%ku~ZpQK{7m+(`U?eYof3y>7aoW1o*)J?t! zoBN*sRO72%*VieaY(*xioZ94KWIbRAz+dte^DDXQ?=$%QA@aDQ;3)2p{{^U%ka>=}fYw1vN{7=^3(M zc8^v}_{n=8?Uyxksq%h1RPmznyp(;dT2#V^{p>T_TGt(w)i#pyJ9N@Q_{DEN`L$L) zRrk(jWt!War#6?T5oD!)6fkS1eh@yqzUjeM7MJlRm9pLzu;@LHc0~zqTlMjqt!PSQ zQq(39TPwDC=G`iC!gt*9ySuIYvdpSwhBc|-K^I%;Ro{~EZBxH^u_0KRi9j~(Y{qPU z+Hw97t)6iE&GoCTbhgwYSDCEOLE|Yk!mqsX(~q{Y7+*7_2Aj@8AIH3dg$OTw|36%0 zg;I$dA}==(u-v5&cjX8l`pOS&Ua%YtKg+#TT|dITbFccSl}n|hNPU|pS?2v3@*#ZB zx_eHsGAWzm%HUv#;`+IIA|>)Cn75Hp!Y{u1`UyjFdZu(X6A9uw`{yIAT&sqZaLYe* z{J}8f7r+qsVmlQD;hw?2@(J&4P-y{Ef#9TGa@~XXxh^ zBq5OBJGJo%fB)x;?y^$Z?Zzw!>_jYW#nBHF6-V+%(U(XvVdsl`_8A6nkWufpnur>c z7EF^Ref)r$6v7W4`1gOe^4SvCY10rA5MA{@SBVooW$Me`r${aAi}so)LDsL(VUlpo z?OVTQ6}Kp#e@*LRd{Ek;{*9g$S45z0BtWKT@T#2Ma@uCbpFD)Eb0!gU`XzReJ@yCEUXpT*K$_tDAj2J%p6N{8m?n@Y}Q3`;Adhog?5(jo0eMk{--L7N3RNR4u}hul~$WHrZ(U`j|#1lgzyz zWUwgV&S`v@#q0Pz|>;BMjPV|JI ze*GW4tCU6fSgCst&hfw&?=1uC`?n4CKS>EE+;r^Gezlde(`4OIVNqBJt`1+VJaf<3 zk)k|=Px;{uzc>1qx5XiIpwfjx4cXWM3rrOxTyod4BMb{?BS}gsmdnb5bIL#A%Ey0W zixs3rPZn&HXAn-wV*XP@J7%mT9k>=HZRS?;+vtv02@+oM&o6wbAz_GT0VG)dt4?lp z+3Mt^rl<%%dEOSETfQJFh@*~uc+KL4HOP~wnnyWl5A*{P@_ASLkp7y(z!#f@zDj#8;cM3I^25dIMI#mg_VHYnkl;5z*a?8}u5bVNQPwPr zCs^=qt5vZO7b5<+A}0LF?YF$h5I<${1nUO)K)g#%bkq`F)Afn(7}6~Jr8-Pa6iFgh z&ldX8kq!l6?$bAZ+fZcnWzJ&j9_6ENcoY|wYF&dGA@4L4;e1zZn=vO_9aHoB1PxBjqMzUl6H;@# z_7TG68(!*dIpdWJP$RgU<4;Vp$2z_UD=RBLO}u+?n2<$-lWd6S1h}IdUI;s1x3}Ba z%WA%afJoA1BC{YOa=~d1AK`t?d;Nqn=x$fdVb!}z!@Q@quSh3Q9YPcNQWZJjaR)x~ zYNKge#9V6}q>AZnL*ip{iC66jRuANaIlCtJuU9b=K6l^ed`jN%{j6zyZJOyjSlf^4 zXMH!SvZjiC`a-VWj35u&lup;KGCU>vU8VPUs&L$u6Ih=9YBeojRRPfLIZPquf^%3?h+44=}@ByjM znX#7j{pif!{>kdwjr!FwT(rmI5gUK5zOCi)N{Jo8KPRJe$|&KQsUIF~6<_**R4m8# z?W|W~RqoR&3Ac6sa+g(kI9<;pmhOO4>}un%+Wsy%{1>%lgd-n(c!yPY|2W)%lk<2c z;kFtM;X`a8R%J~&B%HnX+dlAfIA+WvE^0=$zd`1%*O~~AS$6$j8*^9S`tbE;XBCq@ z99YIBce>JQW@0rzLu)3S>psm#Mt9))e$-sAP_jIeFjm`DS{vc_AGh{Ktk{)XrH&Un#bRAvWD6`h}Cmu$N~9p$Vj;no|U^K0XY39E-H!5tH0XPtG{ zQ;0S^)M4W6-@ez-_8kUVXE}zDQH*WMME)9`Sr9(>`xDy@`Qgl8V#s6T%Y)PnAgsM` zh2Ma7IMx@|2f1u;amvuC1`aysbb<-ZdW&3r7oy>KM!4%wea|$m4rf}CsE-TVtm2L8 z#u0w)&0F?Z6^GMZ5>+s(U{jgd*FOC(vQszrFt(nomz?E$C_3Ha*aNHPw2Td5Y7 z@QhcS^wOhnHHF4jSK;!~gD18MZy4nsuQ?Qp$TLU`{F z9`6I2K{$EQj!P^rXRoR-;jce`)TBuWX*xzd7MY6EwMmN_t_?+J#dyJLnN|zW! z$t@^p)&Rfl@H0iiRpDQ(Jc&}>y;?RHXzJB|1v*Ho`!2}1LMMI0xer$ ztHf&jQHv8^_48jm*;p#JEe=-t_F*$`OdAd;D#Ff3-THh(g+Ou}N(>baX>$2TZ&oyf z!*~4QLPN8BAv7^!KCKpm@TODu`(??~7sBG{xLVtZ@YaJb@T*J9T4UhZFi}I_f*08` zgrD3{@sYST+A@sYIz?Ks;B$C}02dTplC@JpU~6)EBSyFct#;y1P`$4KiA z!Gw$y!L+`Fq>$-p`i*+a^GI<$Q^iGi?6X(<9K%3damvy)Y$)R_A`C%9_j3)^5!MR# zzRno!T|B{tPN^l z;>1m~pVyv5c>AfZ^at1k>p2TiyQOsPe1I`LviXG{d9=^V4%S~5!WdvXMU{&1!{B;`KC09b{_(gMd2c)z9u{gUCb3bEex#@fKl|UEK0tK{s4m5~q@)9B zlYa{EgonJLX55|))S31t-ODHDs zpYU7Xyku8|ceIXnoX`?Yb7SBFlt-zUFm)SP1f7 z1RuW@);$OP_lx6U%igqdtIsnDoQh;gii=I^B=p6Hx~d6B`;L06u@X2HSqO%)(H?nA zGqa-a?@)aw{Pem`zk(V#M~(TJiCT3YxpkS}rC5WR7bQS=V{XVhM}d=;#Gsq1ZL>P% z__L)B3*r8EUG;TirYF$F1RaKjS&#agnC?E>VIq9fx4OM?Sp=pnl}7jlo6FU=A^ga` zhg@ki7244xGLqfdBHtgYeh}{X_SgKBC9o$+>1-%BMtp6gs*<_ zIX)`Bsx@tf0eGXIl-P|bF~Yy!cET%+&mftm4UG}5*l=;5GEVsJi;wnWaoEuk4H{w6 z0ISJ?MBbypAj0K;eC_`+BA>h{hWX`)ah(4fRY9EZKi;Vd;Rn8U{y!Ve#nCv7oZ>jX zkD}MJD-izjhY#&Ee1Xf^Mv#}yOvlkZ2!jLB5&p%V>-++A8}dZ)Bn<(FVQQz4dt~=r zYWoO}UUI~zjlAyGB8=hlh(4h%2H{use|y9b1%64Lr z8s>#1#MvHcjO6F2`V;>4jz?@V>~w$*3p=vI#^m6$3p5Bvxaqe4-f5WISQWS70mB(m z={yh7E{J&@BndzFb%sFr=5Mb2pdl+Ph%6%LZR!CKE_unD{mS0L1UVcr4f#GQ zCU=CVzVMclji4a-m$bYNOVI&60^l=B6XAEic>q57`XL^}KM3@`D*Cz|N z@!=APBBFLb-r*wr{YPGKnGw~-BqwkURLA+XIk&}Y+oK&i!au+Hy*^cOVbbg@p@_8U zCn}qO+nUGxmyy<1yT-7ziIV@%nW}NE-w02$8?Gn@I2a+O)OyLQzyp<$@Es>?U(9SSz=yapcC^|Djh=go!$tUpon3D*F56hT z09VYC->S+@_{j_ZGHmIgS+9qgA&FNB;JnHH*f>Y{BZJ;3S@#RP2O*^S+1w z$nb>0mV{@cJFLRI`uE&$i(lYtBNVsBVRNZy#UsoCz5YcH_v3Awj_O>>;)U{U1AI+T zKBq~BMs-voyy2F$gT_!BvJ@9H{xHRhP`z7+OTy}ZEb$v-+su!%P{rE+JF1q1&wK7C zKV#&zX@AAZ<*$ggFRC69{@tr?@Y9esE{fubaJ))KUBXqf4>`tYY2%{AaKspUtP(_c z#D6^WbVJt05-2igBz6=>WW82ZlJFm2y8VZSsg0Ln!x_G0_R; z#IUt#L@Q23ov36S==UR?2on~c__Z4iXPZX!IPC{%`+_zA;q=B69$|Re*gUQyFmJ6< z+*7F*rz+#)YMM%b`hHOeBV;bkzO939c;RM=$$IpOtOH zlMHG)x$^_u$Waat;l6La!B6bk^goA3{>D@))bZ1+pZKzo)W#7|RL){~n*LoU2ZZnV zey(7M+IVtYlEogPAX@0|u-U$-ED|>U<|!3J*M>z!r{SXr)mv593EzIvan~BEjBYF& zb=2U&f=AY~sTPlL%@I@g7>*1R?r_N5j*P6-K1=x7r@y$zN_M67B`BHLKmI=TMG4QS z_xsa!mbF5XJcH$lo1MlGeyDfDX+}lHjNMV;BOPwHcVcM-cm2eWWlYVJ$Ts4@jp;Ob$LnxK z`1r@Z`X3J!^~9y-Ayg@R`4O zst+S&g!z)Rdc-&!WvUj>EDlwu`kSBJZEe)D$9bZHEqYy$Kba z94CZN|K-xZu!=4^jEY>7W>&LZM+L$c{mq+}TQyi|NDjVEV;1nEgOmjeqsXxka^g$0 zu_O8wlWqmIQb!0obAR?T#Eg!SiIqvInT9poEI51b$oq}W44aUNcwoC%X*SODR0{~7 zao=fvj9;CW+Cu1J{J%;0C%o#P_H8vX3vKa|Yj^N3X&ZwdMi~;`@XZT+UhI;#I2LHu zjGi7a#`9!@p8gxvDZiCfvOK8AX$EhNGTD=%|Xd*v;yU5Nk;cwC#Ra|v80DK5g-J>kmThAYD{PS7<7D;kOaSBi)5U&eoQzTwI68Wj(` zMU6ml^(+WRy+}nx__6z5;}?Q6`=F>rRKB>FcfcmrlNi4Vv=M|(8k9Up7F#*k)=$Z4dm88((G z({uIs2CBEKk3+ccNhkZzbA}(5LhGrHDRM~Y6Ekv!83|axEKRqgZB=GKOG?3@CuT z&p*e9%ra(b%8-d-iw*>YSAG5|?ObOqX*-p0G-IP#o59G2VZm;anBCgigkPwA;*-Wm zrr#k=WTnM~?BGYt(^&T_R>Bj0b)*l!weF=6R(uv|ss-AgsniMY`}}LKF=G2#O5LMH z=i+z6HdOElSbka|P4m4fT*B&6zxHeI8DW^DaDfb8&1z0HtU5~eG*y1WP3wm5Fk)Mq zKPz1%KJM{M6M4A`may=+(|>13GaaRPa}g;r&A7WLjrSbIOL)`0r#{;7E|OTDHI_zN z*XAN@9`m?4L)zMJ2_=wF)Vq{I!kbpS?_UgArYjrgd@D~R!l%s!$K-<{3B=vn?t~xy z!dLv7aqDrc7sH&P_-xg^*?_$XG9_{l$-!*H(J0| z&bKTmBblWXvF^3%+Y*jGeeO7`d)WdgeE74jV-4Y-o;bU05eR0d*|7$y6E#v0rrrPP zh;S45bYt!=+*a*-))p#-pYY0}PrI0`n zJ5urwDhtAs4$OKq8FMF(W`^?(ge5J>I$Rxk%mpmXKiw-Pk8({ z?)aP`+R%n4PfQdopV>1C6NoLO&C$K3dWE_x$)r#ItoB*LZ@sqXS%&|DL&HB;t?(RN z>}T7DlUs035o!PJu%n&uU(bB2Ume|ec(`$F1TqkTTYMWiUGjU6bu|$FW`6V)_mld$ zS@@qu{m*tf+=N&D>B$!x?u-?kbQ8uD>PTcz(C;C9P&aen?``oqjhIUcxD_Ul)bK%s_KB%&%YTqaPi~l_SZH72wOia_8 zl{m=tcQzE^-#-3sZ%ndml5cwqhBCZt^se)3l`g_R&NZH4bY-~CMpuSW4A-f%)L01^ zoSJoqGt*2&G<;EMApF9zDW3z9F~}GVz@*ZqaPQMrBYfUff4t9_Xh%wA7HfUB&vlSj z#B|5ePO5}IIQh8`GE5m$HBX9Q5)84Pv_TCZ;oiG{_Ig8=G0Tdh+F7ma_XMpy#t}sL z)>l;Oh9%41o0Ns1OVPx&eGQLj_Be-#@ZWYm-6y3lh5jVgFg-aAA?5Oc9j-Lt3s1iC z>qbV#yfaby!nw+{ep~S0bjg_Txlh{aL!9{qN#JfQX`hSX15xV<_jGs87#(2h2rk@? z>3PNHYC94h_msQ;)85175vspEs&$09uOD-yy|Q{8`0WV(`D1@`4A1aeZ6kepit_m# z9dnIxbYB*>C(n=HlsxYZ6`UO0#L;{8k;HZ4Z96TwKBB)QY@uiGC*R#ZGO*n%UE-l0 z;64(5>a@ix`f4jo(dR&nrgHEhvPG0if2}G(h&vE;1mfc<6qRaoL=L81@U8Swu8AU4 zoDsT{F8Z8!>b^5F;(#Se5qFNxQ-8J@#Pyt19P2a z^sHvo^scObTB#*&jFvKshAmXmU!GZHt* zEC-cE4+`+Nrn_?Xs-R>GXCjsb{A2?aIy2iXs4eq37__=GpSP!^56xfbq!O&m32+Ub z7OU!sRmIEmP2{}r;w`D-tG;rB0@8o0*;NUT%j|jeu)N&Ju_mX zn^7GtT4o@rxpRnG@i4VUp);-3Y@5nNu25-=miVRg%y+tSUN_SiAZRH$is>kVBUuPm zMqLHGm#-nddbv4QujZm2W*_(K?X5KIu395k2%LA}|G6^E#R3GG*wWNed>LJ393P7> zxDhSaiO!6(;LuUgr%T*x9}NnA0LXPt*TzcIe6li^98+?gGcfFW*W(EXH*|c5JiH10 zZI>jVw^Q@vH$NzQi!7Q0EI@A%vh=F0IG!0_;uz2Gzw;TV+`$=rU%@9cCEv7KgJ>6?bdDY-J~{8(y-1ki!Wu^z+SGC(^xTm zqr0R&U>O3+t$|6DF&j29FF*3R-dvu_lm}L?!o(JF-;*!qd!mPnYbB41tr8$x?>vWC zvoIbw1ZSa%tUHQ=??kN=)eNl*Fs;mq`HeLcOP+Hk$cMong^Z^0HHSiebacbOj?vNl z%3L0bLPxnUI=XRSOaJI-7j6bPSw7zM!!>+6#&zsPlO7;kne$o}<3maBz<@N%bFHD} zhlk$ldL)h%%2vWqI#D;J5HBXp=I0a*c2kIT_3G9A+c$RT=;#=U>WnLyYq|6B0y{5A;^R0;P$V6C+<4c~0a~bCID_fz1qtL!|Ldf| z7Qu4rIIxW$W+7XC{1GFzA^w7x!CCC!!S+ij7~1s&K$h zt%^NK(>5}})S>@aBMNb@GNc|14LwEs6ZXL3Ao~=u_+8vzLH&e zG)4ES{O(+-k?Zt&!{GzfU@=XGuk!v`8U!$mI}n0vav+T`%FBX;ztW|4?T&JL*F*|?@!O20Z(~~085#U4!dDPu3MD$1f&$HxuP0yO#(j5M!@T|=(h43@p zcDyd_sm#nylVBPyP~_BX+r-=?#Op0Umvq``IH}X@u)CnH}?n+l~ zMeYo611|{fwpiVGXOgxah42eL6aXV9Z<8h+XTpx|9{OH>P(9$$tMLWbmh92G>Ug#h zr+#@l7eChfhKiXg?>_j)z8!Ul?x4xhxh&>F_^4JilQUCX7;(zlB`;qme_3p#6K)b4 zza{BC6!Q5&fk4o%8+p+~1jNaI-gJ-nr!?PoMZvol-CwHLsvM{B&2^}X&1=ySK3u>5Y;RC>k9c?AlL&O z!WMxCW%6AIAx#oxlVT9%)QTOdG_$8NIaix&tmlXJq;p3PIrGH%{W7f#OL^T3X^6-d zHv(GERtRfV->Wn}E|tOWIs_L2_+;3Y45aauB3JwCE1^^p4B=MD26%=n#Mmb0)k1AF z_z%~HzdZSLzAQn}!{+n$E?bh2U6Eh1TYNF2fc}ew z3q)NTpoKXQdPgZC&*1v%dIN4?;AS-(laNV3X|iVF$=?*uaBS%fGTO!Xi!6yfi%xXvkpWBCFaDpEq$mM%-1$BUul7LIomFvmx@6A=JibmxEG&yAT@@6!WEoEKSP=P@4 zGQ60nat@nY8_nE4q$1_^lyitPjrXiB8ZY?{XLcfKY`HhO3|FxR>t<-8Mm?UR#ft z7|I_AWa1=G0|ZOWi}Af4O70haPVAhDA`YOzrTdH1 zRL$_(^+xbc+#2Jhnd2=rcQO9;6>%FS&j4PMxCM!_>s5uIR%)}LSOZb0S!9%m=|s%| zY1)v4pHUb%c0n)boHKDN9-?#l6yfS#%ik@UHbFHx2ExQ$qlgmND+Z?>j8-Z zL?dL(RdMX=)eMt~{}l?B$h^ci0+~o8OXQ~6Tq1M1Hwl^h(ne^O(EUrXFqojsb6AyM zva=5t|Et3Hl*Og*ZyQ$&ir<%LCI9#f(f|{bsj%HvPclt_o6}lnaxq9~`H)O?Syw@g z6(s5r;CD=)15rDCgHy9QL`h&2i7;bV+lnR|c1+e6b?phO3r&nwm<91QXZ_7bNUr2I zWFG70V4YS_J{qSI2KH#$)fuZ;hPoDIqW*1U3erU9cH&7dspep$b?OAsv}F#@6OnAF z1p6YXmZ)zgTW*+6h*9UW^bN&)rMW!}%`gFt3N}*=yDL&rQMcA*uP7L7tYm;MW>yw@ z_fRxEQp#YIGZwsV+sN*;J1Ub{qC!i#0bNx1FeK@19+$3KQn3RMg%T?DO26`(u(m34 z%<-aL=t5PgB19&3I4Bb_$pHsQ;=_$+6+g1pRB;BvW5vrT@KutJfif`j-D@*2_Vg6pf13J7zb4c&yG`n(-#E>9 zAlCi24-Nm%F8pf{N@q6W`yiIWo*3n!`AF;o6K#onLTDD!TOdu-Y{oJ)%!j6DLxLE) zae78PMvP?c^mteX`8Uy2#d@h^*p;RAfm0^$sMz9S_~|{>7F4gtha>#41A-;dVrY)sYm`EY>- z5F$;Cg#n>&N|#mW6FPf)vBeF!oY^XuRR;r5>K4?H1Hw^!5y}}?ODB~?>SA)4TIc>@ zwp5E0DqWNg619T0C6ftSR+h#aOrOH?dbBVNO?1AD`x`D2No0p+GwQE~M!J?>*hJru zBF=)L0^h``P2|G-WWEPyNmm&#lW>CEi_{|Q0xAzW3cU};iVYX!Ggx-9V!@ej4TM>vLUs6++=Eg7w1vK|KB!fXhsJScd6N0Vn3Ng5eK zQo>wc^7FHWrGrB|y3h8BDu~k|M1z)Pa0-DnUJV7QvMn^vOISURc|J1W}z%hC-S0l1~2Nljwek z2PISG+;|qC2Z!f(h%iUzswo!Zq~KlPglTHjS4FJE8-@;4NCfnN>a&V2Yfvp|WBlw8 z(QtVxiAIL`nJ^zJgCfUa9r*;vUP1sMAx`E77@mwO&{{4(%QapG{uD+alu{Yk)a7f> zdXhrm%U)ki3B<3Av6&!eTPUNr*?c-OxUsN9TSp8E3#@c^&h~3=XXB0+jSI5VEr!7H zz_BQI+w-^DLFd*Z!hYHPA%HksVGF7*<=w%GB$E~6hmmUK2NV9Xe`kXm@dnPU)VQy zF0cD5!vgwPNE*W@2Xi>Uo#~4sJCKuv9kcy*fFR~{(qQI8$JPX+B|%I@%Hp?d#X&j9 z=qw;xBv@oqg$iaIjwSKpG90+9IKK9tX#80jB-I~QY#D-x$5~{+3?e(Rgv_AbA{wNz zutqvVAx$Gt??($DS67!VEppOvjrX)uFX%1OT&61 zK5v%QugtB2lce}0&0SeY;gpt2vnuZmL@NvpWW6!1H4v6k;uj%63C>EGi7iaP?qiaQ z(K0C4kWpL{Z~{&wcOZBVv$s&92u(rhg6DzFS`ywDuhI<@qHeKg8db<+wIk}50n&zW zDK?QEF?=FLE$?zER^Zhcg6>k}hZ>py0u4??y(E=}uJuPNV~3Z3erW0EDX!6gKA);> z=iO+bP8<5%u=+pKZv}T|d&(1ONd6kYS<+3ex?<2pDz-)AHsX<}aD>ONw4-gpwe~m` zUGT*_ZrQ!#erQ6Rm{21)6fbd+Rq{wm)%EeK40a=>HRd`*i+leoYU2gDu)g>X!&Y!h zWs9`v(ls%Q59FgOZOL>bc~e@Lzi5vwRLG_)GKo)|9~uey!B>O_MVLs=6@2Ikf@Xsx zlG2Qj5ExqKaS9gNDU1bCERr|e7A0rAl^V0!yp*7ynEt4kta6ZItx7TC3YAK&j%ZGp z-RzGB%L<5{fzN7hcBFZ33rp~YNly)r0RfXQe7)D-%5BjB-E+N_2nvH)oxk|}M7LVn@)-u1i3k{Z?`@Ox_mg_Z%F=+}d zj$1_b4UaU8`8WvR>+QjMZEpn=li1^6jEsnnxj<+#nbfoI(B0X!3OP1@WEB@~bf%1r zW^u0un^d`l4PJ$4QWQT^GVs9p8%%XzAEtiABI#Fv^6o;B`m&Y_F8O=5&#E79zhZ3K~ok=ir`A_@%^ne!G3GpaUAa9Mikx_Yu=HVk^&EXG1E zeWZ}lua&*6Dp4wme<>8EOfQLz>-GAwFtLSaPl;L=y1Lg&c(Yy}t`$<+52LUaD z86Be?TGx=ysK$DqFh$16#{JPhZELpz)m9f1)9kkII%n1O+;QtBOS0vgJ8%F-Ti@+o zE4q&knEkR4VG5Qz(<{2Pk98XnPX1a%QoC!jW7rK>hB=1tqvtt!nzN^3c_WhpRS3N- zE2b*0;YqveDw`dvHmCE#O8fy+6QNd0LE7l;imH{A&cV%tqeFdz17hwGA*u*cFYHm7 zt|C(6?{u-!k~_k- zSYx3Ch5W`A!Jt*Jm<{=^GzO~N^4uA#@DF>c6eY$2cQd$kCX+@($!(s)NyEqzkyP0K z&x$TvOlje|-9joK2`iix$?<$=*k z_%d;A>*T5xkN82E%vT2(tKdna9I`PGtdSD{)+VZDjqLiD^zRrN*uHIaU;`J`4Yylj z&*~xR!G?O6#Shcqv+>3BCIcaJ=A+IO;1;7AT!RTr`N+ol&Hd{ya+852aw3U|SL`>F z#z#6~u;$9nOy!@3k1Y93>$!2sASzeKDp(#+{JByNh;!C6Q!Mu6fCHh;at`S^+#G;O za1V=^NIAFaaeR0RU2iuIBHIZ@yautmr{92-sU27ll$F+u7dYUgZ#xn7UpDMSG{g=1 zDbtOYe2g{&ht!-=jLmUaqLsY~wP`3iJPOpF@y>ws@xkWF< z0`^UKaYx*GEn4E)VlRXX6WxefAu#4HmL7{o4Q}Zh-nf0oR@JrWgoJb+VVfX>Q^J_Q zud`MIX$L#~RYaFdqBI3Isf)3gg7wANG1@?6#|Xc$_fkd^X#rB=U(e*{oA+n zuqwbnMf47}#37(+=uLZFIVK@dLerAg!sLtPv8meqr!qFar!0ujrzRPjPNC}DR$CsnFI4%i^!WNmcMkRM7)66_-yt_4Xmp}@ z*O6P<(bGQN+QML1xILz20<6saOO57wL^#l?f?!jk$Sw#z5m!pQ(Ut4YabFD8dMUPB zxmldyhR=&pDSlIiqbN?5zz1?Hveoh(w>ohKmW=$~A?_`5Ik`QZSfCZoATb}gYKbeS zk*+LMOGW1D_hHDiBcL+Qd10z+OZ%mkYP5!wsW!rflc;rBr?!U0Cko&PYx;6;FKo;f zSp{ZzIx5^h9s3iPA=A z#}b*6qx9l0g~*L^L&JT;1MB1JGeX-uB>OU)9RYiEXo3moS`6UM_`nm^IEVP^<2d65M?S!r-F(Sb zD*=a+5!EGTr);~RMA}>ksV?h&qnOyiu8*StM)4&K(`7xDh>8i4T`@&*x^KHfIYNWw zdx&F1HjY4EhL;ehb4`yz7@!VcrYuA%Y@Ns7apFpjn7ppYA%}I)$c|O6vr()L?<;f! z?VxJ8M3?Gi$Utl!RYqs{rj!(kwX4pKw38JerpYKqmSTF=eZHt|VKvGKqZI;T%03`0 zDQK2cMzODkTNiv49DFg-2ki4@_?!gxZ!ttOtxj9ZiV;2#gJU-e>6V$cEfMVEiFRqETf#_5l{wYK+M-hOi(DIp`Ugh@y6IH1S|zX@=m0 z;aJ?>64Qd`jMa#v;P+w#4|{EXz@^u7&sH;4$l6nHi(PKMa8{R&o{)8DcKZg`@AP_a zmO(m9pFcv`oyI)q&cOofEE91GjZ3+=Db-WcpBTBE9g^C1#+-03<)B7K28Cp0 zJNh>b3=P8-8SdM$sef2JLz^aeR$~MEArI?jwj!6r)S{zZ?Qv~}Z{}Zi&XcDuY6wqS zdcJ^`?1-v!7}R73a~hejBt>K+F^@YP^0h8%07HbtCAJA-*bMJYp`X;)qV0yF5z#%I zRBvX?4?K2*FjYs}p@Y6|a@2?p_VUI!fmDtdW6^kqlOvGw3JDrhk8y{K!Ry=(BWWA~ z<43#`*fW~wn6y(mu(cnSXv65n?c1TVzE+!&_}Lc80kYWJXl<#F)A;19wT&hyZY|0= zf>sXv<{9C@G&dDmEXz7uqz<|4#F5TTTee@=w`FJ-j@mVB(QH6kEx&tLQid)^*@SNm z@7@AW_TX@_Lt)ufodQbQw&PuzrJZ1c*RtbK&hgra=0CJU2C#oLvGZ6GYm+xY_&KZ2 zU4<3H#YF&I`L>Z6?+~T?Flv$U)IV|D1#TtUJDP>6mV(Bty!?bK6=|wW*UgqD&uIw=W#DGu=Gs^&)8qJUVYU4x#pzUK=Se2L)0xwQeN}c{OsiQtp-KxRpD_`lN?R zHlDT%baIw3&GB(i)FK}D+;(O1UXEC-e8r6ZPg zhP`We`+u5{c*yPn;h%1qiz2#6g}4PV?xRWvA%XuP?iVBj`#AOVxl1s$t)*Imve!s- zkY$3c)l5-$u}|buB1SVZ!u(7&Eh09;t;MI&(uq1UTD)C7rVOG7hg@f0@(6XW+%5Ui zbjHPU71fYz$whOuxEh&B@2BrnQ?AP+1m?aOv2B92Qh#@<~Qc8A;|`ghra3r6+mIRw=u z2ZakVk`fZb5kdDlpir6wJl+aYXBDcIa!+0=#zs^;ivY*t+7G)z3xPg_5YIC9>QG$4UzH=G%bXfxl zW{4Yz7D!_|ESe@ETB*4bR0f$eg*{Ie%gg}wQJvD>CS&IV zeOm@D*NH%!3GbRCJ{!jKBu?S_x+f`lc(Y9>)=$^?`IO#Xd~j|&IOV!a_GU++LFj5| zbiCV*1FjjHSQ+lU#4!`JHfRgVMtVxXh;-u&k2zx_ZN{FpP98G4!1iu&m(7ce>=AXt zn~zqh#f#j8w&+t2G*pVbWBC{wjF8prmVv`<(3G)}4c!USy`lX2<>%;VI0;%tsovPY zZu4ykLPMWimaHArVwiV{2yyBHlhkRguIAGu;7f0ARA zy8Btq~f;gNs!tN8#1u@-2%)NPfFSc z#VF@6A#`M9^*Bz8D9?_Jtfdv*#WTx`yYW-(>@{;Z5QKYhyv5b!IXZMaMS}a|6$8t+ zZXYxd9?E8+EIWSP6=+;fWlx?bk1|umy(=Z9LY~WGKhWgn9muIJZYNR;4RKy5{t7N? z3-m<G!_Rfa6+$V+5eQ5MDmO?+q?03%@BX?qFON0$u8MeMWI6tGj%m+| zbu8|$cU@83ja!CgZWfJ*re&Xu}97k8M3N($jU_87t2` zxT4s#0*9!tjDkG;L;-eYl+jBN#?2K@@05C|a}*qMb+~Y%?;yWd+$KoMs0q~uq)YOV zWU~fEq}%HFL(laf90jTg2YyzPv6odxUN}nA_$ZAux)UEpKKT@v2uh_b_>rn7hv>_f zVIU?Z#ITaszTQYd9G8%z84;J_8MLo9SBN&ORHrSXxEuE2Jfhd0wIXG5xF(o;r-Ia`d%2N3yVg3)MQ{* zT18nRx_225!QfbMipba`#SIPUM34)VdH1)FmZ8?{tr$u~BNfuMPB#JKZd5&~@vcFP zaSeJWw|?pHMKSqZSk6UZKrOJCQ5|r<D^@$i;|0*E7je4Ol$j&GXM^POM6x6GLb<{BCbF4>ZaCW%S*|QA(+vE0YWpE0~D7A#HK2 zv!-tx)QBYdn;`<^>fS6b>b*C35PJ<%7gU%KX@i?S@zUHmUCMa2)t9)#3U&9ofB-2!ps)Ce|U_)X|UP!TQS%X|+JGo)hef6dRlRw{g23rloGsk`3oj zAhb89P*eDLuqbY9;vzK!{Bc8J{DVbSmkXua71$N7Wkrb7BYU9I3fZz#{u4)qSPAgy zRnMaV{&3QZPn>8x9SO|4PA4Ji}!_d3n4XuwMXjKpXDq=mm3>ZQU z8N-uSd!RmxmHs3acKGe7!IpE&aEu`T(JL>RL;AK=IDH^uF(845lGqO8XQhzrqPhWf z-FKBx3#eGBzQBM07O5)MQPJSh%%fhuN{({YP8>dSCU0nNk6Jy8yj%N6XC_(SlaHgK z?}@cb7o-i;yAra8J#*ej>;bkPO29I5JKOXv>nbscP%*Q+OYF6DmvMWEkiUboJl$H0 z?}C)9XrsS_<=sgOD>^oWPG>;X1j^je?VL`Oi{paWO}dH%rw2w(r&h(!v_ZJ?2Ae-! zs#Osnw?+(t^sb{}(^LO?Y`1t?Z0Ch1w+pAMp`mD8D5(XetzHnJ~UZw>`QQv%TR+(0UXIe5zS^;xc)}Nd@&1TV3KV@eNF?k=A)Ow%Oy@ z4LNk<0ME{9VjY39ZnKF=51Ek6TAV0R2)8-Gj-AzW76)RS8y+p#Z-jd2%S`r)7=?Fr{ukx)M`jxU-nTo=yu=cE)} zO4eDevI>~RsVH(NM6wFB1dWxf@d;Nn%*~YMd6ot8GfFtln3L#Qy*@XKJ|xZ6DZB9@P=WGoNOU)x zE3YKjcbJVLD1aUd!1aw`e)Q8HtYPmc8B^uy*@?O7@c0By>XP`WNbO9d#fCZ+J*=S< zqH3V%P|O`2*@M+f(FMdM;8=0}2A*D|#_%*o8uH1PNE_KC8J#(<-w7jx?Dt){k>>8@ zg-`d__T4ZKLb1z@s8cWRLcYAuBkQ`ruP0~S+BiX|9{9j2P0cRSQXQg>?&$%^3eJl!Ni&FVD8#cqkSECoc>We|=j zdI4NOt(Hg#4&k@144n=uNGMIQ*t0x$AXgk&E)rT!3Gw%mA|y1jym)XwRWWQWa1L=t zG>l@A9bpuo;>9E`;EG-IUXERI^%88@=v})T6c4U$VnkcJKp)MaTdV-S!PWGV*6u2G zU)$Gx`RK_0^Cr4S_D@Xo?$%qYx}Or<9qC3da7k=ZxBG)L~HAS4Rj#>16*`DC}Gz+u(*6hZr8|l=kJaQEh7J5g{|OQ5kD>M0*dOqRdEgc zC=$6eP$?!&Fj~QBJuW%Bfa8l_nr3Rb7VdlL_$W1^D7n@05`V5(K^+wftKOe4fRT0g zuFi_CO?o*}je}W(Mh$s<%hHi1KOj9)m%ngdRks3+#G+U^y1>_lzL^^>d;z`jzbhZT z&OS>j^&O1A_?`rul5u<$o>^FYQ*20{Th7a3XJEVHp)-$wC)BYMWBH!%z#O>px%_?{ zx7nlJG=oh!KM#d`w3L$_>|o*@!2!srT;z~M%&hwG$rT=sx&If$Bsf|ai&W$w-1KWcn zlS++!oosF37Kx}%kdue~gQer#h7Pf-HYbrP{q5JNi~^LBF-c|;_F)P3AqqgVd&oX+ zsai#KV{He@!hTppH`JHC42Z;E_{Whr_&m7MvwS1EWr@R46cAh3Q>#rQQQvMsf?|AJ z(7Dps*>LHOMx`4<9h#`!gk=c#UQ^ zdJ%*1dFP#H8>2fkBbH~1Ig+9`k(SPZ-Q7q8F{AHztS?nLwQ!4*IHLhgoBNW99O=EJ@+>W3@RTC)W7KL6oeNuq1Q|ZB1$_mNa&# zI`f=Er`Uly*)6npy73_R43w3I$4Er#(V3`y-=U+CeLzs82Qm?eaKxk1g)0u|`VRc^ zPI}Y_@aMp`om&nRkz`%uoi2Fp_*dM0pp)5+I<*j$NlK#9=y#T(kfvJ2nOP3=za3Dt zj8e|tVlG6pPNA!!m3sO(`bh{)|cB<0A2ZzjF_2hSpW0>doB)t-2 z0I?Ez*3blmNj64T}fLcpyoz@;!5?IedOYi&`IbDu2I z?j!Pxr&grp7s>*wi-djjAj^^ zU0jjkZKN>L)tloGn=93DXQ zMySQqP76AqqgfoHBeLPvl*`WOWTJxck1@2_-Ww7XEVcFUK z0bZM^m&>S&nMI|&z{AlcFyTmm&S+QAi41Ezo7r_Hzy&x)^XgGz8r29~tPBPbx^-${ zWEYiTO$=AYe%MhY|pV8T27d<%SJcX>2I(nVZC(p4R?{scb8|9cnR6xF=L>uUTuh zqj8H?%#e?0$YNE3-xQ!6iApkFvn}-lVU-s<>^bAwtj&#KA3&PFm6ByN!w=fgY4yGpC7&+KJ7K@i*1bN#|*DY@o5N zjPHSb2Lp~4d9+b8F29F%98qjjvo6cDvIXO-srB@w%C>Hgp1EU6r{c|r@*c9>b5 z49BLC zu%;L`C0%g?*71hfS5PhD@O*?1VbsQvGFO0C3LmmL4NKT4!&O7f25&KujoGrmpwfhM z=&}=;jL5HI7xMgq;abv1<#{XpogCuFzZ`#_L92PXPz;fm} z=l);D+El4Ab9x zQFcQAdIa%m(`9@r9y2fZcfb}LTZJ>N48AAMLJkC_7%_3t%|3jDrKu#i4;G9!au*^6 zOz(mBjLj&ppc9ozc(XE6hyXg|l)P85 zkkXyoU?1R^$LGbW$TFx)&N)`LjnLI#g3APJeJKRoO3{})=CRoB3L@uX>}pQvnXWP& zy}@sLU_R$QB~Y(1!v+|t^IztnK)8=aAGyc(=D|cQa;o7 zB{JJ+^ow@aS}t^$WeBWEUwv|pTIjH**G4jm_-=CuWwwh6Sg3&Oe%Thw+pPN76q3`R z0W_QsoYit@&F;kP)_Mf(81CV|3-=nk9Sy%XL`ap9y=2~SZSeU)Zl7(TgKnw_-b9XX z>$yT0uVN2+d$$h`V^2ii7A(Ip0Y=|rB+LllaSRBMIzq}h^`#K26*GY4z^wW-E30jN zef|SMu=7|zDpjgYR!p~6YB`{#f?Xpq>Mwa zf)+IyyH7cGgE;u;%<7SuRi+HKZQs@pNe@h7I}0+ZTnXJ-RNBkI#gq{hb!CmdR75ud ztLStfV;_*N=oXsN-GsqL?p>-Nmg}@a_LD%dSgkM(>AI22T0w_4J~Ufzx=lskeUG%m zG!j{yu?Jq0X((j8MhsPyIsTeeI^WaL+Q-n2zpK03@vsKUxI2eSlwMPusMT;`gzVCe zwtKe?_8knI$Vz@$7^H7RcCeMlZ8DqZ|K~g!G@Nr+{2U7&f{D8zy2Pm! z>;Oh!PPEa%G$@IiuB=SO{Tu$aOzALoprFji@j7)x#yw(!Eks5BRv{b~dwbV!-@3JL z8!dy%0IF>+nz>{ktjv*k1HR`Zr-aBv-pG>rrMBp~3k}4}V4kn#`EAmOASo~YEE6lj zCzME{;s}ZJ5DKBA(Azt>qkpt-$NJ4U%6oME)(tY*a*NJDObNwhs4j@jGa?~k?!p;9 zTR{>yoJ53DGYD#QU3gY}T5-%1kA)*vEM?)bJKa{rmT*RJ#F8Y&D=z4V;ey(0ep$L6 zK5JEpE9XKS&ac!{Ce^`KVsLhxn^~bsI^0-!aKgfmh?w+H$``Yq+t3$(Zp@h6VukIY zP@MThMulvQ3DfN8cU^`@%ScPEa?Kb>fgu!AVbuy(CR?8uQoQX$!)eV^XC6!K+yYJE zx0)5}ooKDKJC$tDHt%i>sM(qS{s>q`oPRh8>V?3vVjN=Q}y!6TRaRg3=8+EMG zL=znDW@gKaN~`cO{g?=qi>*4Df~~X1BGf*o{Q6izLOvf1X}yvw;S83a8}O(+hRIkh z3|YeY7BfQ&zoE9Ts^>vs+p|Muy?S%(Id{bQ{z3yYu0;c)l@^4}#2Yo_Mt6t-sN(hG zhLTL=X@MkgCq!Z$5A78v3oaMf=bWU_y28lZg<2xYZNsG>eh`O)4>st$ViiiDT3|zU z%6iZe!%2Lq*k?%e#kXOql5mW}JRX-;p(7QN)Y-kRc{ry>CIRNKs0iJPEcf_Pld+Kx zlV^!SFurW04Qk*sE_NhOIK|`M1v~b~br7&gbO5w3>=O7H>kGs_Vd{te;S}i_^5g(1;aeUif(%(SGB@fIf;fX@}3I9>a z4_j3Xx3?g$0MRgIc1b-aQE`dbFucGwV|FKsIan8V%D9Cwp6H=ile$BoUX#Q)g1Cm8 zVCo?mE*%!<&?**nU&PN7WsgwD>-G2HdLwMlzTo>1HoKHCC6iach<|n7Tea{Fi;3#U z9a9$-{Wj#Es1@(Is)6v|1V2fQ?j#LhlUWSBZc2vzpuUx~O&Tu$hgod)jVI_Q{u2R= zUj6@2_pV!QCE1$jf1W~SlUpLw61KBy*C@)yg#l+$Wg8A)XV(EIDL{a>g+yryJE_bZ zqenl({&U9Yr`u0*zHeS4V%>xQySh5NcNwh}D`Lis88hZRde?B4r%=h1aOlZW)L}CJ zg$gNQK&&7?*FCkZn>H4s$F&x=>Px}V z1$ARY7{fX!weg4>i$Vn-vR;&}fgw(=ceMftE@Vu}IBsAYVwEc7P>R7JuB8VZvDv9Y zEOMWc!mJBYhSnvD%Zg!vLvXJ!If5a~v9uXM4`L17h7S_KzqE06Rbetk?-(SL8YC|J z#~)}f?Vp|kTo@^Aki>w=k&-xu0L8r)09K&h&p6F>0q7f?73pz9m1f^#d=xCfo_r}C2^dlTitQ8;2k-QHdjEI3ixQ+}HuX6k zc%Ne0mCnrSTW=+$y8`u>_83y7BiFax{ERLg?w0zI3?*)z25a*FP;#5j1;p`6R&3(> z35Hes7#DquoJkJ-L%M%7tGhp>2M8=;t~naQrxQ)o+z<^5 z=LV_37)Fa^u3L38CWr_lcx|KL1~L>ocI1>KZD=TU2r>&~1RrdfyuC(A z6E$Rb{v-tTnaLK_RVoOUMik67$aD@2+EN-)t}0C!ec}g)w{k2MF@!}%#|2?LSzCpL zYyq7TRb^j$tYLo#uPl<5@|%uRH~SCb0>IaCOhU48TTZ(@9JMcnqe~D_Ah_Hb1OhXA zdSMLXCXp*S@yWva5A+zsW8KJl5nVw%u4Ef|Ekn9Rz_QVyA8odB?)u(277#m!|D0P3u26O>&*gA{kjj?2pl6$bQ}^WSM# z2)K%ZvEQ)>kV413lTC=INQ=;6GcZ1}odd1>*E}=9ff`rhL$7!F?igt+axQ}Hu)AFe zUB)u2DV{+HKx*j)R!twTBc|9};~dVwfcjPf zhqP%eOhaQe?eL5C$72vqN?Zvnv4N6SVFl{pN}>PKkQGV56Qyh)B-1g}D$<0qjn$js zAf29WV$d5GS2u43|5l@wQLf~@dSz)7dE}X#3HE^EoeoEileWSOV@jdwUasR@V|}B$REP*GnS1wLo?KjO(WK}2<$?Ppc1mU+tX^VHfz$YZHw3(_H@Xap#e!uK__QDO)j| zRWuj2K}07LN>|rEl_cCM9#)NSS*08pAv%~Yr8*QPh#);^v-XPo%Yz{_ zMY-4fn$?mQ*fWB3MWb@Ml6=VN!CLy21ou+9(a#gtp}!aR*zV*S*CIA~^g$OA^Mt>b zSENdRAZMI@0FX&lkT1?hCn3qr8%i5kq;Ljd)~Hz99lRKgF_pD*D4TXpVG1 zXvg5;Hp@r$O;zKy-e9mZ`h=5-HFaKEJo8ssqmi4rK!bKzC&4$YlRB`=h_?@Kg#02p z)4{fB;Vku9qo7-pa8%YbW8R}REwqS6N`y_yncO>B9gn-$@kRX?3hAuTV-bc~`VP>{ z(s Gmyc~(l)lKHmsOE19PJ`Q)wO1C^l_4o5e%v2NJeLZH3=L{{;;Dx*Hp)vcA+? zF*c*U#NEEy{Y;+}2x+Eol+WKZ$Sgr?G_1skP+NguVoA&{kn!Z4Xy9__x34$e>>X~e zuKjiOyAAN55H1D@jAuz~v$pkScW-BP^UWTc#e)V}_1aS7$q;h2_I76%aR_V{ZgDEM z+S}QX1`}lW;azfCK$0Ac#yCT2R5=U=DK{4xENo8ewXFz%Day*s}RKzt})7dTLtG#*zoW*4A z_;p&y)PxQ*8hk`B)YT>D#W*EN7`;@EdE=;DI}A9PI^Z^MqSHWZj0b-LVz2Q3VFy4= zA%gt4?wFqCL#f)6l0h)>oQyQ$^tSQTqJjdk94*mqpv)gfGfvb_l^gevnFMhWEdu+( z$L7fij|#ePh$M==1qD@vV}KIWyfFIB%5MrqUhPfaP!?LaG#0*v36D`|oYHT8q&3+# z3@V49j;K)k04fQJT0}wF^tIjz8NE%t;xJJuN#jkE<1wBN66q8jg0_vI{v*Z((8o9N zlh+zNVIY=Vl)4qlXO)3MAEZwzV8I9s>Y(0oH$-I#&Cf(;4lrV}@**ZGC^)=C#ezAA zF^_#n}V*+&E!QZbL72>R+*?Bn?uQ*a`>X!;o0FyDSPn}Sw~vB5FXSt9e_x( z!7(~baL5G}RS0^k2}NAx+)Nx^d^^dnh1gb7r)u1QpqiYx)QZFttYGPv1uGbN4dK#i zMcg52TdSDp7Q@LolZ%prtdzq?4o}d6~Fi7WmqHWa( z*7Th{RxQ8~C6h+()aCgH1mGLHqLQDe-Xa~aRM+$#f{cW2SeVOCNEte}Wq@p#>5$6r z^a0eznc#=h1^x^`8L=BDuo2X}1pJJ<06MweGIqxpn!pAaEn)c1aCxkR1JFhM6$8P; zu&mt5RDY@7$`^$dKtW=;usOKRlI*4pI;C4aAgWSGY}4L3qXWd7q&^}P!8!?^>J!O( zA{>b7U^UgRGLMM@|5ftFA2LmqFoXbP@5gnLEn>b0R9O{W0w|6rwFpI665n4asD@Rq znnZx#zz^^MQHCpaCi`R?duk$A1t8!Q>y>|Q#Gik)o+P+wh=~FLm*{XplB1bJWGwE> zKT7U_GSA!EQJ5PDT6PEF6*vF`N z#NaZJ67SU(O983GYRDSA`_{O8`2~U%Wk#U|`cvz)b19CQa!FnNu*%|}NCYTW6neS( z_7zmJosGS{za75YfG)N5hC8fnHzGia*SWp?1jWVsroDkL5Y5@2`5r- zh&xD|*RYa}KJl;P>#jVxKoaM0(&eKg*n{yePl!nT3b*yh9Uy-SC(CGH#3&R$6sWwJ zgc3OvYxHMR6ru=iD0ah)D6_JrK!$WmrXt6AEk4SO2;b7m-~Sm9N$u4^?_&=s1&B-| zZ;+5R{Ibxs$h{W&C;SAwC{EfbpuFR6wdFY~5P;ro`4x@(C?!hJzH(qv950gNV^-oS0vn3>$IzkCjQj`q zJ?@W>uLj-m58W|K#)k7ic&~Up$z5CCET%qRGwECG2b4w@Rk}fkN+6Q!#l!bif7ol~ zu?K;K|4+CkaEDT{wj1z=5d((&@BjL*|NgK4iT^J>|M!3WZ~GN7Ow8G&?N5i2lMzv% z%v}|{WJ@~l4NxGS6@l+clz;=3g$;uLMf*GllpUcLc5v&{hdu6pM1pF(gET*XE{tBl zr_mS+1f*9D2_cjZBp#I2olwsmT_M&*7#F|9y5JZNw{$3{B!Y0};Gh4k{|K(+!T%zd z*6JszSNp5{ul}iXAVB0?X&||Rsr>B!sBdAncY%G6P%GDLl0R=U28)&NmUSAEZ|-Mt zMrc=z6pLCf0vT`NMEDHO4681&cHTSw5Xb&|wx*`L8oPJJSq{yo(w;0Vt%=H`#~%>P zqmygHISRL0McD~~1Ey3YUV3BLS%b$EmQUz283I%@rl`)6!L7^Gf@0N1nNMs0s;kpX zyl|jXl0m*WVHWM^)L~#3u0Cb{P<-aqNxul~|1iUlGV6SQS`1L?X40_CjwBfw@IynC z=fQdsAQB)d6M}67L97^^P~1YwPk=L`+#RpRhzhExr((~pUIR&}XCVqxvE%r2b8k^V z@dHA_FIkfb2{zIMK|Gd#Pus61Oly)1P4ys~P8bNX6Vu5x!&p{84ePixDpG_UoLM6q zNk}Dci9`Z6jCm9I?B&sU_oL>H8}&fzHR6j0lN8;k%_5e;^CTNGgr#taFvW_UaF4nr zZqnuX8dyKJqS$%GT@N{4!;a7-Xn%NkG2+eeqSNFQJ~`J)R+Od?rk)#NoRT%cv~Zyv z?t^9r$DKxW;Y2EIEV*rdI2y_p(K8+i1Oe%}O?;KHyBuA+;enD|AT07l5j9dvH!Rv8 z=wA@ITYjWgL!6Ga5b_ucTK)7vB1gW#oDQ|3+DHow`Fhv^Jq^*HAcp`aD@e9M((?(# zYb4aLnr{X@#x9!qtV>YlEJ<0>f7T+@5T~(fCC?IR=gC8;V7>RzaLv;Orc;$yrXgI+ zW1pY0p=||jNTVNVJT7QL=)26(x*VcqbW}nId@Su#V=>jVP?WLVwX<8JLVpDx<$)4 zx7b`}7kZU5#2(^DBa=fN=#P~yP3Umh~A zohbyD=7L^lith|w(wnS$N+{KJ3GJRJR?c1{RG-7hm6lVLn%r%L6knufOKL$v$ z!wHGXR02qd2e2{?E{ZoZBo6^e@s=T!T-&*QX|x0eR7+!R2=>}OKK_!$(r8q|P7UEq zn$(94f#;MoU_<9d&S{90nFTghIf+ITcohX;Bp$JT94>T);)FvFJ;@=9WQ5#%9Mkq> z%xJA?pM(bS8@2xiG!^*s;X!VzFWcX5blwkWmxmkyTtx2_TvSpQ77{PEEBDuVW+z~FX{q#;($K zlwMX*%?Cv+*f@o((UOa-;Ng@SvW<3IM_Q}|`DzFzjbUzb%e`egv8QTfUi_k?sEu=@ zvR}&eoB}akkBC=H!*-I8x;H4%q#mfsUIp{$_2xRsn^Nq(@u{mNnUBv$s5XzpjaUQ8 z1udwb!=a#BR?YYHd6a)@NV zf>=oNBV|1J_{KdbMJV502@5NM5xxI86}^AiMa5&II_zsUD-WopR0xMxEt>%jTJf<`M|~FDLY%As+HTAI zE4PkRfd+Ut5%dpS3z&C#%y)roiONSgd<0}j7c76=LR=dXec%Y{k5CQ!deQj-H0O-u z4>xgs(Js*u97ue{@6Y>Z=eZ7YX!Y~4#mv{Rn;dj9JQ<_7#mb)v9(_st_44xOE=rp2 zto{uP=6z{P%A?hFt077`>ysNRGx6qvP{>30N?f5%v<9|%ivtSb*PX+v^^oL&tYWS= z+?`OJ{A(*?2PW$EsdMZ#zJ|iHiUs1j1DI=K6v~r=B=B5}WN}6)BsF0fZ1eb342Q1v zMx=kNwVfdbJgY3=zhI90%I4oWMWDjr5Du-c8lXPetN3>*+Tf&1im>!bOS`QfCj%2T*a^99FiThIig!@fy8#_76zo3gjyphQVHkD# zmnyP@2cp}K@Fw3+HL+W|bX#O$}l9^v-?EAyUJScd6xv~HL zx5VzA--Z-N$aJL6p%jH2m{>Op5H6Uda|LQmRFN9zp zc{H#FBv_~)|TjpytKHTyq0mWEq8_Ow4L61p_Ac=Sz z45W0hLM515a|1;_5La?TB_(yVN@rr|awS8jsYQ4wt+LKd@ zlbm}F>x-HRwRf}mosUpJW|23s;|eC)l}v1(TSogjTrfvCm?W`L)=Zos7%>TU@dHAm)Kr3r%6!B{ z{GvOpQCuE_n8ny#Tu7YOMeePGH$JJ}Ehz=r=0odW`)wpRR34?>#Sh5(> zeMBJqW;TsaVKEGBBr&iP83(H@0kd_)N}2q;crWhpIqnRF(vc$%?KX`1u<__W9z7BQ zyu~c@sG2glMyy=t!iU19V)@cN^;O_ zd&q-!qD-$}PJ)UA%n4~QN@}SLhw$A)eQS4}N?cZR%EYf{4P>r%FMHWJGTn|ZPIxf@ zB(GLe#jwPAp&#(fCAqr^o)KC<;Bo<0h|-9x&@mH^ao_6+ipSK%47HjD5{TntpN1Fw z!~Y$2ha>)r;@@^f;~M+_H1z%7QA+ zD(5v_T8I&Zf2mAUVbA_)PA`B*P=!jpINb67I(oMQlYJ`j$*d=dcSgPVpOBLLlPpKh znPT)@77Lukiiar$4S`9tVU^D_R`x=1jS@Tc56Y~xh7b8xVwh4j-gF=!P5ZP03-#xP zSX^~UwL?Bw6P|vBL?!5O`RU+Vjln>I9P%?CcR$I~5P*=!<$~Ew{z{*v3A?h$Zgv|72AL`nyM9B4 zW%4g=6K0N+G29MmU5bRIJjG!x+yyli)~cxa1XyI<1>M@iE3y3{0b~15t}o@^lbBu4 z#}tq+k%r5^EtZCVUO;cTxVmT~4CSalfJu`NVYEc$N_i$%_cdX0{DcC0td7t`!Ej`# zNI#-AYvsG{C)^^=;tLf!S_u~?FhWjCO^oW4DlqWbp@aaonoB>5vFCZ~`7(80SWg!D z_d%!C`DStP`4V7BCLA9D>h50F`F&fw1v*YL)IJ<10wZcLq{%`Bm;zvx=_`fk9rR*8&71NRXA2<91GBQYe?;( z@uvpJ;%I~zjqdOr%;lg*mP9|6?5O)?=lub250iY$M>F;DW8M)p}Metx^V&Inj(`$r~ zg$nHv-M+*(uDT|Og-F3jXeyur@wE$QSSBduBTVp1TD?^lcFo;=A@OdIdxC@RJ^=BU zyDu~v3g{7%cSNpsLLD>)XWpwTicX(rW@%jNy+E2w28Fe%W^^eo?(z+i2QqN+WUP8d zkQdMovaehFkJ^9iy#HpAe?MPt?SHs9d%sAT{rS=2^Tp?Qys$*`1a}6naAP#%tZa%~ zh&J>b(bfZ3&83`c(tR(cfSHE}cRlWoPuj=uV!^FC6N0K?fOeHh09T1+mJiq}HR^~hC>LRb;$i8=D#wzxwcel?m=u%a;82*4pvSelC&U-pbT5*}0^Gk}X)0XMUl>M|TU zFcU|uuZceTNHB81p?QmfwA{d_4>eQ^1rqTYfq<#OK;$x2>`a+mBBAq!6y1V`p_9=? z7ZI%W#Byi++ygMW<^JUWqyEoRzQW>ShF?*f&61*3K2z!}7*$|Vx27dJ&bJimkb$G= zMLBHlU;%5)8Fnq|B5kAAkt*YdrqWo>3vKBv8a0HoaC|jnm6cF0#0!Z>zOdQy(2;a*AOY>75gg32a5y02H@3;!S=^fn)Jauy9^@5VoLyh(~ zo9yeUKSBO+5@2%c^|sQQ!Ut(1llmANKOU91n3|; z!dsv+IM$46x8Yk#ZEpoxWp=f4a@+`%;xgNHGcMFv51UIJk3Vk#@tG^z9iYS=Vf zL3D0ED)PXoPRt_r^LTNj+95)iUN5jgut(3~&^cKEvqbwz{l!k>En>ZY@<_{kBE(M; zC$9N>YQyn-ozPn@Kof*rhtgUhtOi{-U^Eq$s?Ia^iX0YXNyihS$g)eKO9d#&u{oQ} zQtqQ54LD>s$|>cZ(n%{c(*=}r53|BzVIp~{M7-mWd30H1|aI zkQENsV>-oGo=9-YS9k47c5>`eT+uThhw;@|ZOZ=e@q6lPY1EDfTx-%>P3+X;I$&ii zIPSnLXs7#x!jL|c$Q-0uDwFbk>6oy#GHQ*zIxZF8!LD*{t+B>moM)*%*8 zXM-7fXz=nfg7>o?f99upTcEV(+d(xqHI|@BUF+hCDRD?Dl0*>-V-nimZGaRyjSbDj zSZ=^v!q6rSSHC>(hCEc+H5PzVm(yh~^D0$9PKYnQ&zvbuW0y(TMA#7)p;8V6q;ub* zT%4rq7aaS!<{;MmSl!2#^BD1MDvieO~YOaoV04v(d-w+0%!S6!vQUT7G-6D0Tcu8tXWqr zO$~JU~3so%OP{Ksn@QH*ol_j;Q)}IZt?l&f%kh(fPL&y^E5p6-P%NV}Ix*4b|?Z4ob zgOw7J*%=g}Ci13{rsv{?=0JQVJXkm7l_?vNB7Ta?5?D}xAF~moe-!?}mBL${U9JIw zbFsKFWH1qzbz2$HTCNN@+N}iU+Y`v=MGS(`Br7d9XViFv2P`E2IIfDQh)h4OK)`sq zvB~+m| z8ppRNCQ~RcGQ}^$0-QD;ikMEj*Z+0<-ltF|Z)zT#QtpjWc%_Jw7b3FbasP6Xeb!S5 zYzS$M`4ea+DW{UHXA z%fPDPWmlJ%dhHXcOi&P_zQ_ANOnbZ3kOCHo@}S`glaU-#>aa$7*kEvkpdPrf?1|0^ zQqx6may~lYooGxtl9MY$+6L>iI$uIBszu$26M2G;6uJ^+mz+nSnMKFN)KODA^YG%v zw~r9=Ook=AQS22DVBqAEaTfGQ#$UMRGg(VA)MWii@%^3&IoIR`pIjSQJNtTXu>#Cn zJ(7WVkyQ(e93LSK!0z)v`B<4F#Z$j>d?BFsHP??;I%hF5S49Fm6~a_jkR4P|8|ZY=yIJFz%n zrP|u9oZ4Hz_)eaA--tC~woUUDs+HVx%h0rw&)djQJwXv?kpZ7$J;GMAu30{j|5dC_ zIWN;0td(&H{1quLYvGxeh6;%!e#IwA1MG=L9J!McVUA~iCBMQg7u98UqcI6bqtuO% zcq8EpsI4n3#sx+84OS>2p5O#YAX20!RN&Rh0gyLS^P$BeaS*@G10}n=pTMU8pBbu> zDei0D6_3r*(_p3&79Fir1}s2pG33f3rdt5cxHO0j4lha1Qm&VSkUG1}O^nVTG zO9u9w!xY8z;ylH@BU1(f%?#JQY=r|{=FCP$*tzeSz*XD~l9P=NargluMJ52zzc5B~ zzu5@}E6h7I>NMYL;F^I+dws`fs0s#HWtN@qJKk8 zaj-Bekh2$XXzquCZIU96B?~JOOBKDhGHTMqdSq64OCBg9@q5v$@G5LI`C8E@a4#^z zsIv_!cGUY7fS1i#s5e_v;nuE{8y8w{`tyVB@rd<6!h_m|)wdXe%cei+#b@>XdbxyI z+WLZ?&^4iT7-sn_wt;?9fMi#6xdZ)Y14&jB#Ea$@^U6 z;YyQNqtRvIQ{P+#@~;Wv&K|IbQWBO_qaGsXe}r;ZA{5(JZz ztic#4dj{na|3!If!5kpHlQP>#nuWtr5AgcR(*gz~?aAOOD^RaVlT3=nF;SESY50^|JNngsDn#><7NIOX*Q&M}vQ7feAFP4|Lw)Zx--mJdD zF>DiVFjkKtE(o3J8;gc|pA@F{uUkkRHE5=GyzZ@7su)qbEH=nQ4qWvaViol+hq`gV zRe7E1)KQFz9wPo1r=^I@@31C<&^hFJVQue8OTnJNB!bO?h|A;U9qDKl1r@Mi1H z28Mq*8pFcii=rk3x$@EJB$J-3A~aZf)<*?L6iAf#e8xz>TieAii0Uu`1D{jDk|0s9 zQC5MMS%G3d4cwWH8r2Lv#a+9GA{JUtsLHW24rdtFqZTE;Dve*O)-0(}yo$=E#|U2- zUBt>3ETT%n7>zy%F*^RPE@6At0>dzGz1?}mU|GG_REl1Ua9mx)IzruMc+3otV?oz9 zws$txR`)j6mn8zNjXRY60GDM(R#P>SU@%PDP8-gaiD=-yE20ARkM%-J*zquQZ!A%9 zoyO)V3x4_q#iV9XEAK63y=2*8U=Moj3`+chx55IY_XCHw2+a3MuxhCBlIr<@EM}O5 zUfxmI0aDalF6_%+q6>b|Ujs(SVqOV`B{ z&cW_xKu3{$(9PxG>I`r=-fM?5llTmSprSeGMvCQv0w{3d809ff114q(4oLK}PE5mk zb&?w6Dsw->Y9YaqcLvIuX}%)~=~P`8HtJ79CY>_oQ+lW!B27=6vN3I^K*(Swq3o9A zOS)BI@wXzhjY$vNWsD-ZpeNKoLwj3(vRQ7l+~j3<YO1QbNTRoi*z20RT+2MCvIoYrdA?s?Njs}n%tA|o|=j{i>wmLT6U^GH} z#L*-{2aCvsL-_`PQ$0S&*>HNh$z~t=m%bWdGZF-6^Cyxri>fO)2danGd(5$$;??%k zV3_$Z+B}05ofk%!XmA`aFt;oG=+SSq7)bZ;6j$30bXLQrDj=POHXIOaUY{h6N8zFt zQ!qOS-R#3O;AVtbz)Z8HuZa>RO)MoB*FH}RtNNyumvGf5%Nb0Ht zJ?B&)LbbMw0Px(i`j5QQ9{wlNX?NVcpurd$6J@#Mg;#TOZv<=qCugUKwmc<`cr|9y zM%4oJBu0yba~M&T+Wk+|@lw~C$l_q7Flcoz(|KhSPY^kDbqSz&_tS9BL@N|L7*58c z!2nFzz%X%9MX=t0k>U7WN@w#5q#7fAFFmA;;FrbuLhOpW?;Fs_WocZGG?$1B1d zAl<{Y0jl>j5U`Kr7ch5Uz91h~%>dk8s*p>Lm9AoMt{to5J)`j3QXPnYGDjh#X*G7% z9^%)e3p@P)v9pq=)%&=Jc>*9`WqhV<*ML9#gMjAq;pnDM=`K)Y>dKlFOMI)6AZB5s z5V^?(Qj$h%`|+TZfJ)wZHN|%v4=jbvkrnk--ET<0px}^*v{K2PFt~O`2P@#lorFW6 z*usm$!12pxl8wB)v~+PDA9hCLGZfL+%M1*hojcTK&rx<-qaD=E{P@(FZM>l+`cN<$ z&QhJ->d(Ub9g2nPGN}<}%Ke4v{MJ>_fCCuJ^(SN%l&OO?TitxDdMY|4G7dN0^hiN1 z)bPfaeJvzU#~OSEbwS4z$TdJW*rh!K*By}D(M-bCEvm~3u#TZ_<1z^x><|#=(a(IR zu?}kYEhOI<&7fhF&Y8_y@^8`;P!V=oDKUQzc&j?b1m1!t&qw(hK6jv(+F9No>yTo* zx;h&_!Xar$x0nEL-Oq%Qx=2(0h?4O&A)h>Eb%x-8xtfXdOY=zYOp+yJi3{aZ4gKii zVS`uu`rrVy+MXkR5e(XDzDbVKgvCc+Nk_d#m!r$pf&d3$CGnTwl3Md$E~vN?ZODHw z)TVQ+Rf;w6HB}eluV~H6g3DUpkyLK3E88bVecQ?#TXX>-3PJ^Z{GmG}Ub4oR}^G`Ik8VnaqCve+Kkj~}R5wbI5*iwN; z5*HCzDAK8wZF^a9GuxF<1ndW~;l6=U70fq~2bi|nw!FbR6NAF@Kne7isu%(ek|PPc z@hqfx9wZNK4&c7q3I%tYe-Y?`*03-ttaX$sw#|+Zn-53ob4j}xjVTS-T~1i`yTa1&EZBmxN1)SK8>pw68cA3B#>fy40{!u& zNEH>r2i}#53=dQE;b)$z@E}l={OwePA7x6x!$=|As}iUsj(erE6cIvGvR;D26pVmG zIbZVx1}Z_s0DTe+J27QWY8(lbz_{NZ;vg)%l5v1rZI5LrNOp>R+va%`kXr1vfEi)r z(!;XS!-dF@h;kn>af=?npP5!JuY^w6#<OtIW z(>`C|0v1*y^Z>T#3v=5oBGse7d?c{e)(gCXVYcpSCed&H)*h->ul;rPyN$!m zH@kbQui#Xz;Eu#9H@^`hrWNp$+bVyusOMVVdnd(@xG=_(9Mm{112*!|*jX;tBa65p zKB(YaO%T-JtQ!?=qBOukeCATv*?Q@XmzP!PE$|wA5Zg=d+sTQ;g`}rcH>kP2wZS^J zk-z5O!6*LlwUpE@A5PW+G1Vr|2;}y+z1%Fyn^!PwiCq+M7vCZ9Qy`vls;hQhD;8J> z6tG#Iay3+^^xLz9E?UizHAss+y0vs|0Q1S~?j=4x4>c(Z-`5nb^e$FWjk0#^VyQ|h zbh4=)yhud=^x3tZ_NmatAhF=J7G8H-ONNO@5aR|?WR)S3eFh0cr8${f63d} zs9e(EB1`BhRN`3?#t&T%qGiDhz!O29LkE`w%AEEstAVyY%3XV?eTvO5+#^hPf2)IR zu7{0I_Ho$hEH;p`!SJx>Za0e&kAHhOOmuc+3_vrQlcj@m4^ATEzZzsD$F@EPbL?H> zCZ0#aCe;88K|n}g$V-j%5<1LJb`D_Dk_;pfd9ps52-CL5$#o2NU=ntyKFve>ckSJ- z{5(NTYC1cbus+1mh!&i}K^>A98s*@gTp?c~yI}bD9l~Kft`tABQiu zc6T>l9YQmvFa=)~#f)U~?G+wz2sX7hLVJ_(HAxgY9qAA8b7Gi2oQ%9kqap}|Xtz0p z-%*g0k2xm(;CLk22RO~VCfT~1)Nz{#TCmBqAou2)6#ru78zTs0eegr2^WR`|M*LKx zbr9}qY%J(JOJ=^_csv@ftfFon|7eVk#bW`lhs;xouYa)7>V9mqzlVYD30aH zPOl*S@1f`gd_Y(!DStspn^Pv7;d57_+AF^_F@GR}P4=6b$kcrQ8;pDGf!c&Gw)_xu z;Y7ntQ`qWs^uRlX#tb0GHQs~MPOuu0;!3g@0mMB_01+YwdzTLP#497hyJ0dJ#}qxe zbV}9;B z`xS329ABVMFp-Mp0py!NtpX36}|1%7!*?ugJe#x9|zh zF;5#aej%qIcOFkajF37R(teRTxUYAIvATjz)w=M77ZVHA2HGxSoPm7!QB9xFnguC^ z4*_!h7s<&X=03@|2~=p^Rd`EGV--h8XpRZGxPruwbp(B`;XdrY9qw*yukNhkPdt?s zIZQkq0)cCqwg`9AQRC6y&sL9+OK^n%X5Miax0NF3RshG371GiK^O5E~M4*8vCT&4(Nr49SMv+yK!Qu!|9e*h=Fr$%lV`3-803j2a+(JF~FfW;= zpu;(K{#7CY)#o;^>D{2_3x_Fy5!f+yLnk7_kq1=6i-xcby+e`bbXSW5s?3tE&U!c^ z?f@5vqX5ag%4h)$#(`0SLx|agao)zi2|*W%C??$Yv)Q?a?3^0K1^*OGg1GK?Hn>Gk+ zhTO^up6$lcqWN)r&d~~uAqqVV{pzTsmoT6c81nbXbZV!HGL{ZQsr+s|ZP^>}L%Z;h z+v6jm_7Dk<#SD)b(4@uFLYxQX~G?NYEHJtgg4!u|lE}7fqM0gS% z^GuJ7i_+U@fDKk48hL2`)P$p5RN2%cC^p42lu~jWVeC8!hmWBy3abteuJL8jM#lY2LW9@JWzKR}cI{AzM+oO$o>N^U*fIbK3)QAoWtX zlTgNa9MTNo5SO)06$!rchKnLx^N7it5BocD8O{SB!b9O#?Qdn6#Y0j+@?B9PLN@0- zi)65n!wWCSWKgG1(X=25t<%UR1gMQ0+uv_{lZWy{a>TVI?Vm)zS${0eYRlXZ;ok}a10f+Yx z)F!RR-#)^hzw!TQ$8C`6N_fa{@8aPn&^+!+Kg@cQ!{f<#aHxf8#-9~2Q%4GbHDKEL zqG&J=k8NZPXB{5*5bm2M56cd;HdU4(F+Uj>P=#`Nd41#M>f2X)hc9+k->iLqxcX{y zbypVA#{!>AyOi;^PAOK<%wDa1Y^o!l`bVmK2uxH#fv%WfpZ5L|HU>4D8JK26X7iAL zkD-MbK@&=2TLkKAcs%;$y!)|-m|Uc;*{6*66Mf0-n4~tJZXK3yft{1G+_i35E{W8& zaB^`k7a8R?Q-=)k!Fpi2NqC%N6c`sL(`g2LF7(Tg+DCa$p)?g}r@IT$p~w-*$VCu! z*!GHt;8Gs+=9LNN1y4R=+8IEMn_b4&*z+cLOC#$hr0It>6;V-OgFc2kL9HReX*Ey> zV!{o+J(-oc266e39nwe&X%06^5lTBf82(B$`fmB|zy+pZ(YGN4W zc_-T}D6iZu31OSvHN2LtVFlTFx3RO|T-$mLhxf*gdc5&y7YW2ol)KDt{QR~=7jT00 zh96ti-#>%abkhg~!<~0UY$^MH`W%Z4;Vt|}$p(&lq$BzH%wh-31xH`xz~x+eNTV>l z^fXyt|2IVSY5scsH%jB>tbM|xY3W)m0#P#Mg!EWQ!rm5B)BRYpOf=6qhaC?&LvXB1 zY>PZ4DUdGJF)bXlsYy?e6!f9+Wq9e#!uaW&dqQ8W_cAH60CH_LVcq$}gyKHDM#gzj z^Q}wVG2&a3((OetX&iEl_V|5oaM_xB`t<3>*52GYLMaB_>kLT%lP=6Z%us<6rWwhN z?h#yamM0z)3SbbC2ms`4^-iW_e%K^KLQZf17=z&dRMN9{B{z^Dwn>hom@ z7=8Pio{TikMIxTCfxv%(9{hp5KzZl(1fT8$$@eYPWH24N@KjZ_!b3lrP>o@4+#P1D zt`gjeg|<;CJSZz;g|>S7`bL!?o9Y(YP@`}PEu`dxLjA657cEsa*&OysoTZ{LvsSfW z)M~^-)d+~K>vb;x$^;riHFlv)o@zkE!CzsQEA*1QH6f?iVsq=@4FsXhE&Z~dKW)Y` zB%hO~^}F!nhl(0ZTdb-JJ}^|VqAxfu)(FngThIvFr*ULi+UICDlT_}U)Wp{6dKSZ~I35F*EeVQO-eu;D$#iSVR#R2tWk(MaJwZ^lA8@U3!A9fC5jyf6SBo7`058$7fkN zs*OI8{7QrNZcNYSP+)j*5d+>`9uwjUWJRy8Ff2V2ljp|3aJmKiBV~t>!Q>n&go8k; z$u-#AqBfx-J=R6Im0=3)$|prdbE_yCSkqusRYywF7SqsH_wZka9za;< zeH2T7J084X3zpwXy%uX+H4Fx{>1?_esCkiAOEBicZI!y#Ny{g3?# z5_3l;?E&GpnglVd3&-80^f7&4GQ`h2GGoAULnCmK_G5UjaTFh7IG)9a@Kqi%q!I#u ztLl_qUPg;GZ5(K^r+3{55prH)brnY)0#z`~z7TB_?r@ON&)4qL4t9oQzkk2Cw@vQ; z^YyJU45Iw`I=9c|QU=}G+&i+bTP);$es$EAmulyV?hgGzLeJ!aT}d90I_r7MNCGka zl+qSPQ~O+QlD-xEAVt8m#mfRiNK8-WVWeHcz4! zmM3r1gGC44!AGz7jbn#h(VQbI34gO{L?|0M>8Y1fSxcVz%3A;{pz_Pdk5HUjKH6_0 zHV*My*#FD}z%iFhgwrI?ty-XNm7=$sV(C%;I_ybgWl-` zw9uhN{BUFI702G~_4E#CaBgHHYPvduFR|*x_0q?0m;R05w*`Ejd=!pY7ogBn1OJgV zW$1~41$N4D?=78-j+c-I+eg`hYzd_fkiFMK_B*UJC?|4p@^7&Cw&NgejG()qErgcE zNY^HkCD|~yY~h)J*tELXfi5d3LOc*ky>%g$Vl1>eF=~u0t4(5()#yn|yA}fYyGM_O zkSfM)2J)b+sGCxzC=y`AI*tv9ln@wEDYSp`khdl`<{hKtz!VJHZM)L2-Ij<|Ug#b8 zN3w+TJRdB+v_DC3$^APGONccU9OZ4n35S856}3UeAR#f^+={V4Ec>uaV~>Zu%eh0W zgYiR3Y#koH+~c|g8- zo^CaYo4b=^E@xhEIsZUFu%)MkVFJZ`mD>ivL?-8+47_{dWnO-`8An2zpyC~QvF^I#gbi;1R?=-;;1xINxw)nMVO77 zxEY)-X{d&Jq^4t`#B;OJHZO(%EraH|;0{O+g|JkKk8!zCw@S%qfxfN&R;x)i{9^OX z>dxN`w0QL7Mmg#ay9i#VYp;H!*ck`P!`y_ee26>N-Q5Y|#VODVfNX7Vyus^3GY=o` zZTyctQU>hIpnIm$tTa1SjncvaazVjU(PCFF5XS@7{&Y!yVtQEQIJ@RCtMvsE7xT7q zOUiJl$QS83|J^@OD=N5hZ5HA8A+vpZC#@!08T8`)+4`$n%i#dGbPNV;X>)%k%i{)X z1;(uyg3ilv|052b;I>u!fbUn=<)|kM&t}mZ(6VZ^WY@v4<+>oIO5GTk(UKvm|37R% zymIS+FuN4#}k$IJ6NCu?LLXR#nez5R)(jB0KiJf8ol(1EHnrq6)dRvPD zG1oK@^r^#xi$kdnt8j%5nuyenB5LTFQffB2Mj;-FyU)%fUMOjZCla!Ak(~)Qw+^`- zp9(SP*AP!uY z7G-ole~)q7>9}(xad%-PJHY%V)Epd^AHiIZQ<~fcrv~yP=8D$7P zl}EXLk01b;6!`c9DzQMQ99cOnJjx*NSrwE7-XJf8zKINx4%o8{ii;eNCxdG^PQ%6* zY}v-R0ehlO(p$+J?4gTMO7Sy~I|G#iS>-i|`DPA@`XsFyLcP|Kg#koXi@35*1gh9! z2q==T1p-aS5Q$Xps-qBpic+g^#mBnEPg4#97oj$}iytPLf4mwna~c4#zt`Pi zdvn-^h}eFOlM4^KlL^kvEUjN72r%VIiIr20c3PdME(SMGrDbNxd_kF5vBXX8IY$-J zuI3&IhR^%F%I;Z%D;~!bStK|WzGs^jJ|d;Z-LIdLNANG%|GL`y;+5n>&W$Hn^1Nv$ z%RX5ExZa_7=-4|a_g>HNLL!s$3_F4ZUxeM#w&NRf#t{S--M;*paV7^ za#4fs_&PllHfcSi8i+z{R*V#1{EQmFS>s}K0!If~6juhkt9yEidN^uaKzY(@xtXIr zfzSz8&%)Vg7H4CvDd>w3v9dLjlO!yK`O@_9KxwDHyI_!lwEwf&4UW25|Cs#Ds2MUP z7gh~edNSZ-G#f{glCJCnFiDsqaMxw9oGpo1>o3z6Xh`D-5bd&sS)?oX3IOmJmN}T5 z02cGY&)cVPs^c6HIZN}Ja4`|!;b}y1_3aQhr{FNbNuy#Kn2fXNQd+36=KI(zj1Zzc zQe~z{knkDo6iIKjhKiK2E(poubkP4wVmZh~33Uh3x|$9PWCB89Vupx$%DCg6zpq3h zD-g}HpM!#V5Z;}9X)HTbyBNu-j*ydP;DyW#jySoAo~M%YBJ<31Oh4=vjzT-0#!Z3E z@1tZ7wgPif=rgX;x5`!{a-NI+P)tcLX^MK*c>L(e?;2mrKPpbPz$s4g;?Fc#9nxTR zCp5qdOJko+(qt*`;mArxO-O}_$)l{=|WsFn84xv%V#toKdI@KgZAkS^11f%z& zaDSz}iIDjUJRXI0gaqr{838fE6G&UG&*J73jS5vMRL9vzZoXdP@}B#$x^45%ram58 z<3HCLHNYf#k|haGhG^E1hgQoM=5lkEgZ<-z2j0(%dvzoT6c&mr-a>0F`T;KLC#TA;*P})semf7 zYUSxl$`V~ca_Ox#C@!_OYn)p&gM(4nq=XqjM?Tq*sxekt0f##tMH`sNKzYNQM0BfL zG-@Hj*A)bilj955M`Fjgy~ZHK-I3gn`b(IYWxnOg&jeoez*=$yMDZs88+&&*rfjq% zEI_a*Td-&=^d31zG)@`xv`(2h+qbU@f;33F)5#h@>omd4yf`(`5wxkDn3a7cb`zJt zPcgBJv8R9oL3$z(87wbXH(!OFjs>^V#a$X$pNsC#?uziIT5CFnNhD-Y8pffm4vo;W z%=@hy?JIgnQ+bGMEA+}L7^ON533Wj~%t*l3 z7jOskFKPAeV>fkZOOYMys}Zs&X&O9KJ<_&^$&XFGk$e{4BHBawG~PxKLE&4*70mQ4 zh3OJ>SnAH#vg0Y9>p?Am}TxTh$7h7AeHdfy#NA)Erepec;3^uHH zYgeeU@QN;5AeOVkvtv9{H}3cl)%8UJMO4kx#_t|Idh|!(K|ZLpmgOsa89of$g|zO@ z?ag)c_xnfRK6;Yt5A-a3B~8QUR+hpxFHF>ID=;Um4@1Ylla75`TXRrJ(l~Zq#!WEK z=iu|FGa%QS3WPQ4-8Z$TgZ`svc-Waa4u z2?i_z`fUE={K~J5uc4oOz1Zq}{d^IPq{*)fi@#mag<&J&ucVku@IV5Pq?n5UQI;}7 zYX~dtwxYX@tnXt~2gEI&1p){!a>(KIZt9t332-^yc<>3AAMwtjQs23kY5yh%RFTNf zl5Np}^8;^txJ!H}TG1_NMSD239#WPcLPE>?P(Lco!1~hYk+ds4yTab!ZvSEP&HC04 zyS_yZU%cIXwT^rgaJ!aqDFirQFoCRny%ve5;GPFLF01x?7qCl|jOnl@(8K^|c zqRU%8k*pX!$(JFiVkvA~1gUoBY47AIA3d4k0aJroXWj)L>`nO+qRqTz+%l#CVK|6s z0FFUA4QwSTok5wejk<#x1HjJb|1@`mc=HdCKolC04Qim_yDtU}J1;?_+oD+mkG}n4 z5NY=%m_&l9fr#(E7(DE}1WiHWsewycHBcMQMWZ~BR=Q5_NIk-hSYo#_jek0+*-f;F z$A)R^VivL!SW5E%iB>JM1O|acG+Mtb*k1Z=k^5jW8Z{6jt|IycuFi!a>}(9FiZ(HR z@cYiUohO9nynlupzB;85i{e`i>I%)$z$AF;M-n+yjiOb%%@)^J;{IhitpOShfJi(H zm)cRG=s*8~bGBgF z6JFP>g?P5f4|q0Z)d+YSSH#EoUFT8fk8xF$UM>)<3dCRxRE0&|6$x#)=w9>QIr#fg zR9#r;DXJ3a<)i^=#Hg_|Fui&d{sSn7QiYUH7Q+FTUAoG~OOTG1fd1bvJ%N<{=s%wP z$2Wu1Y{}5&@;E>`BrRp5+u+<~(F&ZCuiNVc*O+{;u&E960`ipwL1<}Bi=INz!!QF* z5K9OV3su_e3hnAOfR^hGM4PqC_f(dsJt%NWh)iQ}CVL0xJg+QYo~dS#hx5*R>Z%EZ zxJY`a;d^>gO0nXD`LD7A++wzkh%uYqSOC$Tw=r@dWX#7ua4aJFZwWqz4k{$7 z%EGA7-^6kOiw{;GVuFlk&a=d%K_LW(Zla#(LGQ9b;i&h=a@gCC+T-5d#{=Ib_(&Ua z*5h%a5&YBnOksF1nbXtTP`s!WaY{{+IiKZeJO&_JASbOW8Q7!TlpGu`$Q>GtkhD`F z`+vnn%F73&NtL7dn;50_7{l$vm*H?U&E6x0jzL~wpui9#uq zW}%D@X3r6*;hj4{c!My7S3Qv7RuA^w@J5(cysjUCt00hZ|=BW?eaj+sVMQiE}!sGT2CxyUl?WwEY5Be$Wzp5VCvgv<6_8 z^7?ry9SJ3ZM#0HRqeeg(HY?9DwO|M<`vb(r$B(KJC8oHbQU-fn()9cK`K* zVBir~f*G@8sHsVTBKpK64nthKA$ zo}DF7SugHvxmQ7@dz3BuycK(rDnDCN-r%OPKwFa3G_`Em)dg>sLIx3Uzbw}-vu9X3 z@WWy?tY9jRc~DwD#|o37?{K4)SJo+%Cp$qK2sAQ2j?juaW$S1`l@4nkuFnc}de(c0 zx9dxQw`Hg?U`p_$7-}_ftF2Z&WAO+E^V*HB`W_KYtFPW}9KP9lvw__}a?`=I_ze|b zm}x+D4eNwAoI~MeZL<)7CK)kaQvTsw2?XZj^ZqZ|Tu1ny1ir%f1d1&YX}G)dNEjmi z_+2c}1||pjMvqBlZhrTdn0FpF&PJmXk&&=oi06^aB3Qr6K>2qPTOKYJBZ%FpAUd_E zPXdsNiGl0mJ%Y*8f4g~ciCD=#Qp8N-$QnPj+m4H$u!a>-L8H3D@Hef~Td;!F?X@c& ze#f{tlLyY#@MH;xO^o1@a`k>W@z}?X;G>~sTOE%yYAG6;c}KqH-4?6 z)6Lz()t#NyzX2tia>p02k|F7UfvG_~gMc3{0)b@;hV*}@nAZ+u%v%U57QX`0SvIW$ zf=%j^Od-gofZ!Y_SI1Cepa?b&M^Qtd^ucOJUKRM6oi=wX!IgH@PFY~C3}FSw{bD7# zAXP}!F1j9XUuG+v4`}P3R%=TutBRzDL`bA6+r3e`Y7#4!7F4x1bigi9LL9Kx0MtAL z^d?{Mq27ED+H2^nf1xj>7Yk}F_2H!pIFg={r6y}y3QG|0Op%SGPMHkI!+@1K{Vg2h zOwrcX2{AsCf2sT^7+OI@1?P6#wkn%uJp?RCEgU+coz=h#t4p0V=YWJt zDg5TexRdP7#ZOD${*#651>j3>V9B3?E)AMkhgN#ksijhsk3AtTk=4>e(U&Puk%nub zJ02LU0oQT}P>5kkD{9$WF`yh?^v1Z@myI&N!0e)XnOtz9l|77C%`YJIKn|Nln2~?O z3F1pE6A5Cl{CdHyBo|1B(nAkqAc{=M{XRHY8RH#_gS6(~!0KTitD;}FzCf~oMFy>i zH%KY?3wU1SeMK;dRv!&sxIR>_TK$L=dnv8r!y&#BGa^@7QK|uRis5T$rPYb>2PdJo zv%u%X5*4r#eC&^|vKM@ZC_aK@7e2%{aMvDnv0?EskDva~N)Dx8^QgzSkO8EJ1_~Cy z#lTCe@tbl zKk{e#SE>78(J~HXH0QB=kFbJX3et%yxB5;ADmqsX08RGjByzk+fH*Z6 zCCnXt7x`$-wEP2OOT35x9%SCdD%YX~QYzHx_vT`{wjVW3lG`W?Ep~esaC%`#zXbf? zH8;fSU=SY_K8c11ft0l0i_f(2OoCNmVl#cSCB}!F2ASqyFbVD@?6N@g1xvw7G}#>` zI8g-Ev4E5xM54PSrlOfxN?;p3W)Vk}JNQKEmiqf_E&c=6?giUnVrvK`)GI)6`v}e@ zN_TQv{F7w@y==3@N8?i8Y=_cPVe$p~p#wsRJx4Lr4`;A*g>m($2KOcg!6b57XDp^9 zyC&Nky@K-y!-h1qctDP&m?s#cZJP|dn+cQlExDqwdHanc92%% z79K1#vdD0B;-S3DXkWm|hjmW;IF+x?@EBzySAacFb{6s`6!v<$&O!M-rZY$ zvH6N8(E7%^a1oVAJ9Bgw^x&$%p{C;^*~c)5*!c>i%!NB2>(Ij70EaJaQ^(iR_U)B^ z2Jac;5?>%V^7tG+yret9B)2ZsUEjD=lUwyl%rDN#$(vA(9Vl4c-gsiYuA`(&E07jA zzS9JF4%$(GnqeqC3$U0ec3D97-ZV#xqbGCC0!fiZYYDqsB0p@*^=>$n6y?(%k2c`seTr0D|_O3^H6O^Q{xk(vD6O-nODL!Qbub* zZm4epE#{p0v?Mc7Fcwj7i$Vowj1>V7L*gvk*9iprGxP(!a~>Nlvsz z!lY{VuCFeuFGM0T*z>zth{~p|QWjU9`h3eMF%k>UGFJ9QVK*hErG%1{$w7mgTt@>B}=|D3eA?7Sx>QAZ>x`z%f4au{XVDrUdxBt z>JWFbuS^&A$>5@@d+HfhdbuFh%MD~NnE`SQE%@Rl5nHTvx4f<}`Np-U*@oGb1abNN zrVplx{%X7Z4(6sV)SLUu{%KT^USv=y{zIAKorYdF=vYT)7LG73y8;1^HMUp{Md8-V z@3&rWFfhUOidqY=qqHT#bG(3J2oeIx3f}zf@^TVBQf-kngKY7;84R*0M^R@tWuyv^ zbQ)Vl>4Iq4ZCH{ml_H?0fpj&nd@PM(!Z9_+KUkP*?j z2=EBRSbMeExIzglm_uMsSIiHtAwAwZbk1a?`_>KrxG zB^H5O(vOySnTa9>tzc09xuIK?7q@Ln1&pyhVZxO^gy8(^Ay{05DL}5#O8tL`fl5#fY>IibKg1EBQ`Fcsj1EYN*#3Alq`9*@Ad359 z3=LSu4Freln>#?m&h2m8R3gaP-nE(@P9BLD4bd zC*_1eH56U~^vgl^jT|I>^%)-?PDaY#q>}yutx5Q#LTk0|sFQ{IoFcHvFOk#bul+~w zMSN?v#ege4>d_*v_4KrA&>Ri5pr19Ql!Qee^L!Cil)dK~KGk-1B>AnOM9_jv2Pfkw zsaPM(l(~y|VJpL7kARB8Q?Pc`uu2#6;yjn55IBX5z?$r(o7P~i!gF!bbI+iGH5G9L z*JtaXU3B zn^kP7hmFV7JK`J)wqqDQcLHa!JWxG>{5>Q zI@2e!S(G=)tZymq-YklKkKJapQDcP0PN^{aDiZ`xs*Z3!8bjU$w^Na^5ZcbYSRK;N zOe&nmIB-n`IcPE|&chBHyzvGnGCR0LP$T765q@+yRPViwjA}#BHXyk5m5Yvl?CEW# z?-q_IFcKXHe_^DGFkOd>%;0wDxH1L5!-F=ufxw|UKFhjb)lPNZYjZlfUGx^RAX=K;BHczf-m4CZsY3R zLRT(?jXTJ2Fo}cuDNE>KAW0qF0Wu+W_(G(ON^2!PoLr4roq(4|yA?)yvNH;XU?=%j zw79!C5x&S<-|WzZeahC{gWP+lo80Jev+p{d{80_0W5F@1TJ%FcTW;`98{hQ<1 zH=w$agEaTMFn93~>7k-7r@A&u6AWRD2%$Q=C)E_4WKH#k}SZIP$eLb^iT#R(Osfr%J~ zY(t6`QL?b=G<{8LiNKME)#TcJZ|9)>+ybYUBDbF346b3=78PVy)1HNAsL9Y=SJoak z!XSV2SZtRlCtP}p62JNWr1je|DomRWA|_KttEbS(+Uacivu9Mo`_*AIpvz3mn+F{ zD0io2XMR38Lt-1sV5VO#jIya8l|B&W^GHL)WJn>Y@|ZrFUStKR(@|qag7pL_YWg7n zQ~Ic*sdzSKDEpw%a!M}YWIQ(0qp&QFvejECfa10Eb+mGSxb-3rz#7TKm-Zo#vrSTV3#XI2t(5+EWG!1PBNuWec z3u{^P7SVgGLv>*PZgNvE-03Z@E9Dwm|p#``b)%2NrIu2Sp7EM05iyeWSE-Lj& zK-(HZhBP4oLBGhbb#4%@yiWy3Xhlej7k1n`9%kNXlp zh!b&^o|80;B($Kib4}lxv0o&YAq}#@ITrez6DdPBhGRm!)>GgO=@3Z3fbTo$5I1@- z{lY5rJmjsO{|S!s5RnO!u3_q7SUjX+U!5UxyK;I#f}a9NvLr5DCcsp7YZ=+o&{lLi zDq7s4lZfOZ1$Vv+9eO3Wk79VSaDU{RCLlz{h28{bTlkl937 znzoqUuV*LasnTf{Xk*nRtIrUaGUfyVgijkXJ`8PUSH0qWi;E1RAN&A`DmP6%t4Fg;xIC$%HE6YNjh?@gyv*(FYqkcK4*6dcC~ zK2)u`r6kXoZ-|P=lS2@H*=7RUqUSlC4*JNU4fBAUZ;FK34@PpLW&1t>jivI@{V^^l z1R$nMgPkBANlzhI(8@hPC{tH#)hp;mDu{5GFpdKKMysaogch(A_7MOUi(7caO-(&c z{TTQ@XbDQZD$xKdBYc{-{>j$lps9f{&LABE*D-R=I{}b5ia3j3BKOZc?-&W%s)B<* z*h{FGC%$mPU&MD@_#(;?(?L$yPFfI20h!73GNzN4m_3mlzH=hvw2IJ#cXAt*+N*V) zcVgL-bg`2UmPP6~$R;OiOitJ3QcY5mnkqn$1+0XOnrqURS%qrgm0eWCpMj?Dh`HIP zWBtuA1#3w(JKDp3eRz|RG4 zAm@GU%(-vuhY;jzCbaV)VP@fb*lH`s_S>RdZ$X*GF|PMbFKcqc2JF(Ze*|eB^gH9U zX?3?i4P3xQRYVaX#UhAYpcIP8fU!onGY>7a;zJ1Y(7PVwS}}dYkEZqk12q#+lfDw` zs1hMcx4%O$(`c-<62PPaJPP06QM_2tirblf7+nn{<`+Wv2L{0T!L4y*esqV(4@RBA zTLKYQJga&bM`*nLlm)Z1LCB>@0t*hB0;==Dh_&<4#Usu8mq2-GQZc+P4Tgqu;nOtA zvNORBjsnH!94<%*fl2U0v!UXikv3J79)L}!X%T>hu98?Kv&2c61FGjxmWqYt?Ca`OR$UEJ84qWCtoHj^PI?~ADjwjo`yTD&mUDit$DTa}3h;R(d3-5` zaO1wJL$euEF|@3VUUg!pUHmNRlfjrX_A5=(zJ+^DMWy*-z~)2RwysgFFeJttqLKnv zQ4CUCs~{pmT>anz8)ux|dML%`oJ!uDZ&LncPBH}Kp~vQPkWN6pAc?AyN=wSt1P+i0 z2@>a&?1vsFsk$Ar-S9U)F+o>;7ANn*ruAHT1kd3NhD;z5HMT0e)AHvFlppxeYW~n0 z;5e0=EXe#75^QKWh0pvD9*wD29GyKo!X0#fIz4*^|ILJIE%ybqKn@{dKf~rccP5ns zqy_44B=OZ!tNZKB!PXNMDlJJ_K@&_2nV+l%NRE<1h$Vhr_wj2qw#*l*&0&QTN{mStSrmmi}1zP?M( zehFZ`-QC!Ev-*0Y*l9(BTlPA&N2ryQm@20YS@_Uw42PVrNwS8~^4sd3ApC;tsV=P3RUiBO8Af-ZL%5eUlDs19=jBE^joh42%ti&dfGw8}SBEF>5YBW0V?OHe=%H*gXgyUC zb!F)(XR@-G$Z}zJiLLNGLMuoUZ-i7kLvaVHn|eCyQ?$Vngl1PG!q_vhZWP# z?zd#p$G_B?xf-SD1a1)b$Xda=l3Z6;84<<2C59lB7RGu7A_lWwNDN)z<^BcspMQ}w zV2F-Y$V2DJ3;9y@O3=!Bw4LP%Bl#k|?fSjYN7`Qv@X7a_L-G@oEfA1e?5d|^);wXb zv$6fvw|n15wy)nG%5DbE0v8QQCw^M^XLbCZjISe0V%)yavoQUnQQQDG9#k8zaYeFo zXY00yauV2I(;&9brUES8)O8&}wq^llJLr-rVhvNs^uW_6fHK4ZeU&wyewD3o$)|kG zaj136_b@CpePsvl z1H}7HUt0hY*ooq!W>4zNxHjC03ufco4bGlQ&JvB8!dJ+=FvH4kmAB-NBzGyrQF5zT z>?d8%>5EwW(${gy=~VnA5WSUSsP$hV%a@C++kZBJ!b2c!%*3U}I!MwTvUEC)*Ssx{ zH_Md$Y;$*Uw5g=vRHXx@WKzUI4Z*}!2iXIo{Qjj`~(ot(Q%?8_iXO-sce_BH-W$$&pgqy)fmRGWo55wLw?u{VK5h<&=D zJdgg5Iie3sK=0F?mSJ7$;W)sFd~X1YTZ^lK0a7es`P2Z1ceV}0+0Fi>`nfXRX=_sM z@ovk4AwR>)$VAak0V-Kf+V)}HFC=PK$XF`%Fr=S2N$m$qKenE4t6IF@Zgd_VT>kRv zH~h~V{?F^-ZwJl){-6Kfd}|RkLmE%nR3xsI&Nt5&R~D9dk%$R<_#+k)5zMRcvY)-9 zTE0P9zGbCDg4jkrl73`zyKlgs_GRub53|7oEQRQp_pGwM|aV}g6 zn|So2kh3CKf@S!+W0VV1TLULm)hYIbePGM_z)6baR){XcAI#B1`r z$RHCo{jkENlV_jA6zvjKTomv8?8SSzfD3`JA7Pk=<52?hI20}2c#RtyVz!v?J?GgP zG-Aw5!cKI%3~6!Kth{i+3nCr7Avs>CsTUX>bVUrC$366#^kkRLJklie?B}>wVY6X) z1nf60)X+5NIkqvJL;D4p@-vTnnAUy+m{|SXadwWRkj7O8m*m z2nkHEbydt=H;JbRrFj15Vz-8#k-(J$+AslQIYMn@qe!JjCwQ#Ljo7AaXB8SHk}KE0 zEe$O3nmLnu83q^j;cP|wl1IMgn!JDoCf9qXdsClLR3n7$FNzclj_OXWkmdpj1RCG~ zd7or(Y#Y7d#ii<=jX=D)qSoA%(@l0x@BrX^ zRdbri5E_T43^9LpT7QIqGKLj&v5%yOv`(dWCYiKUeHbA`$w9KF3|-CxaiO|0)ZE*| z=2ToPb7}H>Lzs<+lASh~)_Q^9?U0ysigZL+QNY(7a$*$1#PQ@cYpaoL)w*Bn7P@O9RXiPlmtAL9XJc~G4(d2|lKff#lyoe605KkldD$poCeHqU?=>jVlNN=XPZpg8FY_~0o`%N(LX09p za#cHZDLJWAkjs&V2ACxEjlj*=*Mp=PiL!X4DRqlKJWS{%7u07JCS^n8;!jc|kH69p z2d_Ww5QqefxjDe8%e`=_I#x0JB6NuP<0inypp^k(_DHk$s#Pso$q`?S?dxthqkRl( zM}jhU-^@)!v9RiA@?8+P^BKN~%EOH~y>b6IH;TmTK~D}d+;72{u$c5{E)bhF!1c{s zZi$KcQDx1Yt;m_h#Y=UvUw%BuzK&|y!7eK{BANizw)oU0ymGMn&En#7G}U5`VXAQ{ zgqbku0Mwv6S!QG~*xN)tQ3lGxF^5z$+e2ptA7F)VFs}MO%99NP={A5@5{BQ}4g@80 z*y>VsqSQ|NkVc7~7d_Zu6BBx$Nk_OF#mI)l^&9EShUIeEft0t>Z_r}o6($=0MmGwg z3Xx!|^(S~FvZ_L8L2$VH;17p;oJ9yICihO%M;NI2;oP@yDR&2%7-1p*=$4fyTA$Vd z1L5gY(l78$mC&JvCDG5x&~tvDh=E5pKXoO%R5e{Zd?-!?HO2{_igJJCkAtxys3CKNtiiM4W z!)*G9p`QCDv8M88l^LzXxty8aQB{b*Lp?B1TyOT@4MvM_*TlwwbChXF*5AZtjbHPA zDnw2_;I58<)`ZS*$wW2naab|V`u_BB1fB{_9^wjk^{V(SLCMh)WgVEzrE6Aiy5jz= zGlP5=<4?^LLg^`YlZdw7KOHE&F$rI&!${O;mH1uy7|3I!~0#iRWAf}$H?g1rF( zVWz1!E#}Mu8hi}OK+!;U(;na>%qGZD<&T|Yn-E|0!v?;BZKSS(gbpM5I*_x_VS9y@ z5_x2Ew)&)>?Oq)dxYH|C@({QSIK~`;(ITFBhtRK39q&ifCVqyK?pdqzH3}48P*9YS zX#JJwW+oE(DGOkCHok+;Xzy@qXMJM_pj1o7puXetDq!}tD-<#oB~;q&KY1^7FpWlP z-lcORgD?D7u{EZI-(#K{vxHt@h+&LAvX}J~XGxQuQ&ur#-+&XQpcIXi0vu^1*s$uq z{J8Y|tE{zj@HHwAiu53h@%Jl)iF<`?iP4mb=+r(qWSSlzWmGl}H^0L@K)Bj_}e-4vg^Hy+4W{SF< z;>4MsS9yFIYSZ@84s?lYm9&o(Ile2LgL<2XjroJ&{Gu?XM?#VQhWwrHxMH9k=^e*+ zW$&0!{LIOuqg~62SKudagcpN3smb&Pm$RLwniwA>!u)0xI_XnXiF^Q~!A`BKw^O9ilG=qN>r zd?Y(Dlf2EilK7p%vizzYEpBl&F+A$1HkMq4& zTjJo@e=m(0WJuQ*IUaOR2_NQN%`QKVwEWDo89kOE7dvXVW5^ZfXjSIHjn(ZJUOzAu z#iOrjaxrextu1^-YOBe;obZjCVc1ZSUU7|PBQV0wh;%glD1ujwj6+@tb+?$tLJ-tm zWj&vX8XF&{!>k7};~!KVn*2x*>NhP{kD(xYbN*=-Sp*!b`^24QkD5%Gp9}E|UQ`fd zFo)|Bd;;Z{DVZ&}OG&UQWv-V4=R`1km_J3%$(suWo%qbWwqTvygzAIxJ?Ry|*Ke;n z-$0Q;P!20Z7cBvg72uHEty2l6PV-OKEeougb|YG8)7$8oa}cy9l| z7N|7w{Og@MXB2!Csa;Cr2%ObSJn_FDK-R#6f^G(k1Jpag8VAKN5f|cjfoe4!hy?Vo zne7571b;|QA!ew;AkbPaSip}rV(gNfU$C+TG=b>fY9pu|C`e~d7(^cie5DzpO?8p^ z9|gfT)4agk-2BSQ+UvdT)%k^mrFB$2TUpxk;%Cd?v=zVwTS0shx^BSre|g25&*m4` zUN0@~Z7i*`R_NM>FLx5aNdN|y#M^|nP=#oK2`?_Q*^K4Q;z)TMt76bXcftxfnrdZ& z4w`XLVfCnU$kZkYa|CaN4pK!7uVzpkBIp4J3b)qUo1j>ds02l{)F`zak=IBV6!U^k z4Y&>!?4*ks+{k^*R5#nm?>roy4d!NN!CE+(XRzQbSH}6oo(8uGxY_9QK=K3V9$4yc z#$8h;xq#&5E=YKfsq`uwSn<&M*@I{WTPzzs4!@7);R zF)4le2R~O~W=c@XUk^0A;erPm10kXQzro9N_{C!cj!fQZc$q9)7amOJ1*D$GSL7;2 zypJ#BBr^CYv+M3vz$0Pbe~OLpg8}0_=$xWxd6c?<^3bM!7`*g^6kAKa_Rm7L*Ge}x zv)kQ8opsH;A!YckA-2>#AHsZ(s<)YsT62?;mLQcF{7h;@kCF-G+TVA(Rs8#Ya4o=s zhgCrJ#II<#_5pqOpS6pq2sTd$pct;gaeP$O5Jvdw!cm)5vJ*neEbu4L1|Hs2dgA>u z&vJMKb}sG)<69$?mX}u$sD;(Sl%+r+If_h0&tzh{V)C>y@ImkqHe(&%dF4PHx=NyO z8;V&GZL_1K6`ng#_}HG)tosJk3(RE$S3{$v-p7YyinU^eO$uCXa*D!QlLx%;01ljy z6QXcCi5sf-z~H5|(SyNO&p{UAz_hMHPs)o0Rx}WMkom;1DxA18_7O9unGVMIRY3+! zOaQ(qI>+eme3bG?Q!}Sh@F#_X$$HihX;y>SrL>z$S7fP{LWbF$y|XFYtWSf&NgI0m z!O^hYK}9J$LOd{Ci#L>WYG~nDdwPCa_{ueOa;8kKW@ewg0b^3&ub?Q%dq{2gA54fT z$n`AeU6?f{fdxAoT!AFugZLh;z%j5>f#OU^r2O?>9UdNa5h$t<7%|771?plzetn4HsD%<>?e@n;{5f z4o$Pvi5A(YDPdbT2N#i^XR)&{Yp^Lr{`+ie&R5x%nyiai&q@c=CO{M6p*fVeM$wma zovIE0FbN-Eq7Ygce){{_(OIncBghW`s7Y!MCT%7d01|1u$6s=qs2!wFHzwOL9ZM;Zy;TOG|)R~YoVggELSAzNbRFq;?J1GlOl}kq9)1RjPp5; zO8A>eNCnL1DsXC^sWBYG)(LhOsx>8 z*NTDzng|bf1NR?EoI?rTl958}z5+hS5!A{1QTj$)9Fp1KfCeaHgeC0*UouH;(UL|d z1-9#1T!LYKVSa0Aajv>#BOAQxU4aEO6h&O-o>T*5*p=4EC=wNH>apmQ0iK-P^|D%PHas^?vRS+gdt*51+Qold@c#)#<(OqtN}~zG;_AHwlaamRY*dq- z!N8@5E{N~^41TnH>V_6e@C;dyQ&a()PQZmr>y2C!@iIHbFxEPk`t`{$)zz^_$4?C8CAn8Ee+`><9+7lq9O=*`%JM# zT!+jUKyMOisD%d|v-zy9^z4f0aEE(0MpqzW6_w2PZ>of;;dJG`st^o zwXMn);6@$(4WW6iR>NzUu>mGF#q=eNBHtvGUtkE&k<0**AR!~HRBfPtAh|cNku=OW z?pB+H&4)j5{1^iszWaUD$VLZg9OYFY@~79BP#IAN(ai#oeIN_tAAz1%#Db)@2Mm1D z>w&2S6YEE)DFbym(tAL|)2IaU!qwZcd|?iG8DhEA@(M?9KM z!zuZ%U4xbdARMJV-ZD5!?=JzEq$e&Ry(wW~P_T76Y7;gBAi=`bK-0e>ql2wjk7Cp` zVpnjfG)+;g@p2FqZws7#hcOkYcY}p#sTnBQ0lILFR@P5c8_@HRKf@^G2IV8v(_>*8 z)qKp&Av?~?`PD_xa6desC03Ay#mUstcd-La4g8v!h;sT7WJ~!=%Bh99q_tYXFLljrK0*v6=lKd_M_e3l!A_!RsY0l5jqv0 z?7F-4*xY0&q3_`aIG&$^%z@{=?KKdbD;Pxh4c&WtrSp zX#*~9#V1n8T1lR(BLB>S)>sidTg6Ciz@?BTi!+cAWx>lv7gc8k$Pv1xnvQjo^5E>1 zKSw@Dmm~E>8l0O#tbYZ?aM3DV-{Kq*tSeN76g71T&YLPQE6hXHAH4wMuCxJPfqyM8 z!gX{PFIhmLWH(BgttkX5mw|97B|Q>uKj;Cm=S>yx%R0II?|b5dq=XkuCwgsuX1iLT z%=NOt6(vHPNBEdbXHDk;{qabIKD=@M&_`kIl=+ST@EndGB!j>VdEmTW0nnE73JGF{ z;Rm3i3>T3l^)ZTwrS5g)3LRQO_2$v=e?n3I9IDXLs*PW+EO)xJyRN*w{|qTmRCgVk z(H%-HXi*8oOyhC|ye8@2-QuZ@Q;IJW7kz7Gy`q7<=KUNTQTaADhgYyB*!S{>>gok) zS64K7+`x$SSJTwfP>1qEU=RGY>q`T*LsXckb|N!W73D{uw)IZbE2VSEg;AhjlXM1V zQLB*Aki+N0<=BvKVDSdWSUbRfqOV+=9w`u@QAbx#^t;IoK7fMeuJuh~#nSn*ny( zp>U>9A0Pq>91sbRXS@Ir;&N~*TsBshgjnWBXq+(zq;17|%HqREUKP#5agNmuz=z`Q zi}N}SE;~#T!gL(+Zn0{)E*U=a{zcf7)a*5RwsRcRix<@>9n4ij$t3zkV`sX|$af{q zo~f4pWC-v{bjdCmwi>VZ2;-e9Yhax>*Sx3L5U(8VGM_np=AZd#XB|YVH0<`=k ztJJ2rO+sQiELdAf18fb$;*f?N!0_WP!lmw={Xria9~#k|nBOUzH_t&OJgTg-d{qEyH#3QYQ|*hOM@y3+!r3+n{Uvrt zJYwlqD*BYDKds>@#RLPl9$Yde*lXMsxnq=(%k7blL^^6E}1iR z<==G3h}w2{aDH|sMFD9u zAfJ2im2e&j%M;`_jVMNFl;V!) za|2u~VE?>%+$M*m2L@gmXx3Qm01xU}m;!g>d>|eR^w@wa&|naNvpsbdwDd8O2D+WP zGIEOEbh)ew8y8uzDLlWEuMvC5PEOdfvrKZfStFT&OO#X*K8OV7eK)3YZW_X>>^>Z# z`6FgoJL`!;$adcBH36#0j$3l|&UD%|@hp+%LI+c%pdM)1yug`d3q*v|#p3M)yG#Je z#9K(yFFk(AsbNc&RoRu<5%Hrh@j*0HK_A02@O#}Me)}qZf^MjpOv5oBTv{hZ2gQmtJZ3e>Jp9ishT)(+B-By)didh=eC9R%{$Rgu=aMjoV=`w0IBG$c1h)qy%%S5!;mXt@frr5YY0=Yk)mUm_>VarPOJSThrGsET zBQoN)$jc%->pLi%9x;v`$c*C;Wf4Ixu#yzq_|X;4 zjNr^(f0`TO=9yq=^yZ78RNl7?1P167vfeaxH!#*vZnk%?c3g)S*lz<^(+nrx!36+= zoBs+LOX?~`Ji`u46_sjB7S$At_*ucXE3WBqbYK1YLDlW+;n}j8BKVT(JzwBa+^pDlXhARd* z2vx$Pe9w|T$no3neY}!~{TpFL0y2oFkaO&}ig+6};oC|?))Lob{5rr5!SLtDBS1I5 zy19%tXbBas8gW|JW!NAZh8RFWosuB7c{Eq4Oq)Lw{7c(i_hzd9jNF4kr~%?YCv{~{ z(lN4JN=K6EjP6$nqL%Ob?0GsIA*4PwQM z-U+fXUiSLOvV4ZBpWuJPM^JG7G@JYh0e0N_gyfPDIo%P`r}Ps*4ZDeuv(^3#3f<^3Z>j*)5GWr3bJNM|5`aVo!cdq`I8Q24chO^qbsgC!&=)u*pN94CYRp z3|;Q&8g?!8h?IL4C{jrHpYeVT!%o|=O~)9X*M@%uH}~bpoA`d{uj(G8!hdL$!0bjF!Hyv3aL|ylTbYK-1_oa-;xa5^ zl#`03%^Rn)5HaFgyM08HIOQ6*t}>JY6?gDIcITPhsOTjS0RO4FWutxCyJ+LMQp%P; z8^Q^{CXDKjdNHX%T(9Yb;P%3!4RWvvn-p^FS9)9o+_1>Nt75+pKeE6fFc?CP)W4aV zbFs#Sp5z-Bham8*r$k|6^dPY4hDx*}levYA-#B**wo^(06iYqs$2VTQR^a#t#7%;~ z051F1v27}QkCJIUK+^*fsjZ;1Af#@@+nDRLg|Y>aim?|OEQ()ZPwbMg8*J7H%LGed zETPap>w*+d?Xzm*fI0YOJtFF0nN7p1NkDS(?PGTLWE>9w-zJh#&ygv zh@>=&5)gs_rL zaO%-Sd1sha{7#F{S`N8jou$!w_Vk2j6qUvF^vEq2DO3Riai93?CwOX$9@~s%<68dg zWf$LKm$u^x7Ndw>qPzk-tlTj#LB%=IUyxO`>ktJiLb9tpsy&@F7T=GGS${<-Qa~CT zC`p~{UlG6=_t|WG#CU2#UGM+E2w<0IQ)1^9=#@r@D%a;KRgh_6lJ(O<4%Q4cQoE(t zj#w^@rY8F$g`$oS3!4Q}W`{L^;MP}`R$dv$$ls!;$`>dcN_1!EiWpVxZXsUbEX48*41No*!N61v(x$Q0}wJ1M!SZ^-zZmG>% zfg~S4;j2iu1IC5E8(JoiM|_B;a<{^jfPC4OMB_9iO9|et+?g*<-Ev_GOgvaDua^rF z;->3PDJ*j$jtKk|>NRy4luxWgZHce2oY00k?9#WS%%@n|Erz1e^l%ZcC&UsTt5=gb z{nFp6do0bj8hva7MUb&L*Oyh0+ZDbv8rzQo8LaITtvGukOhbl*S#5-Wq5hx|JQ z=pV|A)Hz{kvN9U1*^-xTd87P--bofz^(QMHC^?ZY+GW_EZrkb{rf>V5Zj7}K&BKbW z9r!;c-h)xl$_7%UIi*ljc*jL27M+uI9aXqeRXO6r>XK_p+D$=`sD0$D3)&KVbgAP1 zq|a@14}qQVob&n06kVc*p&dcsd-%pg7hp(Koow;q)ESqK&p~quhzN>X`jLsgmuLNT zm1Pc!w2b(n;sF#LRFTgY@^I8qUZBs6HJ6gGCJZP*00#>?PL3+cP?@3)_lY*7g4`Wb ztrP>Mb=1W*dSm3AgON#asDJ$!>Izuyw1iEjFhkM}yIXGzkIdbTpQDG*8!PbYVJ_Lq z@ajx_Koi(-dk2L-CUUFUTNP=L89D85)D}T5>*1CTP!!5H$q2;(0%cT)8DZMfc?nVJ zQiuz%411p!`ClfQz*BOIs3-K-+!m|sAQIiP>Afkp>6KTn~F_2=*8J$SL zx%>jC3=puT5}R-<5SHT$OK1ZxDuaa`#FSF7K^7Aiv zB~k>{P~+uKLE{DYRX&3PD6}kM0M@BtZ$t15TIMNic%ibs2=7KzCs`oD{@393r@I!Q z)3qorwCUbWX=_<|=@8gXs8`14$Cpe6=?Z8tN>X6Hh@$(DzGy46D+3g+*B)AS$%*58 z7UXNlu^TYVJAbHAe|%p9OB7{iQBD9J1k#r)mNrbo1{V;3zdLSu6My{}DJn*bo6_ft z5Sz|Gm`t#u$mF8jUHGmsI2OD_pBV}GL&hpv-ra>nkB?J}@>muX%~*XGIWVB`D=tR7 z8GZ%E5_C|0o#8MuHyS<<*E0H2t$pfV^JCyVoSkTc#VWjB0WdAbg3~}&1QISB_u<|l z0=DXrVt;PVK${HK79jKx9CC*ztrt&GP4v?l$_k z>kV#%xx|c(ZFzI|%fa?*r5o%|k zn1+z?NGAtm2CP$*N{MbyYH3CiD#b?dGxB2Gdf=v$`dDW6j;`;b#J87K*aNm7#HO=dL1-zHN}`N}iX z^H!i%5y@_zmYuDBql;W0U>ISIWB6*fNfYkRRX#lwU7MTRUSC9ZUKDKIM2_CQ*UMYa z_O>?YT)VZrv$TiIxSM;cYkM1OYg;Jc^lE!~V+p6i{J0#Y2Dw#v&|KD`SbWRb#xw;{ z9%PCwX)2I@XG1Uuqw$M8NDEqL6G53DfO@*^ zOrCWFZjwN`|9L*mz0)&c2cQD%f4mI5z{wKAuH4HoR|gl#4+GD(1@mXvgYlT`H*WErt3 zN)&p>vQlv=#7`7)k(vcz9_#6eehIfVGd!@DdIETR=PXIgQ8tEp@)e zkl#@e0B2?&7%=F^ejPRQakx9Ghy<@Zh{|Ss+9~wnYPPoyUT0AE}Z#t8_#NY6PN`R8F82?2UB@h zI0Z4V!e;|~+W>5Qhw>>ulW<>Tj2`Zw5p?Bjz@e=s!hD*BjsEBcd| zE&8)*K%)2yiweWh#Fza$YBk2EKIc8}VxG~Pl)LME3!AkDA$$&QnMH|dH5N+=g?MhB z!U5>>BZoiB_chALt&bH{!|<#I6VomM^DhI9Sz(09*iElb>H-yh10Dr8tL>S?8Q2|q zv->C|2M1pJb?)}et(jZV2S0}ou6b+u9RL)s40Jrm3!4485YQm`{DbSO{N*&Q z_{O7-JuLk%r|B9Eo`l;V82>XnO#}6RY_`uB>GdVf#;Q;#QCGlT{mC&UjQ!`cZq#4K8x|e^Lz;-6j05jATpV zt_eV4UD`{nZ+HnAA0;UdVqhrdSZpqO9ZgiU&F0efpE(z^u1y;oI0=YvqCI>7Ja|e< zG$GL;loPY^z^SD`rVqF}g?(NX(&64;?%Mfm$62lTi05cj^#5<}+9qI;(-SEZu=i)A zu1aOuGna^!SIag4Bh2 zD|{cQ_zu1X^csfJg~wZ9U5x~=68)|_SG0lk08DX5kk+UKMX6x%6ZJuP$7N-3pX39V(0 zsukuQVlLM`x)c*JD7;bQegaQW0ycgAt3Sgu;c7h?-0_Ynk;8xheSI(>(8nNI&Sxz= zc--hJMyy$$C1xI=Mn`(FT%(vzWMLDWtlAiL#Z)2UE{Vpd7k+{nQlrCSsST+!%s~7K zJ}U3vn0SZwh;Km~%t<*NYGtE~zhq^~qKp5o@V?_? zP2YF8{Pc#u1PpvlW5A#QeW;cQWtad9^kovSMCd1xrXoQ?yz9h?KIC)VqBrZ$)ix;j zmDgx`SEI8~?N@Pf*E)SyH8{A#lEpFoYo4j0G-G~cFdFojQ{_FvlRzeZFED89J4Q)4 zS@|{h0>^v68v~1vj4ogwuGt{h0&(WDATY3KD>x0v1d?S1DtoB|UDcAUgY*IN=@jp7 z)Y_JX1q!jK?i^=_aZ`x9B}nj)X)MZFrE6R$m)(cQ6eHf@OXmQRufmpC$pk@>&UoYG z`hap4+@oz$e>F)jD+X8}&m7F$$N?LW*I>fO|I6cJt^pE%T%ZLSM3qpGc>+iBC0e8-N@ZY~T>M%_I))g$=`X8lGz& z) zpuyMLaATpuMXDv?AFt7bCpGEXquNn(uYN`_#8_6=AvG7BNe$nNM`~gS3hd{S|4TQ-04Tps7?h6v7VXf|>e8nz!K+Sz+3#A`O`-GW{#UmsIE}{MJr47xsR~Dh%Uu@gP zA~Y6NyvI3p5$6Ki>J6|f+6UQV0(^yCN9-!(Ne8%!CV`tk-)K_qrfr8#HIZZJ7`2iH&W#3;- z16;FWSMc7i&G>1@FvtK&6UGF6=@qrKj8Ec_$ZIY@q7jARNu~V!l{UI#k-|}{x2;Zj zIu@?gh#)Yuly!LAo|?55P6>Hc7(B-5UQCFGUC6!Wn@GFE%YEo0rx0mfC$IoFU=K!v@trL zNL2a95CoycVxmEen-w02i2*`O!7~$vOCUI%wT>_ynbOFCi6oFNIgC>jl&%ep8px-p z8kR(W$ZC{|5yCQGtOH)7Qdu{vkt5XH+*Bws!f`1^A?m`AmS6%@%?U{-YriHCEF_)_ zl`~8A@dNba3i%co#`lLICd6M6<-@~F$BC4qpVV;x1~)v&B@_Gsp_9B#e0Y;bi@3ME z`3U0xiU^1zBG2N*>D=yu4h5*lzx^xp!ArVUzpzMgIj@s7^59ndGUq=l^r6Vz{;$vm z1NnmV03~In3t9>W*MUCoO1>l-C640=MpHT*D6^gM6gLKC!UzeWDi#ME^M%(-A|^nl zXjYRzF{2V7|KX$3y6-kN^@Tzh@ly#pQBI2X{SS;mQ%8zKPxB9}H*V%Ra~%ElHNo>d zk1A2zlhKoGf&Rn45lNlFxtI1q?SnYYv{nx<4158sZ~k{E`)wSrXr=W|%xS$)wgcC1 z_g54u^M552M(>U4Iod`_Z_ZSK>DyirZA9 zD-o;1NVqOJuh`zDX^~7uqlFG*q&6d#2|aI3vSjOXT@J207gGSQ`7h1Gr=ttJX3O9e z0OHgUimoiNwvw5~2p%JB^9s?p)DVavd{2hqNQI^uLX%+c5TDj+pR_$Z%{5)jd62SA zop&jdx$-0rmeh^NeH0izxvX@5F?aG@5^KcifCj)Cjb)6C^8jC65ZkMLRkrADS?49F zde{aQc6B93X*c1&vihO%r=b^P(R$HwOkvWQ+UExB=>VDI0#xef&RN;kEZDGU%LSmG zGz;TJ$0Ubq7EgyOo*k}dSicRnPSe@^iSRb1>AT8n0miEH$o+s>ViDP7Dl@@#d)lBK8?IVR1gase!Oia=ekg zDL&J=x%G{uz2()-t@)Ley@i*HH!GR1*EY9Cevj@JmS5I;#MkJInYXq#OQ#<5wZ!_b z&o8`~e;T$?xP^s{C8iEo-&lLTw6Ik;9TqdcvG9zk1j@ED>U(zI-dbB(n_t{p+H!MK2k0aG5YGEB7!jZI)hcr->bF||ylc7QJ%TJ#(FF|xgP96g0~Bf10EG-x zBV*YG_#@;D_&#!C4lxsS;=$^`jf{x`XOIM!85ioGr;1uCRB6;1&@EP&c7*m$^;!c( zgz`hVv$?yXd;8QRgk5Y_h;MnMor-E>K*o1?pMkvJaVIvhg5$k%`y)MVAp{(jgV+QDa$6xiVdO zCR&FqwH*brEMRTU{g)+F4q%x}s!XsO2XZKOr-qobqqD>Ueg;BBXfc4V!oOnZ$>f`- zo>J+{d`-is-_+f-K&X=Q7yFy>l1AJ4xjqz_V1qKnIp9P%bKnmzPsSSQRqzt|cgdg_ zM~>o*{ljx=*Q?j`Vi2P#Os5eI@Kw>m^Anm0HmOXlqXf1>|5VZ=t0S{#*rr|s!NNtN zW{~dIerW5tx!Md2(Qsab_eQf_uT2wF_wXOFyQ8Ez6}v7`_l37-nTetk zjB$h5#*0+<_SQKFunX=+jW)VsIUV0P7KSy-8HOXrI_QRrzikB^2UbkJ<8~L`4_vbM z5`jmCWFam|Jt)#P+RLY*Q(c#WvD9K65BR6f-n>I23M5Bw@>}-kwM<3V+HGcnX&NCY&w===`opx3;|&(rZA8WnX9E3lx;!Byd&RQu@NQDqING^6=s9ePdL44*b%34Di2X+ThjAAR z-J4vq0#gJ>1}p#@o}vy~z>~zdN!it$C0Cf0fvW9Fo`=cVv4@y0NImid#NhAT#Rb?v zash@WH4k2~e4CZpOlfYT%tc9prbO9=1&a11?Fr}^R6bvhEZ>`OG5?LqxKU8k8g2QDFA?qB-@+b7lnWyQ zO3D-G`+&RlVi~*ghKp_EHe**zg8W&B0Z_N`b`cNDKfg6?k z1fXbt=vHhE%efnNUgK-lG0%2*(c=4sEX|%XxJJ5B6j2rqV;Mdht(fpxN zX-PV#A;6s=RHbr-hrLs#_{Vbk&j0*raq2Ig9};au>4XU{^<@$Y=)j$#2SB;Pt&!Y} z;$@T$x>(>tm2qoXqOOlsVH=M;kr-lt9m{^ACs~1aDvEnX)XzuPzUSRkdZ%xO#2x0x zDcd4ip~o_=gR(n$?Hrw~!yf5kuBny2a%!e%;N;f;k-GMWJS=-SRI>@k2#2%Fmm!yV;IX0x!`^Ybo9?4LLTVU>!BF2tYLK_EjK=vjjnYk& zSP8m^hKDLT%z%jan%UJaI{K|CAucagS(8JsQ!T*AAWpzL2!!=WAunCTah zykL=}kdL|Eg0C6gB!T^jzD86TUm^qRdx7fIf5~tFDPGIlpfN2mNdSrES($7K4cw50Fi#dnc_K+FsD;i`cJ|1GCaNW0f&cGJZ|nqKYk*3SFX!2UdZl zO$?gEvq`cnEz+X~L7OM27Ob7Bt@FO*R`LQOEI1~Cp2ue8vx=mC5RCZr zHHp4~$k|4#CaGYwNICTpegQX&jJnu9)8qOfIh>~qu3H$y+L1Zb@$g&WiNLx6MjmyI z1{0k}PqOnUeMD@L;shq+kr9fGrMiIWW8fsLdz@ZoT@(92jAKeu=JP?vqC;`c2-nYm z=z>Ghk)%;Ohl4o03Dta*fJ&h|;+A;G)$qSI#8^Kq*$nZpLCj+$i9ZJZ&xf22*^ zbXO&__y{AXx&7t4(3AGjZ%RdNfo96ww`V>&)WPZ#0k7ZFoo_VpopBSxRx@n}Kh5P% zu#Ppp(E+klvz9y3nJR3MGf|DK z5CrpUK;g@ZJ!;8b7f=cp0O>@^#jz5-U_)J}Vq_$hDl_5d7xRU(TiDZdLPDm$nJ@>g z+|rC>L@Q;eM4Lx}&DsewTCagsC!&}hN+PLL+7hPc+U}o_#vToaXLGZ&tZ&Dy%*{Ei zhFvdI6wI_Alyv}4UMNjYah8Om!n~P{JWG8KXKsd*>aInCa7ZAth~AWCcZ{j?U86&K z7IuUfOkV@g>2_VTMm}5>%wxQ^1mfBLZy^>+Xa@%W&R)=u|HiiH-^<9*H8~BQ$YQsfNWrgNFyM+ok<)3n7^p1C)WhOk8lWF ztH)|53GJY>{>Xe^XP4(41g}eSu=6g;cJ{jYMr~;$PtJ(KHhCvhtV|744}Y?QIL%KH zXxA>`g_3VgFadiZ zQorBp*K4`D!SD)77bOC6BJ33`3Kw=+l@oNeU3inHNRMyrr24Qe> zi_o43G9ptuva2(`Tv$j_u-!%WDZqZawL|Zkpad{S1zUbJe7sUVersWLh6~UZ?`pP( z+OI&Qit3_93B7T#rye>eCe>lA3x+UJ>}BJ9ub&KrO&unVY`_1n`flsS^_jo@0lu&{ zbsPU(FnN4#4kvE?^ug$wK6kwIR&bWgdBn5-?154@7abSIRElw7TmTsZp-fIzH5ZP< z0x?SL>sD}}gk{fOS9p*=p{Bj0zle7sXlQ_p@@Ie!T?_9wb(dIc23SGGeVs*9ad+SD zQG6jZW?*du-kKNw%!H>?!lDaG)D1YWky0k=fI7W3QpZTyS{x#;8J_D4gKDxwb$S{- z^7lb(7wZ6E+nJHtN>X5i3?iA=jL0!tGE@d7h9bd5cUA>>6hOH`RXL8Imjru6OMR$b zz?OT$EZ`g#j+*%J%3jLYW3)xfu`zitP7FE-V9ug*gKz5j{tPaJ1+PUVmi%HHP?IK^ z+Qarq52f)kr$}Z<2uqG%#v26}V?K)-wih$*LR{fOglWMo30+WP5a9)qn+(5yU1)D% z@w0@~zHanMV&|$%{R+XLNe%~Gjft1M6u?O83Bar9qSH_~%+5Ijq{_nOp-*n1=neSi z6gsGDWelIqRJVz0sCWZ3<3>1@A9>~-*h9l`V9=n9rS}_CvwAjLw?tgP!yI$9D*ZV^i}3Afi?mx z)M46L7@N#^%$6|o5t?X&--SRlA>(W=udPaE(CiWB`MZkyfLF{p)UypYPM9f|>@}}F zT(&_(_4`4!j{n&@uj7I>zY}r;rtS!Tr}k30&J2Mj4HcN*(kf!zx$<)IjYjz4#iiLe zZ6OTWV5n`3{H0owjmCMUJ!dH@xC};OX>!p_wI*r^DjNz!A}}jz3%EX0o8}O18JJQ{ zHAlo~$?1MSnEie*{r%v3XknS{CP=$CE5IVdem%Ltjir^P`ArmWMRXSCsm_r#+MA|& zOP&nue;Ne<<|(|I2Y*}-zu FDT1T8E9Y}Rjy==9C9;yeYo?j-d2O5q~CqsGbOVS z9$<(txVC+E*l)B9gE$bWV(j~YtKw1+1<@e`N&%slnSB!1=h>x>B2e zM#iebkNBo0paz8I#%=b_klZf4QyfkVxa<~A-6S_ISP)AlZ+8Um=JpnLH41%hfjao< zC2YZE9yO>^LahhT_6Ko=x)=o5m!nKmkx0cnnpyV5;@M(NY4}DBU>vZV2$2#grB*Cn zzEhq}GIWGMF`I=wlN+SNkeaX{%wB;`95ujt*x0S=8s^{JTrsl8*=M48Gqlasltk&@ zjo1qpo2YvVlAiLwZRcvvNw5eXlVn^^N7v!!hv%`mEvR%FwcS`a9NciFA>JA-2|9MIq+{h7RYY-Bf9 z03&&4Po3+rTILJ@TPT=$3b$gIQ?jm8v+H^T!N&O~U87-ZX2`0?@bZ?lgYpU7kmUmm zEH`lggzVZIpGL@we38n2fEOs5irLgaH!DB=^wZ+f&ieM_7fWv|)BN{%2^9>g_+({y zp^8uC!^+F$<%Q+<^Q(^!k3Sq8cb@)udF%1~tEDIN^J@$9hY#oZ{lei3{Ju2beZ2VW z{V$vM+qZss*?V#KU#r-pt>+d$!qM+uCou?0#HZ zy?eC1a__;)r@M!@Hy7@|eZKT^=UM;Or?(&OwNGAmDxJIS=98tvPbEE~-oIU>K+1lN!+vlx=hwUd% zo;LUI?A%>ytgQ8i2j~6!Z(i+mKD}Q%KdwAHx%}bNr$PJntFzhTlV>0H7dHm)H$OeU z-&wi;d`?z_3>HNcR_F=QRI=}qsR)6LF%F$ZC(Y|_ruf5u>JnZz2FJHdsyg6(3 zU#)Dtcy?ps+3n{G=W9D3U){TVfB*UA{^sE9_ReMJ!@+RWUO z^Os8(mv;|Wm(P}Zciz8k{8%}AxpTj{^P#)hxN*7GytVk?Y~jbo{rztH*8asx^YrfK zjm!JXKXeu!-#z(duzz>{$L--pXLJ2Q}N`g}fwe|&oR@?Ps+r9Zp1 z{owJdSIeIUzZ}kYKCC@`bFp)LvV1uIva@rmbKX2WxZ7WSyz>5idv)m&5${`1e|&Ol zVfgCCgXb%kjit)v=F<86`NrvuvyBg(hmSY9uV$aV*lsS&UoOqhzxw%SeFjUbi@8UT zycCk5agQv#UwLI)^G`Rw?lZf!)0w^V;(p_7VdwpW``v}^!pWV=%U9d$trs`ue{6p^ zehaSgP4CX-tH*CouXaw}oZgx3&i*oZbN_5%?)2|ITYTIcbBb??rrwS~v4cbg9%UT)uBe{nEe zTX}zHY2mDW`S$)rcj?}Phs&L_&eHjVrTNy2hre9Dzq4|>)vs*4c(uLR`0@C`;o;-s zx4*oeZ?DbXnLoVMzPfvOXaDd4ZuiFE+0us}uDZ)_T0fi|UECio{ILGG^$e);Z1F{9 zcyed?V`r(`e&1a?o&WT7r*pA%`|Z=#`P;|uS8trY|G2nw_3*{h$Nl%+Pq&}1AI@JK zwl1IFX}!I?w=rnWwktpcPhLMgo_+T6e&gQ3t>sUTU++Kvp*Os>zTCOjI6c1gV7WJZ z{ATg><%h-2tF6nc^QQ|}r#~!xc>U_mcJryINpn{-V9fsZ+5#Kw*2U5U9AA+&96LN z+k-@W_u)NBZC>y>d1Py0L^gyz*9J!o_Ov=Xd#8;v1ZG3d=`sHmy^MTq;EVT8TX0H6 zImH$tJ#x{@`RG!e(|MYZMwKiFcsDR1$}T2M6RTIb6-YQE)RF*CBCHcJ6BH}M_91LT zsL>`G@`O43xyqz7doAcPm{lo!aZ{^q-~hu%I7P}H_wVIfzw`5uRHuPC`?-2s#B@Ik zU7P0r;km%%Ea|7f4077BH!NfB2r7?fy>O?8=c|${qwWoEWEbC_`(weX(MFSdX-}9$np6x989C@~U+g#nqA+DP0qvhrxK zB+k449rT#I&#cQ{@>n6sn8}uM4Mc%t>r`rXv0PK6(A+8p?u;}R&Qjx~3!-KcqkS&t zwmP)9s{sSq^kHykoq>3CH((aNup$N{rMdM8vR&T7_GEUzHzQ$CtcQrGuVYHC5|qqS zJv^+gk*y+W1%E1q$pi}OwKX6>yLh)TH~`Ebke^EPPH$G9{9xv2czO~60K>B7T1*wp zx5znQHDVTt28`~&bZ#}r!;&C>;s`mMw6;hDqCzQ2RJ_oQp85dtBBCQKvvbyKmd^rb z2C>by?_e7ZIKw1mK<8XKq`?0ytH@$ExttS8QP*X?|GYtg4dkZNW71uO7->=^V% z0cSr2h+&#VKuOvKj8Zjg0c7NAJ2g7w#9FL+ZId2=Epb4V-2*ZY+kM1*BMKU7(U~d^ zYA`n!eO&jsH6(>n#kah68$W&T$)qDLehVx6cGvAo_@}yWE=n{qeq7(+!UM^v*J6At zuB3uvK|<7zFXcC;7_@*kICZIOq=I1l10>+_7$t1Qn-uwzFo}SFzmu5bm2ntvtuUrK zJ4A*ZE60;gpc5kL{$e1UW=mA9C+R*2snBIH@pZ*9$JdU%!IJHS2JZMz)VdK?h6Z?! zYA^{O`i|X|Gh=N4S{8x ztGn=-Y2(t&9N&C)PUS~f?3Nm!R~7iq$6edN)sPU7`QhyZ#!O6Z!F)19i-{kI9lkq` zdC{EF+-iIVDHhcJP#y~6h5jpeFr&wZqb#qJ<}@r-dAY4dA1?v4f^M*qE1>Xj{=Inw z#vbWn;von($b5B53p_(BeQ?44>CDPTE!)bbr$(6)9kvq6%oWtE{1jF3EP0b!GK$@( zsZ*5B`W4BW`W-?yZlVcD82w3P7AU$?_7B2gYE&WDC97CBROmU{Vv>`8vIE|){HPsuQR0DwCQskC8)f|d5 zX$fXW3ja%MD}Xy8buZ!nD-{T3;gE_89`7g%dt34$#m|&-g1D~Q`Qa6>owSxU0z$~j z6KRpQzzc^*&M*)`Et5QT;DO7@Q2=CuSNxPsM^AV@d)}Ws9$OdzfuF0P)ou2LW^SuX zryV&C)hwOvxzzfTL9jj+v;)pXW-*Q$^QDTMz5WD?Z=oId(HUtuQ*%!U95gn?@~f=R zRhMr^9R7HAfZW6vAG&sgt3PViJ(e62Vu|MFcR{zyVizyCJ_LEvz z6jw)!8S(=X4;3G0NT9BWm3b4;md_48dAmxye0*LF%~kjFY6*{ku$CE+rstJ|e|sS_w_Jja6ikPrSke0vr$P;>8dw2VQW}CD_~! z_&X2Ho|_8|Q>Z?fvjXjKyNTM~C&Sa`41Ooo#v>4MUIQ$F@EKLM0L3ql+E?kHsUu&q z+7j6KOtQj9UdRCD{X2{|**2qnPgG@X;efM5Vx~GT5g^o=x7uCkbW@zc)KUB7%qRqI zt>%oz2rdJ77_I<9Cy`21aK{W(D^n8P6p=&;;3cl7Rvyjxl?d~xu5df;T;RzZ0vWvv z`o_06;VeS7hRVlZ!EPBYg6p*EH$*?j;-?t39;S9r8bYPCEDUFWj?9(=ZCtZ`0s;#O z2F?b_Q4p8A^9aecgFpgBbQa@N=YxX^7x<+PSw&1O{2O^Q!D_&Hi@h!V8()R>0+qhl zXMH2*pX_r0o@HjkoN%9GT;lmDIzq>Nq{0&I33q3ek2B3d>H|3pDFvZiD!s0Orydsv?wkn%o;H)Jz;>bq`3Fg{t6PizW-^?O7BFiN}IXK6IZL8~SsGK=Mx2|k{(%+o0#Bd;n7~>4D7Xf*Zj9^RO%@CN-{T$o@~NNOij8Ham1KO?aQQdruk<3l#c`?kK`6A@aWgFl~^DBksa@>^>KF-|5@oY z;a%CzrlXNWIL=RL-+W3IuBLa=-hCn^$iY5YLcs9%=J3kjYk6<@uAC1wQLRTpjhis) zlG*%3(`ZtJag(Uhw>ZaP`7~QlNP(I%C6xZN6g5Y^OFXX;$!Q|V!UMD%8zh`C)Ym7y zJ}!%bNaq6L4r5IGO$wAn;{+=mMBGu;Gm!Y=vLq=DiBEAlnoV>XK#vR=U0JYdV;`Ke zC*GSVf1uwtWFp1lU{B<3{fks;6+@}2p=P2#L{qIXM+pTxti&y54X~IettQGqRqKDd z&gRP8!<05aay^)xZ8k0+!T609ahnPHCmv3Fj2@LIB={c#*E)Vby8cXRM_FcQABNRjk9GAfmZ)EN z8!?3kynvYL114j{SPaEu{P6-bsPSF+K_TQ}NJ!%<&c?68o2M61IC6;W;n0ooLx=Vg zj7>!dhox94a9wzAd`Y6uWf5c_HGWxz>n6~9MwKx+H|1gs=H;>A5ik`?;X%W6%O$Um zU$T$L1PjGkXNGVv;$S67JT0M)ERXu^xb70g9HX-U%Fgbj6l%w&u=YPqc7M zU@!4D;;6mx+vkeYqb50d0IGhG46K9a5(CM?1Vua2`l*<<^!a+uQ!n_F1|}~DgvBo( zc9}fKa-qTiVK-GDfd8jg5JCd1vPT;+gT5@t=pvv)Ii!wS-Q5}*Ht;rqL*cEW$R6V< zrl(TM&Qq}J00w@aO3UN0MFjWt8c1WWAE1`6Ir=(-PUPwoLnv{ula$~L8K?@@2IzMh zN&>(j^IKSC-$1unzQn?;p(2R~u zUkU;CU^d!_LF$t5!Dr}GyWf-iM=&_G;4sB>NSF@yAA?+2XyMFC!?|&ytAZa>aJE$@e&>-LmyHK zBIK+&d+Y9W^Q1F#+WHqR=-Z`cQmcx+N#!1)k>M-EGa+l@9HrP!I%K@XV&YU*(XD9C z%zW1E426k@ja9E2>JpfbJlzL%(}^ty46f|EE-ErAy^3|2C9?dP~0YsoGh+xjN;5}?NSO9-TmN_-x766&A7F{2tKS|wjm{=jTk z*R=mN4cAj^W{=o|_zYekzF273gl5o51av^?OG#LFfv2tERf*p#^2EhC!n5o@{-Y=% zZ(z4dTR5+W11;3~>Q(9*s`cr`>wcxsN=q=fWF`Z65K-*ACRAvLK)_7^g3p2eXXxK2 zY+L|`NWffux^X>L5NiZ@$@S|KqfO;coW!V&asn(9RgvtDC*0;&+Eu~z8$f^cMH}-~ zpFa7L<1bh!8CidpFfqi20|lZlI$Ms43t2B%*L@ zXOdcwTp_*1Z@_(tYTMl;Uc%Ehoa)lNZqw!&p`!F@Bl*++Agy!=udA=giDI~l2Q%C`I#Zt|W0*?vlz6asEqt7hkyq*L3fsG=aW0(_AG=}c@IofmSQwR0=D+$-Z zb&s7hU|fHru9}!=*SWdmMx0d>xb$7{fRvYkV+04c<4eP6^2N(kn*Z>*X2&NN6fab2 z!G|`8z40Z;MJHdR2VKe54hR=SGpDgG(iyjq3=%S1#x0nh5Zwd#lx{cf#12`8Y_&&{ zsjM!s50D3Hr;(C^g$QvSN_k;2fMW=FExIy>n@m`-17sc+ukH?vG?LIBD9z)?;8G3- zG_@RomeIVCvK4d<@>R+$gOy%)t6U|UQ=y=!? zO!RGPQH7YW0All7Zo360#qdwPhnqCIGcPIUMD4h zPVR3+_{8tP_vZ6)x>4Pi)l|CuZS6ztdWOE|Sq#L17L&tHy^~_C5_*>@g z06MXrBv9l4$be%N$o&KrkE7G@%JlU>*groCL_od|-zYfePd7Km}_GQW~ zDhZ=!gjC~Dp4(yY%lP31G*9LNFGeV%zYJA7DULG@QHqAt-ED7f4(@&w>~znyFlvok zw1WtXGjiwI)Qn3p3r@Vwd`H4ga@dL<{hBq-tn`>1bqkWt_OvxHFk%IVH5l5U@liPl z;FKJ!?a=af64^qPw3pnS>)@55D0LG^@b_zWLwVnp7S{Jm;WxDp>G%XxX*~mG-~9}5 zIEz$HMU61rciHhegZX_>Yj71k9u5Qyhe0+e4_B zP08m>AWT%~_C*THg!%WaSb`rx1YyQ^zs5WKBAiKg>QP`6IKv6?CdyL7EteQO(%BLL zD?An;%&3RJwB5W0_sUaM$w%+<{TWWwrHqSmL z10wM)^|Y3?@lL7A;US%r`l#~Ypht9tg)owJd39^=<@_5SHP6!d-dvoQcAB1! z))~e*ZG5ahyahC?)5x`zT#Qnte~|pf$;L=;vYvzt;_?%Grw@q8j=vFjWWc0FYB*Djr3Onn1#WMH3xn8`;9@q)Qo{mV5@TfF zLKu~=FrkuKwYL_m4Dv>kW65E*cAu+K4LbLfK3!SMx}qX-Bw6Y9x{w+g^5i@mX}yyo zyT87h;x|G#_zS@%U&FP`Cur?P(~MBhK-i zj735IIk?sJ6TCOFr%WAW6WnLKr0Db7Pw+J7L3z?5YX^V;H>C6?4zG#N6!QA>7FS zA(9Y!uKAObEr5BU_Mu)AD+l;xwp3Y#!{zxn|{u-*}I#Ubx7gw1QA0Wz! zXj7y+>jK%TKeLM8`8TG_1mIGJvg@C0w4w=hIPxa({rT`KayHygYBq1JUwh-lwg$E` zD0`yam9K|DiBQ-z^PrD#g4_+i_Gf8uj2XiVo*44{3U>;gxW1+=wJV0oL|l+Vv{S<= z*>g+*1D-*Q;#F~CkW)N-8kk1)C!jHA+a_2ZY@coQfTKh?ms*9~c8m zrH!!J(W)FkNm$jgqI%rfZo;>f9SV85G-)u}XYV)vMA90rz}`fs0fL)bjhubz#S3~E zIXuV7H*f`-@EXNnYzZ|ItBHePD5{t_^B7EUl*AJOAyX8IK7n7%uz%7{)I>Q9MU9kf zE`HT9XOyhQh(S#_&iIN7AO-8n(hgZ#>cisI6^s@sF=d&_t4hCz;&m%%>#`>T^8`MV z&1=VLFp|UbfsUp02d*{SQZ8>(zVeb?>&m+%{2JK_9VC5O@n^|_OtjAIu|!z6_$1&y z+3a`3i#>mfQSXH9O{i4yh(a?65P-99p)VCIRzRCcb~0y_*A_$kV>u&c`M?UnSMW7< z4>MOJPXbb!H?^CCKUD>MB4e7wI+K|V*Iv|z)*6vFn3J!9^svCy?EbOEJou%ih8_mqdrAWh<#(HOo zf2Ed8Eq0)2DK{%#U{v3texJrom0J8VwMG01D#Dji9mo1YSqrefyrr})BdUpQ8n_Z` zzz8*NI5;^+zo&!+x^UixI-MXo*uld1{d?xRiq2Pwk{uh%xu#AH@LSv|)k@01#%EdD zEpEhKIaW8i)as330x=AGqk6ac6B$;j7_rotcHrX#K8m`gk#*rv5ys>WK5x7`_u{oh zGQ=D$RH`?AuHH$oB*hvR;CZ1$osqUm^>(~^x$Oj&Y9Tk0 zAUgpLpt2-O(wvyN9i|dvH|jVucJooDn|!8y$cH8{=+Ksx{Gt(EY8NBi-dO|b->jE& zM4+n4VM9EwA|6m>WqXrp**7YjsiIVl*NF~7ust4dW?}>2+OL>91V* zei@`(5g^Jv=(jsbBHyrI?+55rKPTlf2yU4+fi*$X0h42}jj||l&#j(KBe8c@^{TC- z+TlSK8v%C7DtSmoP2BQ+el|UXyBuJZW{!bm7MUJ24%${3fS>AWrlA$Jy73|0%p?fGbJ|}eN8|!n=vjFsU90h@&~6ZT&kKcrsFqI#a|Z+ zK8E|ItM_8YenpUqtRWd|8iU0rz_TR6z`9N-Gepa|UWJk=^eo!T`98X(`XF@{jyQC} z8t=;S&de~O8_0KjZE|bTgyT+J8BZ0GbK``kp{?Sv;z^0pxZmt!4IEHz_)uv=mK&xC z9Ta-(Ps@#K21P#B{KQ=JF&>OYrU_1DdjfGr=EC=IbhXB+nl$)y&H10c8ob z3E6x%&x^)H6s(n0Ih~4rAz>2Mrj5MHOl29SG7j1uL3!z!FEg&7WSwv<#k)%3C)-R& z#p3{LpWm$J8=UFpnDN=qay%OROj%CXuUUBb-{PIv+Um=3{Dazps4IXh$?R7UBVeTB zfAyp*Q?(b4o28aXQd;vH$0Sk!Nu__R51y%*K@vKlK_vXxD2R7+`e2`E z1o!kKSY`%ogvnWN7^Om&xv&WGVrP<2jmv?lcQH<~M#!S1VOv7>AZwLu6Q9*<2)7p# zL8>Xjm`<>l{HdAYKRs|R=t;)H{1vU4(Qer_a0Oz^_wA%q{lo)q{7n{F=nBa*SIltM z>%j+CEtqav*o$X4@GaLcIq(}m? z-bc1zpijMiz)Eua=#{WvQbq)}9ZCQ+j=;kqaRMY-SH~Q&&5E+mVUBD|3S_4rL9hwS zJF`b8ltCwn&fO`Ty!vqbMas_|C1Nl#@?33H!7t$(K$h*|oo*r5bQz4YOyv;@nFVn# zeh~Z}HoOBwmm>V0a|%!+pRwe?gRV`#vYpaGi$li&+W-m(nF(YVUd@T5KR(&&B?sjL zYLN%WRhs?{|XRJ&UcGQzbGkTI9Fr5!kdOWRQ}OjW$wKQY3N?3P(5b$z{6=*pO+92uR*wnI6=}0=jb0mq8r;aHG9K+A?;|rKR z`4FqF99A{J~SJk}RhX0=CdZuPuetG?8XNHig?|5v(($tMwTQ;Om(+DFF;k z@9h13eXfwMz59OfYl2qdntf0^Lu&WSkaOIWRMEPf!mqabC(K}7&}oAt7asjQh{B!1 z^>mo5^3Sbv5n~mul3x_9k)LN)D6pKORwJ0B+?%G1I$}3+w22#f5L7ZEoe}PdJi(uW}FFcbGM$agNfieYd9! zqNGiNSyQ;eWYjRTGh|F#?cqUgxS65lMrDQQ6)*Msy?(tCo@(CD0gEgPN0CEdw2YJPPk6^4RH)awCPh;q9iAtELFU?Q6gDZ z8(n5*+^~8ZDt~dyk^;BAj&SA~Si+^=76K@E1rjxu;C* z3Cstv;@6jXM3%Ww(KKicSl$@ij(Y=k5$zJ|rl2tN_C5lQ*-sT9UH!C^SRJwrix?v868pvmRT-!m6r&JX8xDvp z+A^V119>HU0t3hzyu~{XNh#jA+`q|lNaR0XqM#eKHYtLk?Bau;4>>cOI(Cujby@v37j0X$hEqglRfN*jR2K(r#t=Ucug67tc5 z%tXF55g5d`O3kIte3L}jL^Ba9@@-6bAfH>AvdtT+2JE4Z%Z?Ge|w;%~mu;Qd|*?QSxg4MEF$yx*FLetF% z;ajho+^H=5>xbQ|IbtgK$#z%f=HJd31f?HNglWgv^Aa53AU-K-)qPmE9! zU2}B1rcE|q*W%{n!U{nAJeE#%y_WY=*P0&88p~LPPdZ3?jsvWJc+&BB83}?EPFG-} z3EX<@@>6s@-K~5rD`d2aa7!_R`5dSP0b}iup+Xj;QWhwamRSiV{bH0S_#k4ZVb?x~ z`k!I!&Y{9QVZVB0orqliH8=z1)`9&8mMN;==C`opdm}E&j>aU#yk@?SIolcwHu?LV63MAdAJ_VJZ7z!f*Z1RJi1J0V|+Dk z&5zN>U2e%)rtE+*?YCwy7y^O9qON#6bdIsAH@)+#VDf}BGxMR(JnjOsPj2!HjNf7y zMYTPyWpHp5PCGG87xx^Y+!26DbRA=KHB^e6$MN{jhTpHy(vWbDF@cpRzS`i(4WKpR=to8fsmHS|07aq?7%mY!T zSbT=`S|~sVUXuO7{@%$r+%j)T**Maua6~pwN_ZA5m2}iGQX;fmIagRBG#e1)u2?h{ z1=0`v2)*#anv~dx9F8DAA-6fA6~QaeOG5R~Dl)UHr{lS%O^wdPerJ~@T) z30iG_v=99;ex`Sy8j*(}gDXNehgD*7UDOk)1%gZwX86;#AW^zRIg8MS8XZKh$4zm? zV(G&Y5(Q$xX@H3Zn{>6V@|EPLGJ-2VB>f3Nq)^yTZZ{lFctr* z%)(l&hdVe&?#!|5t9r4})+jSwMU}tA@g&ow@)#u(#NuM&W6GN%F3P$>y4M3dmt{Bp zH{Wtthkq!3{Y>EVSSm}`{_8*g4?|quqcNkWhdnsF;diU}?Y%H{a*qC?9HadaPfUT4 zbK6&bUYzB|4QIa1%bn9n-_(-+Q+-cQ!2fVDNF$|i$Wdbm#Yc zzy9<8!TWxnAR#|C2t`%xUdSIF?<{V7beo&`B|pf9^+zPIj1FqRs;=77Cr~%V1}Cd#d+K z0;x7^G9`Y~Grdv0jZygMl80BUhXK0CC5Nl5>r0G~l5ON>W!ZJsl=6!GC{Z}azluhH z4>Gds$XAje!nc!FnL@R)VNO0|O)N;>c1{Kg6_Zi@52opGnD}rs>?*v9@3eq{z~h*k z5!}X(Vjs_44(?-t>!4~UEF|U^qaHxTjwH-+NLEooNfcqj?Yn&@Gw4p8X17`?P!NC) zu!GxA$>8`qI&}P%-ZRck`TcmV@K(e2-UY8KT}6A%pjouLj?3+|M~wC1!Wpz+e%mx+ol|mL`i&2 za+94{Ob=C~K^*|WpQb_0b2y21hdeF}mjP+gi9mud9V6w1;)QG0BQ9H}(Ip`pqWR|( zdJo?_$&Bd}&b5qiC1Xg)T(i?}o}V=OuMuO4K|`25J+uWK1X6$zlP%t?&_)0NfV)NA z$4Ig?U$LxNA3$CXHBUgY;jpqq zHC`3zD0MRq)Fs>lnmG0KmO_DZTF;3CEFnoU17XEe^2cOUpB2I*|4axau`g-yQRndJ z1pkxrT}^fP&c{?1?*@GEcAU||-g3lGr|e@z0Ri^>618q@bd#r4kmW;ZX8dvs8u6m- zQsSeMoq!&C&{Fi8WV*n#>BGMz-h?=P2n#He5Cq$hVU= z|E1?*tgrh44d9tMfGr=>rA{y1gR~+nMsQI8m7-``*>%z)8(CX?y&0Ma%~D$wK7|?% zK@Xx-ChMj+zrt9X(B~}ltiE5ek4_$WwKMPi#%$fWLcCGwPP92;y4n+v|BPi$?dOTrcc2m*`gk}$4+sFLG%vG8 zpecHB6)s74g>tj44ny>LoEgBJe#9sA7>B;#twKa$YDstqsk6k1dDhKje4*$ic{h_* z1Y4g>?jetYJp2dJ~>#b>D;Q)(UAFjp>0x0p(vo!|~iLgdUI!gAZ1>RIZpgl!z5P`>%dKo;1~#3=csc>Z#I)Ngg6j{}mxacrm*iOto>jstQ3@vf%VdHlH zCC3T|TCy64u3-{ETj@kXey2J%uknw{Pk%oaTlP)LP=25(I#G= z{(G4o1q2m{_X+_Odqxhe*NJD%@;44 zgQWKa9d??7g~l1l%J^;ufV+$5r)OB&Jag*n@4D--es5^RVAp;=J#Qm1Gwj8@hjER) z279{g$@yWY>o>Y|0S#7mG;OgiKcoRH%c(IoC2icU{wQp*J?xz9uJ?OQEO+T+yNNIk zEL#4F@wy5;hs=2fS5*`ihIijUxB&YMY?Sc6)ulJfTYC#@i%WY;8yjmIn?MDy(qzBe zUR~ebe73Z>xA1I!^(k!5k_RcNlnyx~;sHksk5 zanY{q!&aOu$xdaa0wYbMh0ps4;Tl$thQqVL-0Uo{b<=3}Os{`9+v+uEZ{3}4o^)nT zTmPbKsBlTwD+xI&xW=|XUmICBfSSTcnJe3mL&ZyPX2QQ{dx(|Gf>DM;53g>r%=GpTC zDCD@3WLbrfzzdHKX~dPM6jN-MI@pBENwZihUK&>ol?pwr(_DWiA)z+_qLXj7tZLMB zd?x`Ci7R@2@T#X2F z(KND7D|=JKp(K3f9<&{BjS7>g@TxZrKQa;dum^+ra1!NS4#msf&k`?;gGFoma^?`gpKagI{d}*VEUK=?`N@bMB{E?Iw}i2|dtx z%n^^ix^TZS6cz43m@s+ZS$10yofZy>iKPdZB9+fIQ(&_#|sv7zEfuUreU|1_*Gz2Flqsbr{ z_{{)O@zr>8f^a$BE8G%_Jn)w6VahV6fiTtMi0 z@6RaJf@qyCoKAd zGCPbd4@FTCcPNoVQugE}6%0f|5+ah|0;FXo_Wt(wdFs-)(+314Ig{C4WyTWD>C;_Z zU0wICrX1$Ax-{2S!D!B4CFYNI<%BJ4`3OQXC+Qvw+yHrg4-E)~MraEZhuyDZC!Vr{ z8Q=*-(Hf8b&!QV2_B;xeDi7U z?Im-ZT2VnP34K2;uG5Mm>^gs@^&4H1Grt_9XC6&6guKEMZ`Ztu2lMq63c-jq2Btu( zW}f@hXbV--wZELE>M2)Zqs3k;q1^V0R4m!qrbK~3_QE& zyo+-#*EwS&H1Ncdlj74%gS`uRdvtPnOwAUhdU1d0#TeQzF1(&qhi1av0uvKY@Z<~* zXnsJ4v_TSQ){&S`b&xX@a7S6*>sM^!C&CW*uo4ofL#JsGGIrxjm(RzyU$1K`%kpvJ zrSmtdtDAd+wcXvdKXraC-)!&f!==Brg+qLj z-aA5Rvk?Tn2@bKXY7y+9e@L2x9jmBp3QWr)xRC$sd4xeZU^OX8nir>s9{${LwU#~35Hca;=< zl0FE^)I_m;wWrkF6xB(p6TQ>;6uC%}ML^9wBG}nM!YDY5a}8~rK$cRSq$-7`6|@BE zOb_vduGQ`g{$dj5t{1VL1WZX@hvA7F zlp41K=D_Q^z@|Vvky$&fXkA@>&amm@KMi))*8gqoj~l>is5XYkjKJm&k6c`;QoQ&8 zdoTZppeq@%5KTumZd|xz5W|b*RLTfH-)s&b%C`PRBHjr0t=To21H6X%RxM2ACrx-?z*}EqfnKfj957b1yrwg zN3+QZ*d4U^{{??>8Ta2b#!+S$Y;GTJZWsElj|D5>)6)qIE1u)ve|tzNPd$)JWRSi9 zVC6Q0!{^Wd$MJVDd+&P-v5)Iibw;{@oqeWqT-R3AlV3a2B^kt7-*s?lR!luPrrogl zcaj76&7uE;oC1H;W=VT`fDl- z9oqSe7&^X2iFo=PdMtEI-tG5#WFQ{HPI%Kfn_S?wak=IJ_x{0>FvbdPbV_I|77=1% z@8&7;rH-w<%=1dDU_OHX-F@&tTtPOy3dn^tv;9cy<8TM~xaueOUJs|M4_5B4 z-0vX$#2$Pr52{eYTZBB_zJ=qiw>uD;MV1jvClUPOxKz8t5Yo|}W}Of3kbZ(ymwTpC zTkHGvJ$#Oc0q$)8rPPsUU`c@QGXTYYIwNFXF>(#^{n1gW$v3KGK}NegIi6nirh-0U%)F+M`E$E(ihj0|wr3GS~ub&M=# z7CzZP?#TMu{>GEl&W81ztrhIinuXZuDI6gDE8s^A{kyP*0U(xQ25*gE?(upHbPL<9 zh{x)D7lEpd337>f8_QC%XllrD3tgPRTM*=$y%YB$N@ZbgAXU5n6K5*1Y{JZ(5rGeI z!T21Qlf-TAYyK7v#+Od{#`2Wr@4X+N%Vg{mWRi`}JHt0{)8oRVRWx+Gny_4048ZQr7^0N z9KfcpvLf`aPB1zouJc%NcDk^-yC`$8LS)U>3CS4eR*o4~2@-|^$R$jDisrUh0EBd} z?IT~Y!!s70;|Vsi?s_sE?0M$ewXP?G3aYxy7Mb#siS!HF1E<0`nsQ6ABt6uJAhp65 zWJ)PA&M~nrD#y%mpQ!=@RL&;3qz8V3*t1yG(0sY#@?mWCXmrjkgkTILz!;H8$j1(E zBy+Yy&u7}*sACU% zMQK+AX~}F_FgU^W(x>CUj`EG#<*7u`ZgU;O8Kr%cZq?&UUe=sHIX$nFnAgYRpturX zjATnVQb}j?-Lz}Q89LGd6K3hA6^?W=oXt=vP6W_OXR|3S|Fhq(%=JI(01nTOE^~}r z$6a26eAjl?FDWLDhDZg4^t(JezK4C?mw!3T3k}$(HZ%SS7-=0hQTI6Z5b^xk1+bxC zitHs~1g32BeioLr&B!f#5y%!WH39U%+* zsN+MVY@pM_S%}VW8C2;9&eEb_R3Kf1XF4F%*v*BPLV$Y9i-W+mpsFlq+UB9j_SKHG zX971Ayr6K39+B|lItAHSyj5_asDq8A?Qb~N82KyH;gN6JF{akeEW|URjlsF_;Ts-Z zA?FrmK|zZv04R;Ibr5159jQhY1P30g?N^^TMFq)lk;R3F1<5@};FjDQ+AD_@8&yiL zLw-RvjxsSO?g$VsyY5GPI%hw^`w_@hY^XNYVzfbIu~~!cK&p9 z1drNrhiVSE>Fquvy0;ctnR0r*N=h-JFI!7D|%1L1j|s;x9-1TY_AHCKm~FQ3k~U z)m^o#C1}KiVFa!mk#wzNS6iB@mnvg z29!zSZ}nOU>NOUGe|l%6i&hwjYBdOqBCHG;AW&v0b@j9ThyFPU0Aw^q++EWw4e1sADuK=iEa|WVH8G8VN-G!{&)l0@TZ@Z#_5fb|zcZL13NT z;Z=TCe^}v+6S#xpP5#a&&%XWNq_^@Lr`vpdo};m(0smMDCnRHA&ha;LR7H-xVAFC3 z&QPQ0LRn5|sisX!5ConKv1QH8>^;k7h6J`+x}v+qK!dygp@r#K+}|d*`#4ok zxq(f~IIkQ$#w5vn){{rQv@XEv>iYKc=W8z*6jb0S*F+5oJB&!n7{O;c(wuj?nS}q6 zv?j2pZShX;m`}QG54z9V6f=)dd<(~e*ubOd>go}+6`r{eHsVvl^f*7(3Lr`NW0A6u zNq9`TNF@uKO#Cl;hLGGwe)2qEQ7uuP;{tjcTWMA+lc7Jyu#8M)oc>jg-BkblZR%?j zqBn>I%3inH49k|ePwOMJxLPZNw6)|oaEI<-(&L}%gA|TK_g<2+_=+DTUGP{U-I{Vw zXA>tyXUNjwxdB~4!$+eD*I4O$jVkkgNX`w(HHc#X42nY7+N-NCcb*{RIoN)&^``+- zj5eNFGGh}Ss_1>Foq3EwImE||slyF^*xY|M*xy}yvA4Ou`TfQKv7tR@=q-k!K$5z6 z%=G%qP@qD^V0U|ae_$?VWjpFl-7%La5GhJ)6J1retK>k|?4L?&O_=lw`60j2QcR{- zyO(FRo;(2We!B1NfKO%Dc?iavlC(!t-S#;Xu*4Q^Kt|xNg(-N}%a6}_I&iM?U`=t} z*JBt+pl$ggdh4;i`XT15^&VzzA9)GYN zU7YsB4G1>JzmzXnyO&akQVeW+Vl1)^Y~$l$qS}FJ1~`Kw{n^iK4B6*fx*~3SDXz4O zIefsoozujDm!L#K^Os|IUm1=B1(xsN`;_uCMon7Bu0nn1LTw%t@b%KKw#0j1BX9dk z-StG5>oUz92v17%Vo+-}BuixyMix{s+m0LirUF>W;ofe1axml$N5XSf%8eC$bIi9yy!PDJ>Q z4pypiOy8Pm{1nwFvKDB2IzEG=8G!(0hAnnLp-Am0VK$6-R=_r2RK?g&ECy6I5*doH zCqLj#$y2c|g8|a8=}Dn#*BgvtS!i7W%3*sd-&C9(%@Iq7V;7(G) z!lZ&}U!jZ~$H*op$>x%Qo~V5ocB{;0Pf;PX8GRK-z)|^69Tze|^2`E7!7(5mYl^qP zZ>qSC2kO7L(w91;camhvX#~fi=AHJsqB}sJClxIF545BT?unT=1`asLBOYy#$b!v( z2G`GOC!WZJB2Fa;U15V3ha1(Y4VHjZBlLt`DEgT~Bv-vNq=85Yf-0|1;S>Qx=KUsM zOUD!L`3gb+samI~a`i^|e2FsZyv|IMDGFO7`!6tD?L(;P_&BT;F{wC3?)(gHA7NM+ zDcJXpNdhnX8yBlH9&+{;10+3(B4~zM_M8+D>gZwW$u}51CFArJhSSl`{HlZWZHkJ_ zIMj7lTP zN}~JZ{k^1+mAsV2eTFzC(r`VycTX%=FhidN1WxKZ^v8{DXgvZuyOR&ro6k><+pr`L zpy)rs%?uckyhm0+wgAXOEHcaUOs*sNK{9L^T9mvv&_C<}Ii4Z*w5kbWOCuu>el!E{ zH?z(`*p64D&s2>4DURH3x%FN^Ve)`w?bc6j|e{xQO&7!YPuaf_esREA{!A z+N0U7cJqQSN<*r)I7sOw#gQod7sQD9f{=$0TvGaMZi%PK$uW2b_}ZTABJM!;A`A}* zwth@3r7cAifSbie__oO34`#OT65hvH=~;FU>q7M(38b;ZtbRAJ3pSGt=qglz9t4|D zXN8WXw6-_;umvI`Cf0C|K>*9tdgQXiXD;}7qd_{`v`tzuF#A{$7H6dQpaUir@;wUZ zrR4s&@}29Z8yr4G_!B3$+D=Sp^-SmkEDsEBxyE9ZTk^F0DnM4+(R;cwO!t_V zwPUHH4ylJMkaCB-6R_AR+?`+~$fdb=GGXp!r2PEwu&ziCUSS*G+{CSkZI%mV00_*? zb&gI_L_aQ(r;Jonc>CJ&f_1Lvm#^X3n|r9DHXP?@pLWC#d(D)94r*bGqgBtcVSzo7 z91~;|-#S9R@dzhMFwt}9fY1P|vuwJ9z=YNSgls#-yB`j*S*|fzCaft(4D=*C z^zAGSstSbCzydSh9bH@^8)t~f)mi_qqbXF_pOA8N#50n_w_!qTmk+qClDb6&_s&Op zUFcIx8I{TU)791Fw7HZa5>+AHMDiIJx*Oh&e6D~Vphh;nKj4OZizr>09M14uKqJX? zX=%f!#yl2SnAjgctxg9@II?*j_%eWQeC|k}vJ(`N!P|$AfE$c-Amc7QOh1y|m~r)$ zq^EZ-&zNl{nS(&57zSr=KdN}EPgv7oMA=c!SmAV^kQ2j|pl%`tfhZ@wjJCMqH8df( zx(b_d-Pqw%d?~t0HLXU~diFj5iOuM&;g7j79TDCF&-RO~0YRf@ z8|(izSlin2UU!s_qURQ$WzKE`G+R6sbInI055gDU+iAdzE*f)ihU@bv^b((emDs^7 z#dB1_cPxg=FPF-KESMHM_FSBKCP0MaK==>!I3}Vbx4K~ z4+?w;5Q-={O}h_H$4DKY!8lFBV-af!v zR)R9SxRGMf6#L5A0D+c=Xsq_aF1Xaj58bJs%-lkDF@vGl93yhUdQM=e{hRy!?9EP5 zepi-(kc-p?6Tat*Ek}T{h~&8N3?WIH*$}+l9f;%NHM#uXV;8G;utHCU8%3L#;p$!l zk=&HVjEE5OTh~$&H>IJJ{!WKKq4KPDMOdJsuvd&>HBz~PRCsQjS>W6c+F0S;L+8sp zL@OgT=<+iq8kq4f^9zHOGi=^IRarXvXzzA@8`kN!VPB#<+3isfN`MlNGp2wk8bkLF z$R6<2TSqpLHHAy)>|zfioDN@3DZs;3RVc5#Q+??SLTO5NV#AFr?;eS6!>M`6>9Iye zkdp(Z9E-JTPoDmZL$0_eRMnuYbZbn=%jaGPL295jAP|ZXwI3NYh-)D9fcBj~mbZuFQ_C9Dwj}g$sa>1BM>|u==4AEge$nx(n8m0hT(RL|0C`J|Bbe z(H_pn%)JKUYaGiZE#oE#S!6%B$TL15Iel3snya{kkMjpTT%E~I-2ER9O5O+5NgS5o=85Q9>dN%B z`nF+WscVZDt8V*To$@fvY}&Mu!DM0-E)0a-gNxm+XuY9U^XcsEjFV3q;)#R?@TyKp z!Vt6zhQEr&tQ^nuI&wyR-wV}#@5E#9@40g)+L|ce|W$D;6eZXKX)EHSbg|l^}#=f zwtL$zch@(HZX!@R;_k~A`4W}*Or7&Aht|d|~ zflN@8nhjv{dgOmKH8E7RU+MlU%<>Rx(ob?oxetu*=<6z0m|oZIQaOh6FrM`I^TDwH z=6?TQUj6Os`@hI3hYYGT&eh)eO83j|O7@+=WExoW1;F)amxlp?y7Vd88TVT8klQSK zM31Rfn$5^7^zOwC>Tk!IrXbTik`UCVXyBzl7cK|7Yyx8H3!CmxQfx75+4$QfD>*YUd5a*kGF@W=4EAZx> z&H=ZgW8jiCNtRFfU=8pJyIc(VO8=E9RJxxt57mU6P_kb&*fU1H7}Qyx6?n5Q=RjKM zRlVXj^lQ38qRdE$yLdymi$`taf{z#DRA#VXU)Y!prLWg`g1p~Sx+ODdyyILN59iFL zF5C8B zFXnVTnt(!JLpUM>3~KM193R+O+kdtPRO66Pp_;JfbM%RRhe3#oRKws|)>9sP1o9`ij*tuGSd9*fm3r zSf7(CdF{(eWFEn29x5S(^*kQWj)v1?ByaGRUR^Q&#+vNlN7wRk-*|U>!q$~7bsc9BushPMeaAh@K<3Y zA`x#1yB2j@;enDpjgzYBY{^D!HpBCA`i4q%`rDz=6d@fJWowj5@51Iy|3|M>Zhbq8 z+jS#RPkRMMCuR~d0eDfZbnb61uFh?fko2+0GSkbc?mcBm-{G=bZs8TRt-KpzglEkL zv)RdjN~G4pB_S$pnExzTs9@M)MTQ5jkrwh~49hV*;V@$40)9?%I{pzUPHsY}bV{Fd zWmCzObOqdRoc%bSPR^K%p1h#DZ*=?&v>ZK##AwYp8w^CsOfPu?Iqwm2M5c)g;B^{! zKmw*FfVgL21ge*0b=4P2frh_b+okhHKV|E5S;IFCaC)yNlao8Rb*PxRjS<0gh(!mf z*xV^bd+Cp$7{L80mr*8a4-gd;2u#5mRELm6me{(Y-lUyXyHG_fTl<9}Es5rOXx9od zt3X_K>iS*Sslo4H9(tXkQj6r$SYp3E=OvcJQ1_KYc@g&29c#{n-do zS2B%;ttE~ea{lo$9AJ+ef5Q1t7cusg{wC`!e_u@AkC^@^9HS3X8z1r4>k)FjrbwQr zH_pmeq&wqa8NJ8AUlMl;_>`+W`)Fv6LT5zsUTjSjd6@a zGyboIHv^crdp7h*xG;&D>(M5Q8K^^67^j6Jy)wY1k z!ZGam2mVb<&l%^_^=!383@<5v-^eF0~91*N`-TH0w6Vko!=g?Q8lrBVC z?4zcYL4VkFl%vmzG){-)<_dpGZ3kWTxT_&ZSc;k&;eE>Fa6LEPDfQ{~+0n$tCEEJP zjd1^6dKLV6Ftzm{{?r*jK1h2#?y?kC(pU5o}baeZa-vc@KCJDIs(AHnnH#HWZVdFx{vmdd)$j8tI zJ&aF#>F^Fv2y4W#P>gRA>3_{aIheJej%(?W9(*CRVZVFGT@@1et=)5(yOxmg3dLOF znB|Ld8t^Zi5a}vc0o0Oaz?=Ibx*vRv8aCvwzFL$Zk&}st2WD&yE2}|7C3sVeN(T~X zb<;yo8m62227IciU%;QiU`fqne=IgKF+pHO8~T>(nD#*ZC{kswn~ul@QyVQ=bOAYC z^~zk^CfB6V#?sO!wd#oKfsKb9kdk)eGu`p-vszX8o(8Y){rPbAMehL7>lIY$iD8Jm+Lx0Qky^&$qmqEz|ghpLb{vlUik=k1Fo%4**N zx!U&^aP;1P_%$#InWisK=e>O2d>IZ*Lp(TX6YiGP(WOl$(If{!t#gz1#gF6FD8!fq zhXc|~#?Wia8DJHFR-p_?11;hBPk-(mE*Rzp6SI+i+293CVMBG5-A7(n$7`NsqPy3a-DtB*oS&>sq@h#mluBIm)~4m|hj zC{uX@lnQW$EG}PHp;fh>+C&u6_DXd)oWYbMbb>RH=}ifQ&!ure7R}A-s7hc1Vib~3 zK@Me5zRq=4nNUyy0uOYg=n1scoIoHfbJ1WcT1*LhOny+C%{Ps^b7EU#%tfaO_JJ(Ec#mkP%-yAO z-iCn%FhZTO3X!vbcAd;!FHVTVG*cW`q<8JG%Y%h^M2@M_P)Itr zB^-DAZs#^y+~&I6MqKW;6~J*yROqo37GpHo-Rgx%MFhRY6;%XXGM?Z@cd9r94$IFV zMoMc2{&-hn&5l{dBVVfCQa81W)VZT{1-`SJSyYnx8Vqz@X~_KoDy?j6CK88=e#zkZ ziJUDnw1f--b>maPL^+}9@Iz|dsSor<({MkkI6%`)Br^<_VL33;?gd=hg0|KH3E{Yn z7)sgB(OH?b>Cube`3);;LQ3ro@uBM~`+f~#E!KgNPg3e}=@PYxR4o|bDAdVognfG5 ze>(iz;c{;UYU3}|3<5N`K9rezcZAw|=2#9C7Fn>pk>fn}4fMIt5bK72D|oVoZfiHD zAI%~fN1Qg9bgJf}8a0AVKO<4|udi0+FV%PvfV%UGXtwfYA|K=+lnm=TQrFmn9iq|j z7yv)azze~pfRIA?Vos7bMj&mG^T5(01CH6ZYJV+0X!gy-?qy0BP;&8E-{i}Gk7^cX86 zf>plIV1q4$1DyjP-#&$ox({E)_egz~l9jjzF4+gVrU*CjyalyNF0Eo>_GAO$HLNv(&MN8$SUWXCDphQU}2^ZP8&h+OE zQ~7f4NI*DTk-WA127}}u#r|tkk@85(%zFUBs$(?1QiL@=4C9om1kUgRE5iZ}H>3_K zp6h;a=YQhj#Y6t*DN<&NEV=LwX`y3Wd^ZGf7EL6PZV#WH9eH*>7D|)o_<2fs9XW(3 zg*`ET49#ykohn7T5RZa^+uHcF^!!8cF(q*?V~c zfm?x)*a8d3#Xup%rsEIJE|KmqJ~E?G(g4IOF6N%9^HIeRi$s@RN?wD%RNI9tCT%Nn z*HoDLToc=~WugZWiVy^o-v1B_D~dD``595&)}7!agvE5BK$qe0WgSkCxZ*@U7n3Dj z=P#Yrmuo3n|DFdW%wiH&iD^WocUSvWPYa0 zL6c{G_JqJYhbwX~5shq)sg)fRFse{2#DXx`2@7y{k&q~9BX>Iwfn+7s3}Po&`tV`Wd%-`P+T7Yg9|wGH=2iIVBdVqnh6?W~4*2vy;1elS-{3+T=*{;3wVTve zBiWzIf=d-gx1TqUi`TyM{*PNO)i8Z2gch;|vh5d_iCd?>H)WY*XU&zJ4FhGdEymBd zLKTy!MJ!fY%1%+{e;_@9kI=Ys;uCvZrch%JN-hof69b@5V0)LZUqJ9Rps$iy0yUw{ z*s3oLA=lc*V-Q+`)2hTgFJr3-3ziOr)Vm&B;AAih%``{6*70yW*8#- z*me-&67vz?fV-wA3NO>0i2-3Y^SbF@6MDkzsAk+|05{NdAp0KTVR+?rVr(Z~wdY41U8B~%3A~F);=8*!VMi~V2<=KoJ zX?MiC6|i;~ML)*;s?!QA%jW=sqsv;aT-B)q{c__Vc)b6UxBBuYJBL449QEL}KaNEb z(9OZb7`E^WdaW^zQ{Z3NhEka#{kL?f(aaNw!+m%MiBIxH^L*<}pi$XtJ(pnEsww%2p|G zZe(Hn{=WOGDS`RNU3WoR3dWOF{4JId>&k*~QB^n3nJc{zqb`P|X;%_8hxWE@%FXzbC8#m3ZNhCF^+j9 zkFNVV9E^tVU#beem|Q%aT%x+5UyOQP4|?m$q5cJz$b#+tA{PDbx{suKv{tAO&fXh* zqlH(m`(RscIBb@ir@MO(g}%kh4fXE=#i~$9{OoOkqu>%ZRO)!?Q#ra=Jg(m!F!x|O zUalkZ25M`N6-BAQ`Ms44o}9HoDV}$UfjvWd@=3jt)-(8e;(_`C^rE1_q27I_Q7D84 z+XfEL4_{37W+&k%1-brF_kTjhuxr8hj~nC-s~mr#3JYeWDc{;x@?t!}Bj+W^U1BvT z6oPp>q40#(GLSz@oz6Y^=RN$?hx}>(k~iMepAMGr$vyPI4?BnQ-~SB%(%E&k zX^@3xgs;f#-Q@*O>F$o+;sh@$Tk`mm4@E)eV!+c|x)^hS00LAwXnR5?tnoi!Rj1K$ zulv*S==J4W%S6J;&xO^Lz^FE7*4+$u z>47{H(=9Evjlx~|*6&@?SK7F@xB17-7yGFdkL>BU1c}9Odk?-6l|a?c3hW>djif_# zE71&mf})+u?cNS<2bGaww$LLctPdZfNXBF;))JpmT~sqPwxJmjejRkJz+#svhf6#| zBEGv?1L$#U%$EQapF%&MUIBN^P80&_z_SOspZhh5bHoDMmCr?S8y&rybZ)~aLkiJ4 zi%j`E@}IMGW)05(drp1qmi;;Ifd z4D&ruV*&!BgK8waFG0VwYZJ7Nw|Rg}&6D!~FylI^2?>h*6|X~SattgBsP^cCXzxo% zmVsfrx|%YLuFF;INB^f>J`l?KDZx-YXhKCG0bZOa3Pn&QtR6B(Wxh}A^@sMJ{E8ci zZAFpxyShkOTCNx^i{kLAT&OS9bxfZZ0cuucrm@uo{&<30tm@#m!ku`i+WENx(0_D& z!Ft;uSRkY^_hR-p?xV#%-q>AR-{0JRp*+BQ1&0=FMB@7<74na}6jnc@S~8zJ|oF!IRBhaFuU6ZWpYsj%Pn0 zus!(zmm1EYbwP4+<=H(Lm-k425a_U^^K}S)eT)0I<_@JRVBQm;kb)ah8xYv`#2UZQ z$12ZlH53!L@GDAsd=q!<(K}Qhf%`r5$VsaW-l8IK$k-gMOEj-bU7O?1`VUXYfp{j+ z$D505aam9;ge&2g`7Ri#3#@Sii$^D$tZ71$7o(&rQ@xm8ac}3n*o1JS)3HSw#6==tMXq-^1*IU0b;!)jH7hG4+5EYsGb|REusUxRu{A zvoM)cNG}BSb{WCt-&`;Ljmz;+dkX(}hl@A)_wjg$t9A9X1`ize^>#nLdV4Z?J>>0G zUA-8CMQ8rPqBD4cUZ|xUhoc<=kGO=)p%)k@))%-Fo9QVWfacj<25Di<@L&4ldQJqS zFrGicD`;tYuo3TMXIyizh$oJ4q6j@;G3_i&?Nzde>-;{Xi|PPeBC-~} z>rnogbg|OKxuF%%;Xq7UI?JTV9R_bd$N@$smrmgnM1lErTkX&%`vq_)ijUi@X79&1 z|9l)XOy!V=`#^0f@LywXa7smv>1h7h6Mdk19Jg+| z>E;kUHW)pVY);u_OxU}}N}Ctco*aM)osGO4hJEPJ_)E@lY56S(USkP=l!G#BlAA1& z-<`{P`4So;s00-k2f}5Yo@-flXbT`+rlUZBhnJAZlh{tRx8&h{QIubr#Fh^CT2G1z?4`KI%``(NFE zSi@j9@F&L*P=-q&C#d^(1WSweL86WVivGGa=@GB05()#A3nl4{{zLpl zm@q@S$19!vcYvQfc-T1|{skdR%0Sb z#+hw=FVoq4@y7ux+=FhCJt@0h4wt67S9=Rn&e(u3LmWQpv<_9HE8m%szQS<{K@kj+ z(aGeTx&-b_LFpoT1_EkJ^a?*tfZEOGHDWl_cuw_@cfz~|x!4jRJ}*Tr zOvk)UJqM&9NB-@$GL`^>-!G*qaP(mPa0BgaY(1qk5s^WTv~;U#Xe|Lrjh)U56mg9! zUfpUCA#UK?{$a59a%W?gC)TwYPVbEARqAzdRN!pDqh8R9u!_j%x|Qm?-Fqv?Qe7Xq zDpar~S56urfJ(&C(IB8d%;XaTYZz_ESP)$9!apSlseijErp2J&E>KCwLKKVCn z#$eHR9zOc*13f$hm7=XL1dZ!o8ZT{p=+s09C<9vJ|DXiEKt(^(;bvDe@q_Y_T;L`$ z>>hviDaKBZf7d%5A0c5#SO_+f$n~C3a}m(DUhg7)!SW4*fo9c9Q(!KQ{aaJgZ{GUy z464|BC~%<#w_mz<;;X8%9+{RnG#^n?v9oACsd8~V!M!{@k1ZA)Pf>$m@wX=fk>R*J zC6B-85Lb>T8fGF1A?H`H?Z3IF!$Ax-QL6lh4pN%_hWU1-S#gXsBet~LOS$#4#3Q~F z{Nkx5ByjhuRQtB`7sLb+j^Gu(x>Py-$$QnRhuf?G6NWDZHv zhuC7;qZ}VNzC1mZ0#-s#Q5M=9EU@VDMO3%Cn60kjhLejavackMqUV*Mfbv)>3g<1S zB~bAM6Px)=tL588!p0-kuE#QJV~CByNloTuhC0 zF0T87p;5uygyIaHGG2=c<&xBBjtJN%Q#rS|9I=X(o<6EOLQ zD(Z1eh4+Y}7k9X;6J22e=<2iQjwFbN@65X?^kZJl{6$bE?6Wv_U4qITcF!dkq^@EZ z11pHe0GAp^5e_W(?159?llnpS7gNkux~LJ3^Bkk$`|5{$fy6A_Cer#f%?4nu;4BX3Y@QXzWf0rI}_cl&+&K#Ik|soIA=2#86&8d?1{11| zN5Kzw)u0L3?sdJ;kbRTL&Dr_rX#8e8LRv6fjImGf#ax=g(`B@yRdHfdd0fNXyXJa$ zkY3Q0MvQ=^&21sUsCCi6#o|dCa&+ChIJ8B_hgfrYs7NyrXkIC2n6isC#VzHd;%0%C ziPP-r20I;}?ZGe{;Ra^9qFDB;^MzIa;<})2-|-u~fD`on;0+oLa3$3dBs$haJ7!FSYC6sWkk|R6YEJv`=Mhr~IL{cM zCXE46ATB6oEMkSlqG5hIyyA_a^miW(sn){iHVrh_0*mWi%DgGo4 zQy3R+6piSspuo{3s5+*BJ!Bat_-~<6A;jfs?9u7*3T&3n8gfv<6Bff<>B@6hJ}oPu zkJaoeM*518J~1MXiIIN5NdJQdO8dD@sR)54K6lXz)_MIxLA}LA>TruAH>2NK*R_Io z4VQbvd!ds2qH;AieYy;WtJ6^g#9V=N>Ygeol_FR=`K*p1OJjcNHlE3xiALO%q7X237GjKN(=cnFoY(8DS!6SnCe=|a&;9^b^y`b#RjGKg2f}KS<)4_vArekdAx!CuWILp>V8zD zaB4v=>m{m-FcCjnWp}X~yNCP_NjbO{*l;35JJy-RWb=F(f?`xAF0mF}P+F3#9$#3% zn*`RhQ&W&AiS#Un2QQ)?LY-sbuZ`Kl3CD` z`)YZ+j`WC&5xQqaMoXKQ;)HK`xrf^_j&P@mIsgi=Z9|S)Uhg4+uGS{m%ixv!R&&tONiyrMslHW3rBhz#{`^jEB5uK9G0a-vO2w5RiV9M$ENDw)5+RD`w~uAxdc&M@~Cgh%Jj- z1iY4+W^jHA*XIRP_8a^;8Qpkt;~U8A_z5zqj*rc&NaB&!O)sNsG|6k+{=K&Ka%1pf z`^5$b^9k;aK7ok|e;KuGrsG($v$)t`FhRXUeM*20L3tAx&L;hHJyQV%L=~%cFn)N4 z1K5bEAqZhAU&lU%bLB?En)4;`8=>go8A3OT9iRU02JGS8jVD0C=9_4ABB~D)oA&iv zB=Mdl-XLhz4HwZ(VFOAu&-QwR?C{DL1ez0zUq2Nlqqo>aq0Qj($)DXIw7kRz{HoJ z^Leh%TxS<4Ibw7g<-9dFLZaoQL_S>xOliM0hJe`YX=UegdUt zbPO%zaun>sURUP@!@*{K1%DElU`Waf&*5Kps~(%p2nmHAW$?}$T+7J30|!q%n-Rqc zY_)8|TQ1hF$-Uy@e*S6cWZZD!HliRt}4#c7n-7ksZxrCG0(%=4INJ z_+W$Z@>7*iRLEJd%sTz@FeXm0;~-Q=PboGM4xv&|@d%a(+$`v@R7BFVEXg4Oc>gDf zVW*yMcHmxzvLDT5G_w*oX8a6GV79s?P4bs}8bH626#d7|{bw&95A?9cp5~J@Q(N{o z_V)J%Yr7kR-Ip(3AQwpuLS{L*EsN2h(uF`Q4xG;y7I7{q~zQwsFozlwfyPS+XkXfyuA>YOkl2R7@_GZ1;JCg(nIGT|H>7LdI zVB-^^u0UU;0{wF>rL~{#ek%F}f~0mVIs67m;g6Ewvy0OcDD4pA;dMALTRaDGdY}rP zUpWLDYS823(OU^cuEUU;2Ftj&^c8B%Ji;9lbVefg@+;gthM#aW^2bB@@fIPCGyeQa zeqN1u?v1~{woiG%!_rs3lV7KZPo7;2`QZ2R;OJ^7AOExbLJs}~TZ2io0{a-C)Pemv z2lnd}*spV7zb=6NIs^9W64GKNMSp|8sHK4*HQ=b z=bv)Rr>sK-6XLa3IL+s*7YVvt=gO%$b46-%;lYjG-w4G;PZfGv)@Kl`7{R+O+X2>P zoGlzqH*m4U`G`SW0@OvV^soNq=db_e=lefDeE9RjuYZ2{yPqHa^Un|e#~mI=zWDBrJ^T*azcJ#d0d6YO$LrtkM#^zRH9v2l6Kkxu?s^37IAWxrb@$GHQ4$ZYlNT|y82jPi4 z7c`o&42-D>$3-0v^LDlF%mYZ(MFbLV;s~3h>>S%{zCb2 zxy`C0vx2|wMHKk11JTmYVnEpTNyiSCF%^?7nu34Z^>^HAxN*dh^#%!o{STw@+jkeM z+=j%3UA1IiB>OS>?g9zQNS(np6{~MmT2US)9caFwlQxH3>E3>X2xh!3YNh+lJrc_! zE@F<1tPwWDmeMO-Ho-q?H723RLE4VImbQDWYCif(Ycuc zqyRi0o>!mae5Tx}q+5{8Z60S^X8(yQ#Ge2{9>daB2XZnb-i3~#ER|ieC7l1zJBSd( zkC#uFv5dbzyt_(|QeUxK3TM$70_Vcn`WzL4Yb07?mGW_YXpZbK%6({*fOp^CCqMu7f(aRlUe&7CI~& zE89htTZ!mfYZ}T`R17b+-@HM1w72}fD8=+gvb$=tI!N2fKn2w(XCfnG5tp-O7|xSWL;cbX`>)v-AN&# zJ)32F531^gkHPO_>|jsK#K)JhQekiD_WtyW!Au4dMUM-XiNT8VCt?}}3yHBdMExe0 zZ{P8TI)c-HArTK(O8aYHgO%GWe)p^(&#*vUx`o5~Xs}3# z009g1Qq(-)*i;9~mHM{R51-W3o5A2>VyGv@27!QnBD#~#|FHCahI7+M*^UF!B%EC^ zgS&f>dVJD0;^axFm9`+&LYXu_hW94UbOq^UE7}0(sKq}ZBhbb33O`-1!H3*Bi@BSt zzJn~rv7_sX(w1ri^04#Wut7l!juG z`c$(nu(HAu;V4ME@W(NM3J@`i-JG5S1u3AD&Ori zAHD{-yrGPqAA%_RErmewLse3aOTAtrFhdEx@)bkF5K4LgL0dd#i`R`7Bw{1UWWkZD zhwy?&m~k`>Kh|DBQ@y6mV^!YZ-h_KfNCTrHY=~H#Kj?*tVU7yV_lhH_OR;PSYkt@L z_FH;ib&}~P4u3Lg|B?9W_VkboE{3vR!x@pvO2HU`BWy>=51|oTlv!_wjw^9n8DkF& zm$j!o9oCu?Yzx*oOQ09ssE1D0bJL$^ejg`A3OX`wDn<6sb;H-YUXt8U^bad>qR9>7tifBK_Irq{2(~W~& z^g?McuM=kLU5tczBJrZN6HwQ(l9Pm74y$#^yQ(YW=CZdeBd))o&s|E-w#+M)i5L>5jF1vXgAXxLFcKG6|n0p_|ZDq_}M|p%&XoFR)igDs43C zp9tqNnMc#q9r5$|1l&cQjsXT{(QjMQm(W&u-(fCQe~7tQMg%X~mYFU{Fr4PXzr#-9>bL zFAvj~-!zQDeB#4bE>W7jFWy|X@N)TD5RW1W&7tK6!A(B4iE&kDKvQw7Elm5f2 z?_ne(K?C1hzQ$*jZ|)1zrYyph7@M;_5On;=21?gBVdL7)l7YcbU$=ao5=8sws`=!7 zNG4qdRf6gQ-0wR52Qc|U6%b!C0MAk&ECUEY#>Yty>Pxl7z+y^EoZGZ^X^*1n>VIM! zEm*)|jhj1eh8voXa3@Lg4GwN2!-qOw6nxrR7?5o_mJ_AV6_LyL1YO94KcvyV^noTa zKkYGLUjP_|$%ER*oC0zZkhx7-h^vh^SA=QGU7TTAVBK_BZt_%pH3b^QyS|ZnsEng7 zzlMBvd5#a1Lx%ov&jZL&8X4Bu=`~!NH@o1gh)Uz;qCT0&+8Ml|QLkfK9uq@QW5ZRW z!C|q$u-rGYBsynPKHUazeh;eWlx;Tn6*PX1oxYPHm2F>#Ck!2P0u1L1j!y+!8AZAh z4KWnyBwH~TMi56$XOIw0t~{kgBTJpAz?AB!FtLTxm?y&ifdC`QwzW)F(eZneg*sR4 zw#gUq*;^a%gV`=>AKAJId^EKxFzeQ`yad@{DoC=qC0j8NyC5@FA%nWmLdyRT(|5IQ z9?5tc&hWp;_pt>&i4BTBmH|3CN{4P~=fsKaZv%#}Dd3?f`cTk;@vKC5lV0 zBw|EbyvA@bp5auA6eom$h5)n^A*gK0x{^Hz!i2@;=o68zVO9f#yh8-d7zG>54Qm6^ z-}tk-ALA3J4gnv8CZMrpn#^a<+2(WY(5C~R)NIEhc%_!=^e z@`kcE4c}l#INnxvUbN8snZFB71`29h45yO?NQuhAg3YYgwm~D357&5STKrv*N_?>Z zUF|~&O`4Tm^R|WZX>jv5q)AFY z=cdwMZzU$0R6cVKGV(U{9f2_2)%;F44jTmXPi>6AK%s?7-=U zoftJ?v`8fZVtP632Uj(=sE@3G@gvofvT@mgx$LY*4uWs-FOxx^&#j z0fz~Sufd=9D~c&MF;vnpLD{g$T8J$R#x~z{mTcTx)n=t&6eF4kXGEpVCq|EMmH`qu zUkbf;qi>};bp#W{sc&JE@Jd}B zh>@)Ypb1p#{mm+TELdm;(Y(qIq9if;HDuuhJDUR){McT!@QV8867g6gS%Pab0`|=#F;;>vm%Yve&kPgNwVvK1o6v zg~i+A{b~Qe95&(zn@cF_=V7j1g=BEbOG}i)Suh*J^g%^3QVt4XBIv*yDj3DZ{ajPh z)H__`J6pYX@2%X9vw~N=_(@)ee=sBP1eMJ%XUOJ5EoEGVrPPS}lN03sVLZ4cF6G?Y ziW4PwUvuwed387`4q>pE4@)L$o-F%!rq&e;7xgq-%fM1U*`M{99a_;{h50P#6;jTo zK4v@p65I;GXx0?Z_&iCH3lD=k06Y%V0q(N@r=1N*L)`8r{xUiZxsZ{+nk>Te=ITlp z(@Pw{GL|3ukE%BHM_nxI38T5-5h#?qg424wI^e2Bo>hpp4JK2ojO_=Q43HJEl0_ps=~wv6%{wMO)w zH>7(SOu~P5(8a}-W;*bks8kH^^uED{m16o^`H{aoM0zmkBg+nPjcyA3iiSPC^5&5b6hr{c5vl zTP|~pQftcgq5ZuxoWj(0hG3*Dp7qa8$I@$)h^^$Efv=Dx2AyVcW^Ws;Hg%enXh1+j ztMAQEIyGjJ14G--o`ir-1`iwP17}@LIRNIjsSp@w6%#QZc5Yw$lH=OmRR*8;R5Ij5 zHbs6~+lDz{FdJ33$Oic&gR~r=xqP*;Qp0tb)h&&hlLfPmL{G0M*<8;_@>MoU`$rlg z$ws8hJ9dR|jgw3YK{KKu$=uCR8x6&Go()(XpgBb?nO5QxapcXtovpP$4feKQ?yjRe z;>J_NGm;qul1}0O#O{;K+-xcf;Vui&AkNc*xuV&xhBx4E|5w9W(m8xh%No$8O9hmj zaqQLA@$98sRg4NJ`6d$TwZ^<3!8la9oZ+ox;%Pq+gW~FWg6o~_3S=^CQ_~VqP--?< z$d!>7keUe%q%I#um{M<1*i~txPxA|5LN{s{Q`;V` zPCv^5lVqaASDh87OVpNg)g!aBdCk{)6c&3sq=F=EjG|maOWxq0Qq@}SS7`Yfx4R!5 zjn3sb`P1=F@XAc-(T(SOv8W}vrFxVFADishAwrh88naoU1rJFZJMx*jVHce0 zjLRE7MOGoC(|@$c@JLg;77I)_D(ht;J;3N;)+-PpQ<2-0SynKnSU7-X?_n}*&K5{hei9ue zY4EcLF9+BPDtp9Mn$1=u)+D>?KnBC*wCEzQ%VplRbPHyUMdx;@d0k^bXLw~;^YUW! zLuJAV*a2G&l3nn7tS7_HMM{xwE+-tKIh%9fPSi=P7$_hQkCSbX`5v4bJlakq9gt#yX7j(GE7)1((!lIqj*Ik$DbfVvJ-S z)X%Ukr6wc-e(y)4bI|~&ay5@!5)dlZw0~v;%9qDD)zH20ph|Ips&)bGu?Pf0y@@yp z1YY+fw$jOZ*#Flug+Mf9r$lgg5ST2Fy2r;Ro|;YVMK~BAKhdsf-jIw*xK5gp1xfk_ zSrzJUO3nUM`B`1{sfLNAoNsk?8<)ZTJGMyx-hfdAI`fyz*U&`f7uYH1IMtKvm1fAK zN7ZzS8_<_#CK`qLH#}lW(Cx8_8##H(2`8e_7`Si%D0tVN+$GMm#7hgd60TqsPnbQ& zu4Rgm7;rvOk}D1S$&ud(p3mnW3OoZiX5%DVJYZU7lq8yhcLqYq2rGG>psMI%F|4I* z#*t>C?_*#nx>yi~24YHH3%mFlrEh-vhYfHzKJ%+vj2F|AUQF|hrCgdCO%cnmB3!p= zSFGX&icI&*gsC3gHdsUyLamVk>bMMY1^*tL523SJ1Lol0?ff1m6?jwR-u~`jXYI*g zYvbuYqK3ReJvM}clI#lML|%cBsW0eNCF|$08#hpp?E-!pT!4#0^3%iOcVUs0x0htr z;EoFKMNYIOrJIToyc7F7NRF0f#$gZlywKFs@d;8L%hqx|mR(6CGeojy4!gCOj=1W2 zuc0(7+U_t z(rz`(LF7Na9G~F&3TS@#<2p5bU2Zx1QscTQiow_f>Y5fCdwMc_TfCe4xwrBozKF@@ zd55EXZ{#oKCZ1$;NmbEue^Cif-WwB`?N7FFu_P3iYAaqk8T|p*v>HuGOl^&Pz_$0- z+TrPkVIA}&1n6P%VGDWs)i$&Dy_Z-bokT8Pq;z|Q{Khpk7;LVvt-O4 zhSM{tV->L%K$9$WxtIWns!}LdM$`Y4BB|n6sghW5t>n}d9w&sAJ&QDjTk*C78$+OA za<@}3SzHX9QyQQT_Oc3`Dr{)hwl7R9=0LN3{)ABmk)O#l*edy%HHowGR`E$_*qlr` zztA+hld4>z*JK3b2=4EJcA}@5vGwnEKrtv!cEOBb`3G$@P2bkvqP)D62xlNytBqkg zL;g-?H1E>@^#>54T-A#MaKawp$tVX3P=RZtdaZyepsDLGiUUtzGp`z=DojacUTI(m zR58R=V5`P>11)!%pA^y0T8ltvopjCAQlW~nJ+2jYw z*IHavz~Cq8Y0GRX(kUiy22G~uJMl&~f)U)1*tdd0je)|cwK}@14p&36u}b1gZX;9r z$a!&MxCiZwFwj}rl#ynlMNn*r1*TmNTb+6PsBpPp4niiub>gF1Edb0~JOnt$h1AO3 znr^ZLBQjG*Q#wt*Zk83`r+YyZi#o>0n33LEkmH_1U1e=SvCsJx9;b*Th4Hk0{xl4Z z-L7ZM;(8hsT4oH~BYuiHhEPUEwAF)$#rVWEwoCP>SzP(B0YNt8S!0}F$mvdZ9ZLB| zdr?U-F)-s4!nTFK*O0*u)H{eCk}6U9ku@^SBmam@FOC{GTp^en-?-5ZQi~If-=Kn zu=JEjQqiOCoVIz(jP<~;4eM-0Ik!Db_fQ(MW8-8+jKQo2M?%j^&ssOKvxCoz?jzAd zBaNT~Rh8@zvcptywz~#<-5J)#5MP3b6rBrnA;v6jN5j=GfX$pK5t$|Ulz|5G_H>O0 z1e|o%;hp~|_gvmxfkDB)L+51fMH=NfTg-=76j34gd?CEW2{=g_V?7983i zCn{vIzhO>U_|qk*c}t-}PY@&IzJ zpPito1z@fPd`@>SOEHu7&nVA1RAFCaYQ=^Chct-HYk|Nl9eciE(QmM*ihi30t2M~k zb)^Su&@@wv5h|ZvJBP|Qh0v@wf^$>367HBx@#ZyAI zdL*_)q)-7!jTS5##a9|fNNmMKUeX;RwFsIDmuOd3-N{DPxNPEP7dX9~K_+wythw`; zOhzXfJ{8qix3Fq4z@eKtreV5vlS!BK6Yrj(gj)%A$Fn`ue?=z$o78I#?9@IM1{iT8 zU9dq$LzfR?BOg|7{C zibLiXBQ=>z7NJ|&CII9{972ay^%uN>yk-u#M0fn~4<(h{3?c1nks(A#Z$3<)j-@Ir ztyszgdG%98WrI1et!>j;L*=aShg3Mz#}Rdp8banN+*Q79)KIRf2pkn~kLdyC``UOx z^nosU;bYCe86i&RB9QXIW+-J5USDxHl9KG=KkHl|5v~^Mn981I%c#WQb`OT`)w*r6 zOU6gj#!?_7HPW`kd$H@zCb_#JufL1W{SbNtnI4w7vLWgtFoTK|d39CF-xjKrhjH~& zqcN4H2QmY}93sBqqM_C{D4t{Tox(p|5KaW;pCE*0PpOonWC(R`K2Vlugq|*evq18S zW?xPT)|34drhUy!f}NS@X~_4n9&$}x{6F?45Mi%{{oIv@I-# z7uf`p1nY{h?fLL6SgyL_uuo_;NWzHO0lvDae!+~gVn4YNz?~N6r5;WDnbNhGLIziY zTG!`VZ8$-qe9R-WcYs|1E5{}`KM5cKC0P*=P>Z0<^?RohB_r<9TcBEVfR0&FIeIq3 zcF__|X*+S;*-T?InX}=g|ERTgkP=)SE~SRZK+2VbijwIK>_sq#`?fXA-$t71VUV?m zd~@;7ReN$Gn7it)l%Cj06f~k%t0T@NZH!`6yXWE-LqO=>GKQxm%gJSdjg(F6 zW^EtbzSlaJfh=vZAUlH;63$w-+hNXN;#c@rz5-O=WZx8=s|}}>N%Q29PO5dz4zn0U z8N0cRp&gBS7E9sSXm17@QV*5`G3h`0fSZy=8!XV*>wd#P795?A!dhK*ZX*rFV8TuF zEDQv%_@p!JkbQY=Db@}_jUbhZ9n`t&oZ;o`5$^UoK~NY0T09z_b#w~QJl5=OwBR_4^-CTc|%gIYB+obB2e{jzFlLUjK(XtRFN! zJ36^MMx`r)l(ty;=AKVU6CESt(`EQ%f{3itEyX--_gdop3=n`>&lOru+rrJN`!lS! zY=5$jHcnQL&q3*B+%Dr$9+%5Zf5p0KrDhdMb4DXLpuI{&ZYH_70>!=xl|mETC? z8}l3a%J_{eQp(@|UTjA}Uy$uokWyni+U(=mj(q=r0^4yrLvl|V#zrm-cIz%du^`Cd z_?|IC?ly8}xDSPB{5e$ub4i-~NWXF=oGM-$GzpJD5`htV)7Ysw9}*!Y=|CE;xj+iu znFK(7oalW4$~@R9~!Q_5&MFa$NWuto5hnEFTr!wV?E z=FUS>7w9Vq2B+{uN)^{avf3?C;RP0(>ny8W0>QZ;@er9pfrOB8`$bm|DXWtcQ@&0m z+EMC*KGazug(*p|bf6QNiUX193eNEa+mkAg5GXXCD_3u9uCr1z2?m*|hi#gft=VE= zSQSUrtjZB}g~$RLM4v0vC3$0m(HB4}Z(HEZly~}4Fl+)&WhlO!J#RN|sXJ#)lkKfl zHU%o9URhT?hA!43IE?ta2D8y{dW15}Syw^v@a6HlU`QB&a&xrgJw%zWCv&#o`U^q# z)TSxN73e7Abv;O}Kx<78;`CnWl=x4ynro(2G%4xfRh9!&%*o~&@R6yn^0|PTN3ENU z!uO@7CtZZMEukh6O%0-~;k8McMqW)}g+Ue7AluAU?@R8EFOx5s>DBHnu&-OHc`xvt za6oCd8Qk8f3xXS^l?1bfNUKf@y2p9N-nC?N)=XYj9U*aQfjU3W5rZ9-?#MpprP~)H z`yJM-$Pf4CYwgt_qi;UBD`am}W|}9>w%}}Bn|n=3NWZUS%eO%2hY##;KP#)w6Ih)9K`I*_R4iQ3FAoT!bII9#b>z)4JE0A^+YKP6(Xl=g3lt2Mpsn zVGW5IOY^?+SmG>u zB^uhTc>IYi%?RMvVRo|MtE;;&w@^i9dz(d3{{7|V?gp@kFYLwB&HvbV0(^+am5?Nr zqqin+$447K@vH@}AiKQ>SB=yZ=P(@*A*p$WBGO2_rsjq79rO*E2}7{FoU=`gx65Hp zGPeX`KBnQ~y88=AIB6!>;M-8cp0Bz%(w+pJ%a(w|&YG~HAj|l9e4{>jV{&3lF`otw zvFvoWz2}&qt>kNvbI{q6PAsS<#Lm^uHJEI$%)il^sN z&kYS3QYmBwE;;c<;GI+4rL18YyG{1(&f5CFt^JYZp=3|*Wt-Yxu5Z71y0y8!4}?db z9`~i5Rph@!#W46~C*heIh$&P4j4RKLF_(9>VZnG572m`p?EdY~z3+Cuv0eD+aPP}I z9X=o%2GfcUE`Z5L+%|Nd{EW>kw?WMA{6fMu1n^(V8hr795*ZAXPcLN;!(oUH|DPXr ze>3~dOtyygKrZIgpd}EzC%o`~B4X(OM9B5@;K3`2YqQW_|51pZ_3-B5t2+|L;=i2f z5P?31SOi^|vwtgxH6X}~u;Hn)Y1wy)WXC?%( z{2-wxQiEb%XkiAs=IjDVZPq`?&FB{MJxFi25RwXzt#n`I#U2*z6UT_rl!`^@OQsK+ z2=XiG%i%JjF)0C2Wem5U$gAurCZgR)?+q1iJX;^)fbH?)D{9`okLTJo=bA|=Aum&g z$tH!YB0;8aq^a{h*-iYx+mkaeui<&AG}pkITJcfQ4N7`Q1&zu!it{MPCg!M4sm`hD zN*+bh_cRV*rIz=$@zd}M^qPhE3^m|3a;(pAVC9DNr7d--l%L^(oISNHM5)hvV7`h@ z)4nSOc$*sY@hcAsOLTkRHVw@T81S=_7X7vjrYa@0?JjOB=t|aC?M?spU%wt-w7h@! z?#EMQwYHncAKsnY4^d!;KMuzyl-+nGn%Nwc5_=`(L*Y{uKySv=*~MDYoo(dU47?@6 zv7EP0B>4Br2L(L2VvnFzTI1~PRJ3r}5qX2uG1q3eCk`nY=AcU%JAS)DB?uew6uBR4 zqZa{I-ik3KW(cL3PmOl~{{b54N(V~)Ac)}|3pY~+li}v@%JiR%W=GSpnl3cJR1lud z-U0!)`*PAPTxGNJ7g{u~9e~ZH6t)rsi`fKl7+DC(8ix~Um8B+xG&K`#4J3G&I;NdcHdu|Zm4 zj)g6_C4n|y=w27Db^n2G7N3yr^!q|TSfR0b`y~TaIYbxB)$hv2;_Rb;7|koKaqDyg z?_Ct}xZAnS`Jyu@7<9T6bkgmhZto=$7+!~qw!P>|z}I|MX3 zLk|4KG$gs{OuhyoFQe8Z!VTX>KnCh+?eR;g7&^wyOwivh-iS|MrXo!G0I^VI{6Cc2KoxC<>+02Tu#QX6LP^STue~nV3aS0dT+XHJ3hKp z!O*%gxDe^$1Z_~pt95OH!Awi%3gINH@+!;7@6);P0?hf#gIn*1??)gbY+MpT-UAm_ zsb=M->&VO~cr7vU=@4)ho8TiBQnkj6Wn4o_pAm!w)`QeFXCGvU##LvC{s_VY<?~)$Ssh|m)`ftM`AK%Q78=T zY)B0(Q(G3O(&XPYnI%^5c4TjfGNNE%R|1`=atA6x23HgbNz3nDZ9(H6Ax3+sRmR8e zbY5S<)d*+G#dvt)kylD!k$TSUFv@iF7j?4Z1L0k#D2aS}dD;)@^(#2B@B0KTB%+IT zqLqNGHBVP#G+S2H(%4{@oC zvRszHQR{}T_arHVIvy#(+@1bmNK7X7)l!GP#L7s(zlTQkL4XAoc(7XV@D48T8}V3$ zjz%9`D!ZcE5L)De#>!@Nd#wi#Z8*RzD<9Te)HR!o%5^i=9qcN}5fyD3T?+2)MHD(H zoD|deKRy2o87^180p&ZYd#Y!_7a14{21?KdS9_Iw3M4fgZs;B4Gb_(if$=(O=0>n+u|gAGX*H*edn=jI9P!Lo!@G z2mb^GF5^1ytxU`wx$9VwC&^+C$HA=B|xbOxKwsR>ePccY{C zPgy(#R@2UOj6BZs3F?){@O(-Skev{AFtb=7Fsc^RGRkQ|9yb)Yn#AWDOIaM2<8={vRDKK!=fJ|1Ts!svZ#m&IhSQ-pYfs2W$QRZ`l9q z{r4R)yZV?9`2Gc%*62pl6#@UQ{ft)p=2ooIXowo&cIGBupclm@W5lr zWEZade3qiji-F73^RSay#Cq{Tv>PJgkVd&5AQ?WR(Aso=sgr5{T05adKZi02HEx4Q9wwL8v;kmGQI2 zge1PFIZ4u7U<@I~&@u7H0EA9tWo1Qt-Eg5cdg|xHL%TkmOyS=42|7-G9W8PNw5t?+ zyGg+$Jtm=>Q{=7$D!6~&Oh}v5{fsha=BOf-v(`1DAWmAq#EPvat*-HLP=fe9RYVjI zM5nN0p*zM9L_8f&l6AdF!V$NzD$x5sbz&b36 z9-2C>N>B2SNjZfJP+AtZc*oAWcSJLy)D^ddp^5?g46#<R<2TKdNaXD1gGKZT1a^SfuM4*AK>A<+$Z{z9O%dP#v(2&jTbpZpJo?xK zlB21!F0Z+9?+!ba zme8z|@k%8trqUz)Sg#;zB*{tE1-OM4`jB1-T%>!4hh*==!<-W!Ikn2v<7*Y%!mQl!rz4d;;erw{ zqpVFALKaJzKWu<-VDh)@@Qz=26|$SWa}bwcc5phHzJ>1K&#{_~PKb&~OQESOpPx)A z38&%VHL5XcoryS52GR)hcpF(?`{777rh~*4+GUcrqP_6y8f%*f071G9Q+bUkIA%=j zMyBV2xOpD3oGUZ(a1+)i+4zQ@cjLDZ&2$dzsInpp- zU*e$#)e_1syR9SLdYAZFM11-*!!9A2jV~rsD-YEG&vu&55#*gf*xFIDSZR3aWA&vQ zxH&YgOy;SI+*ktZ2%xnEL3}!K5feoSy0BypK*oDJp;#jAg{UoPq29WBh^0m()&4C- zs@eWd7|>TKJO~hA2Y2+#=rz59LOh@z5|FBiVhJ zA-(9#p%Gn_oYq{m*j*=*GPcq|B46j-#l`t-_1-;bn%rkN$}(Meceg`A%?<3%wn`>ba)c)u_+|hmZaP8Wj))G}1~3BP zX;dw5k_N0vskS-ChiimGnlD3O)I0 z=7=%$ML{Eaf~M>0oU;}cJY&0;$-9Pc;cPBl_~Y8TvMCJbDy#S%9?x1*pg;v;f+d`S zDJL=$+6(FPIhP}7LcUp1I(sLYV671nwTIeIsCBoY%(18uof6&y%oCnc6Yt#+g66G+ z$&>kkIfGHp8@9si@DXmq#&>aw)mHFqF&5JK@D7zIC7tfw5clCQjS_i7P=CbLXcd*j z9#(JiTzIe|$+)7J>JBbR0b9a$*8EByHd?k7o7K?_XL=;Xek6kvgc1M}@l7C5qPH(* zHwU2{A2Z_i3moCsLqm$fY{;9;q69xbp>Jpj5++yxPRKCyHRkf4xEbU|d<~J(Hq);}(9Bx$KMj*GG6IAAq@6?t_WWC&c7Wo+Sra=co z69L)q^VnG{8e|p93+_{Fh|=fI8RY{R?!j*KX+9SnkQtYitq$vChGW90ZxP(lo5u{2 ziP9#_3GOm$g2zGeRGlUrFKS`*utYyuy^SgY=Fos$RB_?ks6o3SU?|*tt{?GoP*_uq ziVXI;A%LL-mM~@5`}H9RJTzjoO3T<1LJPjDxj3>C8N7I!t;fwKS9TSUOtdy?8$0ta zziI!I2A+PEJuOeyqrva14g73A8vJv$!QR^Qjlm222Q41JkWYr*+F09tAqM_~ z93~%KLPz`#z=bJ2-rRq>xv}+Ru>YqWb{hH&FL2Ax-u4T;`X4@Ac=cp+j~QlkDm=i1 zQu`Z+BHxThC&!slPVtTUgT*tRNM*?mTY{1=r-rdP$H3dwOUBOeQGK4&E(!J&Q^+#fda}_`Ba#8W5W*Y63--^P;RrG2JN&a z;q2Y~M!+mP;z4H29RFpPl_7VduRIe_^V?3ZJ}H|u5-lE;c+*#H98d?&C=Td{g6+31B`We?*MJmwgbHmKV$KXwZhTPb{l+Di0wVMb{qk;@%=A3S|EY2eR zbzh0N7jKKw$eeC{^Ezjk2eEV;3+YNssw4pzYOD}~MaO@WtrJ!s8>8GSzwP|*7K#uu z$2REnqyg(yv)j2FqJ#(wClC=BTZ;+=^8siQBq6KP)A4x5;Di#9uKCB!-I*V9YBB!@ zl{tqNRY3IgVHJ+we%p%i`F$dvL8o_|?eN#A_=6%Ri=#D+Qrqj{Wp!HP*@hge%pkb+ z>x|vXGRLzu6du7*FBZ3|Fv6m6L)0#yj8JfRa1KA2*(y?t&#i<~tRP0BmEGH5LWwCX zY^S!7`DIhXsb0uca;I#sr-z$DG}f4kfGn+>dT4H%*z{W3%41i;noh`mr>7@|yx8FPF(jWsi3y zOC|~us~YJ^NtCF3W*Q}{0w>JZ$J210RcG8;SPS09N*g^WuV z_>X``<@7%TCP^6z+6bfZ1qJyvp;3tmCJ4hOtW~6Dc9#0_EDXwY2-ym;6d|)bGpxhw zq%<2eFmr>5HgReyZKjxIdhQH~iHKozVU*}?MCK#hNMp)nLtEPl;beBmWQt0p^8z8@ zAr=Ekb}P;(Z6DuD?LYUYNH)wBEBq;2Z(Q$Y zT0Ug;<8{?Ay`vfKoEHP#x9t!C>9EQ4!oNceath8b#MXrKNjywA7z#0;w?%4l#4>=| zTw6YRQpeI~#_e&~c{2(35~dhrQ|rY~$>h8r64`js=9D?xGA;>+z*`HM9h=5K$ z349-6%n~*cE+mn_WprfI?YxJ(j*Cu0CRHQY3yuKvt=BVfrS8Nv>@t?tgHrHdol=EPiwe%9)Ro*VU|sb4(@|tUKzDExF`uG8n<7f?6?`A+7{- zXmCtdG`*B#+^zd7Z4lXya0j?QJB^?b-Xklr3)G&K9I!3;UgMiD|hDKJO41;V~f>$ef z+vOeMtRcF#eJbLtw_nHx#ZcjA7DOm|wY_g{CfPOpV~ACZONf-(gk-*Kd>%M7$CL?3 zXOag~VM@FaL6zKvY>C{I9cFBabir*5QK{<}sJEfkM(gaOON5}2QpCUQ>}oOMtu1_g z4Hb0bG%(+)y_1?B++33l3V4z`Kqa?D=oC@oQ#K}Twt(7$V>psKl?LiDSHT?P$^_*S z9)Kk{b;>}=nMttzcy!{160K?$L>;v0Zde=<$rGhiR!P^P&}Q5{i_#U9E6=5{$)uxc z5K#QMi^xodrutY;N7PC>UsAPtjihZ!89}6_B+^=U4Btt61`pC&2lE+9eha5QnhZZ6 z2p~sN`(`SW6W}Jx!zl%-kAk&it-?K50x*{1&)@sT9=;u3O6g^Ru8u(TN?@7wD~pt} zr4b-`&tUE%e(W=7C@H-K0;*(MUlZ-U5sn2d3B!I!OQnMUYhdcrW>|#=Eio=!&6rpqmv&4&C_%79*4S z8xp0wvAZ)qz06YzF-2zXg$@yDywVJu6|6Y$f1)Y|hnjE#HkeGrwLF*#EIK90cyfQC zw9K*}D^p!jLRKg160+3X?RaoY!~XEM*H>443hV@Bvs|G9SL>f%B1{}R$THBkL4PSn z@sU$Ij)zXHaw+VsP&vc~GsP`vP~bBJvr`gC4SXvK_LJ#kex4EdauRllRGWitLZoEX zTWSRFy~ydm_{7#_GVIk>j$Gq=6cns^B7#CNeVB2|wr_03^4VZAKIpRkim(&oa=P+Y zI;iDbDfKL|vJBA(Mo0_Wj}{Obco}J86Gax5b)ZUI=s+2hw%RNuG}@vJw)i6ytu56O zd_uID70BL4&4^AJzRv-m4=H*$ZlRCXU(IB>u1lNU1f9LNBQ#eB?q4S_}hF5$GzhHCRbruC5%`}L!U zFr_;iR=RvhDqzOBtk8+bTJYI*$8VKTDT15qwFPhy31bM;m(ScQ5BZD>k!NzB5AZ5t zsS7q__B08~XIa`>2M)6*oS?j98hY+*156dN$JD!Cw#9fat5rQngIy}6XxPufQf%xne7sHk` zK!iG*MZ=)(JaaKOAj85pw9*TR!2bG6qSVUhP{0L@6UI>f8M>b zruiKCoY27Lmp2HLLj(IcZxCjQ2KF-=P*+VDJ!uGc?j=to^BPZ!HOR_l-&~R#v5Vqv zzLcBGd9Y$o>`Z~1C^Xst)XN|+FI25jWWZm2E3?C;BxpgGVsL1JyQM93Rz-tyhPY8B ziIODywLm!0o75as`0GB|u);QyJi?v%#m@0Jz@)(}-gno1Iet5yyhkxj@=taQq7q}C zV~`%!Shs}*d?MEhv7K3Em}KF)tXJ}-xiZg@M~lmo`=^in3O3?X6BhQ7vP0^GCW9*9 z%K{TUvoZp0#pahmE+`*1u~KUo`Jl3fAb*94F8e&a4bQ$;pG0x_4KX?0v=l1KamZSz z$;&AP9fH4*q|rhy$B8&C83NW;R{?kK3v4;gJ$2(9P2)Gb5l5s>W%iW~Z$k&iBmhK| z8A3AI=eEz(7HE5$Ef~L-Z;10SYMzR^>OD ziHnyK{z6#gXY7*O1uyBK`LVy13zDkegClXJ)hkN*yzL{GIB5VTW6anYq*HipK#Ie( zDt`XS(+RGVAVNuP7-3iGR?7>Ln6`5wlZ} zngdDmNK>JAdn7~Jrd2^8-!EPF_qaA@GKV4iiEH=8H6XuanSMG$*x1T+&L6+D7u+lh z{pF9?j#TD^6CqjgD<}x!Te9(lgzVTyLDGhcnMWq7P*jqj874ul7@sF<%~;p)Vi(>d za)12oZDZIN9*JHm5`QMeJ@5z@zTHgyHwn85Xe7=HMFS#~eGAnSKOLU;2UZSWx88t% zr}JwRnt{p*$$E3ElZ~N!OhLt;gc@R>a>5obRj|Vn4esSVQ?feb)bZ2AZLqJ^N^*#i zX?ZxXe251jj82M4$^=3+$7E@Mi$Dgv&N-USvAT_gM( zf@0YdsFcG2jxVx@uoEOU*h6{sY=mvM;%XXa>*6C6}IEQkvW*V!yjq1`nftaqqb@_!Qg}U zQSGSKrppmaStkfKI+BHl&%$cL58@4L02K4FLQDlXxAmN8XFS|Fmab*(*e#uM3*71e z1B{|@_YM?75ddN8wqkvA?F&wA(PGJ5X7FEDa0nLLd^iO@>(o+Ax9thQ4nGvgWLTg* z84lRD6(n$`?HWy-Tn`d}lOcHSbbycveuS~Z4{_^Tw_C1&0izX0vig26q?3=>_KdUy zS;fpK9cvOjlLE}N-Uy(GO>*=bsOdfrg6qEDC?pB*CO|@$i+XT21y(YYl!UTAl=D=@pM!d0`B(%I4_|eP|QO zR)IF-id;%oEIxHB1Dcx1}Ij*s6Gdp5Y!%66M5 zE*GFi$5pd|ToD_uhiAvKZV&pb5{}&rkLg42j}+w=Q8fdv(Q{+*pq2@0E5ftvs|6a{ zu2F$1ul8;9h`EMmVEd+V1IpNxYr~PoB&⋙m|fEnL@I&33z8LBY0HYgX=;LsASZA~%nRVc0Mcb4{9Ng)%jBW%M16r)kTr-{1c_T(? zPKJFJEY%Y)64K!@u^bgg{0p;+F{B1I0TE?T8rdNsS;Qef&oqXu;a9RwRiHo{l{tm* z@Q)HU6}8KU$0CE`CUO(J%GO#@Ye7Do95o)YNYnz^uzyo`N|WGe!9>uJk>FEd^I2`6 zWogN%k%S2Eo}5pc(y!Z~-1(}h$499;vxfNB>EyktV9zEeqoa5(bW>*OaiGclx3UE4 z9~-xkWft$nmFFQ`1(|;a?l$EHN4YmUdO5=Fn>fk6d6WHlUYam1$vK>yzQZ*fT&>?+ zk&Bg774x1>DYrjbxL#*0R>MMM|B_F!~xrPd-aIzl#cz zoq7@zF(JXf7Gxo=F{LEO6T)9e4f|;#*BR2aswTEP4p)Wg$0#L9pEY$vWnALzuc;wP zADCAH1l6p4Cxin(rwH^6uVfe2 zaD9_+xxS6RFq_*Ub1sfuIRPuOrP-SGel$n-+-LE^po!{X$z zrpyyrX_Bcm%ig<~Y9EQau2dB#Q_yfK0w6(zp;xH_>qtI*lsH4Gsv(=Nxzb%OE3%QM z&{bm}U*{fn0lJE`k1kP;8D-9AKhUd2s^ku78$OCsJqf`ylSfDMDP!q&j*n+ViRPOZ z@Itgb_pcf#n}%|jn^9(yxQiVlg>2!bHu%b+lw*CwXGYO1or#r$gK= zg}N~WcVjxX$n;n>r3;)5sdcQd;EHhA`(GL1f+KGdLoK1BTb^(TkA3mw^51gF$k;-CF|kBs2cP4Z9!8dHck(OZs;Q{kk&D|mxpOOl z!z*uT0f$N~)ds*I*GRt)faLBuo2!1+dw{SSt*9VClkN6+{IIMNAOg;3Bd?nI53r;) z0;1(liBnMA&if2fwiRyR;?pCXhl2sgim{`o!*@f3>BtM_xXBR^{q@j!ZEvw3z2ZYi zBx2;MyKNe<=Gb!|kAyu1`CQ#lNnaEwoRQ**@@BBFDs-&FfMP~_RbjR@)91G}UYY&G<$>JG(>V`VnDcMJ2+ z-%CA0M9N`D$EeoxF8xrbI22(@d(8?lB`+c_|oJC632)6w&7wFRqpFwV!6gi zFt2#bDDxm43Aywh#7Rd;C8~FVIl6hu$&wC~CQ~H9$WE65?et?2o8e6GN^DJLXOMSx z9Y|cZE^V;{SG}LmQL>%_AZb%Kg^N`!28ngB3@9QXjA5vbA>Ed|C$P?F)rRx)1VwmX zTpajf5`Pw)Fp<1~0ftZ71zv0d8gCPM3aaHZi-?|a(_|eeF6eqyAd0MQoKIDle*Bec z2J`5%5ITGghE2UmGd|X3DzIg;--}DW$QHl~&mPnBV0=aA&EB(5G@sv}F`FB1beMAKS84j74y2jy@vHmxRU-4t zb<6l)?u@_XMV2`KcMq|NH=zlJ^f5vZ@s*{O6s{8wl>INo2 zn>KPU2WqK)DHr5?6ZYaiN>@QpVLwzt2;=076x{UQJ4wOkZmik z;zxw3;F7zh;A(QP z+yqvhML3@n?5uD8WABrZ780}JNqL4z`KbAl@-zx z_kui$W7sDRe6K(~e#GsLzh#?_j17gf?WYG0D-wXr?RA3ahz>#S?qo+C?CO9c-U|

mBMaBrWnRz-wK}Cqy;ue z(~S&`mIlXoyj49mrn4Y61~*|u-Kow|7a3$Ts^F_(6V@LheGS2L%JSF|OvtHuG(n&> zu5=zgnNGn^yO!%GH#~!N&QLs#5$BnPxV828$u}>5?-UNH8ZrcJ3ydv?$9^`ix+4uU z2?5|vN8nI41g}Nfsc=(v$v7D1r7jGBNivYrPU1L=sCHTBB@eXqK6bxETxhzoXu>4$ z`JU%LZr4O6kVrdm84xOo;uO1rkkVtd%)3U%lPaA=JDSQid_(zD|BtFI6n9j%heFTOC8YwYy$p8wk z;-eY#|MA7~MmOBid&%OQlhZkGIg-l-c)Q~OPM*%K&7U75p&Sa)fY@Hl6Z-zTt^v`Q z6-xNSlf7?t_Mc$-s$%3Klp7Z#UDcX)2@xY+of?ce1oBAiO5IEM_kxGS6822w? zNh#h@eNKXYk@M{Lv|Ju&CI$K1ou_b8oDkWOqACaa#D8I zdmyJ;t}p~(C8oI^68Z2zoMs<=i{1*0_6dlt!L10|bNoIfoG@e4p*Gfn6YfPliTAuc2e(WaJMBUm#37?u(mxL;Lm*SnQj+C`%+DF;z z&R0MHT_>QH3{$|L?tcR18CH?-l6)&Ln>MpvpzRrcL&@p0-E`-WL_`(OUgh1zrf?Gc z@JfqRxX+A^u^$+Y7MG(ZnCBB*psdRY9T++UstZ3fZ~+2-*U?{`M}`(4SC`43oX3 zLpZI2XY-P}%0CVf00S8$_YnFlDAziti1gtw@R%7LW10|#f_eJm9tOov@84Xa__A{n z>ox+)mnIYP4v2a8#CsB+WSdvA^>5}Uk)yBOd@;SG)IWFmRH(hq!JJ6D7|vBCrV|#YuiICk%}&_rM6Z*3N8p@R2s}Q1|sp^(bs7hvKrR z?qF(oExr`dsZ<5>FQ8X=Luk6xxsvmwk^nFpdz6X`)3ZW9$((DXlOv?SG-9LdVPOCA zGIIJRs|OE3S=xu=fu)KBq!r!~&yM~NGmJ}0k6|vNRx6ZM1xI{_^bF#zb+PG z;@)hMhR#_30s>XXH9N$=v$RX?G{oRwXOzb^uY&>olH8nu8o$x9Wi|!phT?uwJI(nL zYc~k&a&YR@7qG-)e&~g`p`8ei1^+6Y6D7MK8YkS;UQ4PLiG&>*&OM{9fM@W0tdHks z_=#tvr}yK}2;v(%kVI@54H!R*Ck_lR60~!K+eUWQ0%_)GZ2i$;o`8`(m3%y-L22)M z(|99Ja4WU0%T1jaSbyREDvv%bn~a|>JZ0554ISRs9b{DDSBCl?o!K@(-S0sBqMshS#2d)id<6v?)Nah~MJY=}=8|NJkip`)-<_R2zraI+WmM+!R zeS24_HD%Sl|G;|+W`49p0`Cp`u=Hu- zsnqozI!J5`>OQ*D(M~6mw<563vK7eS0>UY-q_CBD&ef8l(TJ07z4qZpj39`-o-%?c zJbl#dL9YkMhk>h@5e=L88SuhUgx~m!g?5T<@e)K1nixQCdt57k|~4@(6V_= zZUP|xgf->FeGS4O11H5b$RI`y_%>Z>n+1=S?Xg}S@5gS6rch8W`s1S^E>Q8q{7?l) z$twbhgDm0CLZ=ImhveI2YtCH5wy7oGkRFB6lt`$iT)8R-<;?%y80bP7kv6iWzyxg&xaU@?)@GUeI_v8V|@<*$3^qz+u5zosBN!s;1my zy43tre||Z!%&~8$zGe|(E#_}=<+8CRQxHW$;%6H+d=IEK404-)G+OsSa2v4Ed3YKQ zOI=m>V^}uw%5MKGM7TH?1=4r9<+2K?|dI+jczDaWF7cW-g8WBxXiJ)<};~z~U?Nt;mbAPbD=v z_T^04!%)Ne8jwhO@f*jK>D0HvCyZT8zO9TU-G=1EzSi&sx&E)woTj2_@`f<-ohQOvHwd9|@4ZsBVs6)FXW z5lexbt|W)DcVTmD9yn7SNyl`BK=3T>w-9I{rx%aNb0>3}nNLa|w!4e{V7Y2wZ zB(50{JA6G@V_@>j6g!=^Qw1Tk%t&$0zIceJS5&YOEQM&gB3YqzxzS-0uu{DKd)hDT zn27;c=lI3`G#L9(M3n`NJX4M)Oiu#H%1tM`f~~qRT*btZhO6)c!3lte_>k#M-`9L% z@&y>G@0U!b@u`^dA`?in$1K9{fiGxlrdgc5(W3U%!jjVg^FSI|KskmAqEyoncA7V% zO34Muk%d4Sx$V-OK20LAqAh%*SWnuCFEZ;z_mFJQj@%o&?8lLGlMEfG*Mk10K8vS4p9iL4Cei$Q4X{%%%*fXaORUOH|r04iJ$rerN8AO~;p-I9C zNrz!Y3E*yqy3Fr7J(SuXXaPK=@PDo7Nc&4+qY_=6dqIrMD%2Pz?ljl z(cbW3-$tAJwONbANI+1lJs&bY~sp+Ko?!C`{~r z!F2u|CO-H_G)lK|oNO(dQ6PiJj`QqeP{e3@WE&gL`;y}GXP^=^VBpg)Fd0UNA{ z0h9&+L)^xAMzC`Cb#4OWG}3%TB_q|~DlF6}LAq+{1Sv{JA83OGLEyyyB0ma4SCaC5 zUcoC&M5qKpkeeVFJszV8>6{9y@!7%sjPXH`p8O+7r9g3R6we=4>Z;zu$8!51lI#2( z6e2gm`FfO&e_{@YJxeGKSmbj{kO}Nc=vZTb7jvX+@~VL3J^HbrN{J+F1!HR#{<=Ig zwJdq`jpX@wK(d_0AzVbCYDkjs8nNJv1GNk-Ss>ubjOQS88ufZ)EEbWVs3m8VN!WqU z8@K4>UXTpEwakZ9*t6%(!~&*Mfv|V&3WejV2)ua6#514M4m48XgE{cLQxbcCi$iOD zlsyjM@;WH`jf_T45)O}Svtw`(2R3KBVn~EbF=`n>V_9dYR23WQJmkWcD9UA7+B}8V(YR=Ir5CKg42&h!vPSWu8@oo%P`}zQh&sAY!NwcsTrk*vAf{7oac5i{a4_Idc%&)^sX?p3?Du zV?%M*eCN%@#rbUO?%fkylQe(5iGJ_m|1x;|0qQMErh>*Vpar~T-#0KZ4Zpdr`(j>R zj7|@>$D?nDr{}|Ik@lJ7bH*Rxqy<71ySuB7ym+nN;w=u*4Q|5c*QF#*TMV@bA;jJa z4pgu@dyw>yq-~f}LELu!bD+)S6X6`{S)tp4bEsT=+kM(cJu@7;)?<_3V{1EIY%Ne6 zrBn?-w{gX`|BTug)J0(eruP9cLJN<0=8I~|3=SR)FEA2=g4-{eO?yC;J}_wngZL7g z1+kd^3Xi`A@qDu;ci0007Z39yz} zUwU0AcX3*Pj)r4K9PAn}yg$4i%^nFoesjr);`kzXmAGlax{f-T@>PeZ7>V|aNEUyg zMxXsA?ywACC5U`60KqvBpo9vKIc?>5rBg`I2W5Qat|;NUH|i^o)^`ms0bgXQDhF7& zU)EIn0KoA>>;^3Vg3Spl&*97@h6Gixn1BI@#bz;OXaD((oAT{)bON5G~q zaTqv>*G}dwU*ndrn;lOVMDb))GlSu@=@ZoPVEo`7*kr#|nMSHyhK+#X29m|G07Q#n zYPetw)OM<8d$_M&JbSot3|VzWv+<-OmC6}uVHsB5+F;BX2-4i#^-DEXV$8M<#$d@0 zc~dAuDMbW$A7fOkmDevI7yKcl5Dy#s??A+I%E;42%!ohthGR)!fLcJp==6_{Cewig zfK15nr^o{skIv?2J+>b~-MU1MzyjlLd8WXTybBaF#F^kH?|7qC71U{rd{<12B|2l6|?iomE4S&4Q>^VtOg zMk6lql^m{)E_iPm5E$Vyi>8TW=g|>dc6Tq3&;hv2-XN6?i^QyTj>q#UWUWZjGr-B| zdh@1WjHov?x=?}%n`yMFRj&g(`WEli7prKFpe5`YvqFVqk;k>2^~95;jZ`sbrpUvA#pM5N2>Ij&*8z+9kn5MhNv zKt3R|qN`qKay0FqjpnnvHoKmW-)+#(SMPDUj;(yq`VAB!EDBr7d&Fj)+5B7-E?`;E z$U0w=)b4No8Z{2#!9IEeWYeB=El>>hIz31LDJH z8(p+`IR-sE1>}ajYUAJqgzqD0m=NqLR*6+az#0G14XIvNv9@M(iUW!0OV?@-PF8-`8^*ZKK?7d2(yV-v|2 zpg@D4D9(vqD`YTxORACneb?i(6qJxZO5G%4Sy7N95VE*Uk~fDl*-UR}ga{Mqp_0oE z)I>T6qbPMr(Y>^)My-&8=_&eYc@0&Si}1V#AbLab71ZjjEsXQ<6tyDE6mzAJ4Plxy z?SN$FE@(dH-Su3qEuo8B-YJS*1izSc^bkoEZ3++Y5m4di_!2OMn-GA7X@i~7c9LTz zJF^Li0`3eZOHqbDJzmGDJwrSv@U)kJsVs?VTmt<6G)e-aVr>7o^MB9ZoOgOXbpL;W zD>D#t2LJoVT{j3K?%PS)|FP;C^-mHAG=d%@IdYPhd>D(pIM!b-I*udK=T7&~~}KSCIBHppI38>_c`CiY>H90=OLAMkk# zZC{_jSDW#ci_5A7qod)8sPo>)ZfDf(JqqSvj#IQfS)Rr3k^#sq}lkQ8Yctl zHEwQwWKA(f`85egGL50T?G^Ke@|w?7$V^&C_^?my0Vd~t^RXI>m3#5}wJRTAll1I}w=V_{liP9K2MeHG*z8( zcp`GA`jIXQc$BV@&4IYKw;3L&SzR%CvsP_ad1Q^yRWp_8+^+wSR3`f+Jqou+0sejg z3vC=ivNmU*Yv&rZzt20@ttzZK#14s9f z5o7hNw_;<0I!MRwP_dRSyTo^a$xV*nw$7h=a*2l>Nat!h`KDMOhmx49?=b}T&Eg(H z#|wyCy3Ee9*nx-b*>snE@5@@y4l7pehWEh(PUdja4Y4QzUswzC7N*ktSkOrjX6INW zou7(Egtv~Rr72?0t^8KNa0{Hj&o*+`F8!Tt!&gBm&yy-#CkS{aqb$2WF`2trmlUk> zpUgRh{K|V5(Pt&-uT0kc(@}r+?=TUlt>`udR*#F#7ErYAX23z=Z;_zAU$tKBge~V< zuTVr<2bXLX-_L}H3idFhJ}58;-}R?&Wt46AzJ_=4qB4QE_m@#h!G){*LCJ3huiKG? zA>JCJiOeM+BYJ!!=hwV12hK9khX2x zWl~Dr36BWV6T7igN(c-0mRl-vgv=euAqf*YfU`(3Jv@qdE`vwTl#^E&+GKF57A@b6 zKxbk6;h-`DZb_l_m_kU6W>*|Nvlo-6Qv`LQ?0T?OFmt*0AU=tVm|uob+djtE<)1dQ zhCRn|WXAp}*l(F%-H_*0>!y(Ka$HzZKYF zLsB5Pv3Dt8D_au`0JazI>KD^yez%~?|Fy85YnBKvU@1Gx&`!|LPgR0ZQK1+aHpN~! zavhw0HyOae2luT}*z*Z0OJzr8>&-^r<1Au%GshrMO<450d4Vg#*qdtK`mWYtWR=PX zrEtd#$A*zkH6~M!4@ZL!LNAkZB9ma+1lW%oX#jj8G&r_uHt4? zBo}sCh^Cf46>%CpW5yZb;*9$-&UsEJ{lTo*M@}%M=8zWmTOMiZ(S)7Ybm=JGSch+M z+AlxDiPp5wL<2N2{Y=`u>gta4;9JFM5hCMx;3O02{-6bgmbVi(wdwe#?krdk&Ro^W zt@{k;J$xp}Gz&6Jc7ui)GWT58O-3h%2Q^?@C;t7_C9!4AHhe{?vTW*=yzdSph zj4u!5fOw3fp@XML-ycrxpW=XO;dK@v-1J}@HK&f40Ks4!m(Jm}_KE0~ql*Kmn`fih z(En{a7=xarG$n3-E(Hn%2`h*bo0{M)7XPu`w zw~ddX=9Z^EF4-%mFD4zaaXikg9mT+x@XmGt{6I4})+h6ebA*PdgO7TsiB6A+3?n+{ z4g=6H>-`a3q@#rN%DEe3Ut#LaNvR&EBS5^HFtl4AUxfe<;X3Q@u#=PN<57~Y2;q4=IuV^{qpR1gjl!3)LiJ#apr3@55{)j947!dmmxd@L~{_1;{_6}3#T+uN0?6;-`kQAHiF6VTY zpa?D9rG+DeEwUM_7i~4nS>cmnaztQ49L@~N5kOosf>^!`>?%ICtrmkUv6pNoh!4d+ zuxEs{p`s+}7zWwZS=Uoq+hH2KTBvLtu^iBS#61#*70#=bn@rFJ0jEZ9nDs#}4&ero z!tk6#Q0Khc-U9?7jW?R8MHI=lTiGCW@g6S2;yN8p_*6kA&w*@b{GWDuT!p$`A>Xn(^Z-~G|!iICzfK1a+XcW0x! z`FQ_uAN+9GWe*4-1RuF4ouNK92gL+UhDwSSnjT(E4n@D@GB_K;ljs6hKl*$)+ZhvO zELcePjv(;hLgl+;TnYxwrq%q%=@Ts@th^0WlL1eWKA}5lrY9l!*dDwjMXd=;e1s`A zVB8?xb%q;4YZ*-+;*ySY&&|?gSTmDpJZhKbybwwUa`DR1pYUQaR%v4FT%v2x!a#o?*^Mh)=mFZKbtMpzXeJw zMJs;9TCzC9g=U=Ot~~`>E4wQ!z#s{)#mH00sdeV|*LSEnbuye1uI|&93+m!;uzzq` z%VSp%oH6h6tsa?zuZ2ELU)`0DdDNRZ_+z_Bu<6%sj=|rGK~<<=!~9RV_i1`%lB^H^ z-GrDcbI>XJ67Gl*sxVK53Eoqsk&_BlSDSbC<=ti7gDI5u1$s=lO6Th>IUq$ zS!XPd($Vy!V>iPh8&%r258H4mV0Di4@VKud^$AL7d=gj;ywri>bt-5HphDhWOpx;8 zACsNyX24+*6OZ>uzBPC8APEY1%&{zVO z*$8*Nyd1wB0}7JV!!s7BmIT;V=hkeq^DtzA@!3os!qhd8dh9)(;D72BeLK7i)zBkc zP@&u|)=3c*+Gc>i!!tozU~Y-G8PJ|irY6nWMSy_R95oWV9hif=f5eMn4wj?^iB)vB zF3OwqjmLGxn~{B~=K~bdx-ywRcd9!8t=I?635ZmWt0yJvmS!sqCa6r3#Am}{6w&)+ zpqa$MT)Ng$o!CKzG~$q`^``%hRiO1;^c1n-%p}w`Fj=sx8RM!q zHvy+-_7QTe+eZi|(|tBM867pzf35pCpkU|bt=UbSL?ZR)7A|WdFNotvrkiJIA-f22 zUsBq<1wVl@8`3CmApv>~n|UZg7=~0B<~HuZiNCeZAFZ0J$3sKKwSt~vkAi^<2}kzz zahOmmKQvlZ!k)oFas&@k9Ef~CLi6JzyL{G#T!n(YZ9B=b+dhs4X|b*5^;N#<-$Ysp z&P6@)Mt|V5-YhcpsF?H~)O}rp5i0J|T#8?lJ*+F%CJ{H7bzqrFLtuusW2&FHgIT^; zjYEMbcLkRYFNYS^6x|ZAvbBo5+W2ZN{IgF6!>BH#0u{fS`y$N!PUoM)E*6ub9ciTK z=hk-iYfHtn#dMV^2oo!O)?H>{g(S-7$Q#96>5@iLURaG>*h1sX2ZON|6C&wGhPi5B zFW;G9 zycxI%ec{f8bJ!5sAD=S^6_94PIvkXxMmju6UC-JqJ`M7IZ`ceZ8!_{#0Z+194A=6# zvw9KfzI8W~Z2^FxtzBGPJ|}71-o)p%ecgf>fOA6l+;>_Jg#o+CcFQeb#%scE_C;WX+KDbXc(Pkk~Wz;-{R&nDSu_(}vXxmUQCNrx8>-R_9A zZd#DZI+0%KIukyk9XDK&e4U^te+7{MtCAU#6nx_PK6&6!%8U^5=~7YvL_8{UwRu;u zHjxn#j}ypNnnh4!m!hfV{Fhs&oNpt!lF>nA-#@ZZHznHiaEv_Pp!;W^DQg~MlC@CnC(0!Pe zcMmRTEs+kNbOYM=ql#cc*Ua>=eE_DMO8E+|fbKd07o1^=7;ayzK#{pXC>*P+E04T1 zZ5Ca8qE5K!hGyYpM0j!>NZGRVt`)!>3WTAw^|6{Q`9=%1u%QG;ZTOIhtYl_myCb>o z?K?w_t&V#7+_wkk7zgwsP{V=R`7%jHlmyERdK7SFH1-gKj&5tlAji-xmK}|2X`L{S|D?2lZY8E2uybP_ zg(ucgXF?@s>^q?oRD_TV597;DQV9Ym76hdAFC^uPrm-5KK$|ue7g&%yCn}PZQzF;z zmhn5sXbZ!$2{;aeIU#_RNi_CC7;JsIDz_WtTQeBpq9Z#sj7Qi6)K9R#E|m!puubh4%TLNn8>|JT zz0*MY6N~FQ6p}54s{7*R`aCcQt10y9?Z#HKA+**($5$Y zB4*u55yc_Vqs6m@K_Phg98n0{JHf2^2 zNHMZ6u0+nVnIvSPVi!w4K0z$@1$>@ayp6s(*hQUehFQ25L)x}9oer0^l&>NEKyC;H zo*opx7yT}d>XEnKqI$MZtx>dETD+W|GT#RO@YmW1kPNKK^5nzt2r6`0`8xJ~AO*}A zNEOaHwYwDsjaNZWfdscJPx^=SHq2@Q=uUeCS2UMA*2p8%7$=D&0(GxPI2uSaQ;F z(CHS&Eh#I}>jy_CcRD%3z{j6KxHUC3mn@*(4b%W#~*TQTf?Cs4{}VS znk?|LPSpt_$G(ktDcw#}`^uk$hQ26WU3eU48zydnSjEEN zY@9$xv0)=}ameJiW)znj>IxRBIihsx^mSIW6q4Uz1RQP8o}yn z#+v=92!z@~av^RX+!ZJg*1d4O2zC;yTJ}P#6V2ZA?{BR01rWoXlr|UHIXf*yaPnpt zd9#cE+-#CoT)BG-EV3Tfi5wCB97v0m$Z28Pu>ng;dquC3v}8avpNC^*_a6 zKusGt48ty@!>2vK0=ZU(IUL5L4w-yB`@ zyBgsgvc@=F`4#iZIb3|7KS#p~AJsJ#FVHluGWHjrp+OmHf?DoK{Xk=i4!oZZ{~OWp z@poqccoK=jeH0SWpaczg=kq4xq5$%LvEyNn@vN9So)}W&CkH1ZR)AS>lKk_!&XYgs z3M?L*>=Qm_`l5r=gri?h(z&;6ANQrd-{pjs% zekT37gVDs_KIF+Gd(YU7AT!ulK;S#k{edd2R(6wT^rAwbhTiE33%`i>P?{mYN@SMz z1MP(Ri@iIVoL|0}KshngNT@QL19+-5QKdLbl@#V@2rH)i)#CL`Xm(^zFc(j6dd>Qc zaMIam{1n^?^oX#dP_t9N^yARm8igeD#r1-82H)uLuk{GVwenJAfj5|R=9b$R0P64%Y+z6#{V{h@z49|n8c04F-n4+v~VPGG#mWrpBp zi3^54=N`~L`Ju+vrA>#Khk3TBP8xJTpQOeU>#DHKg343 zf`IjF9N?z%CkNE+bmo=KWX;p28^=X(?OiE@clKX2gs{T^;P9p3M`4@^C&wd0G_u%Y za+&z8%_HGr?5Ng6_)HBW`GPVas9#aJVvyfCs?CR>QsHTFs`x2cod#H_p5&qsz(XIY zPULL7Et9)it4n1CY_z%IOyZ4dr~ zT8lVr@%bccQBZ}&vo&t7LT));)F4Rc1!XmXUH}`FQ`a9uJVi2K3Q=m6VrbcWb(G*2 zE?HR)fke318Hj9&epSu&h8m&JqoaON#R1uiEZ#S8;lPUGgm9U%lvmX_+ef@%M8x=w zc#N99vTU7=U*5ZSr}N8uxKrhq_kPa5e!)XKC<=^l6*veg+0Q^4T>ojIMXSt@SY!cH z`xpI-IW9()iBb9Z#lQY*vu_e+=2F9nzYLVjWucHUI_vkX0wTeSVHe`C8`z4F6n7ha3ka;8FaYKO+eRy7DPio@l=qQ?zS zoV9qx9(hEj*z6sQXXM2%yct5ntgeqdA&1A#mt}pp)h=;#j_3&jQ%W51L*?0ZtX=g~ z93rsluuW}0T~xtnu295EChGW-u1Z3al114`cm;~}{4HoEvSpPIymW-leGy%Wnt)4E zy|4uPq&FEC+9RCh5RxRgKtuxy&TE{he7k!HPJOuh@Wr=q(ql&23*(6L!@m3PX&|V>N zuk?-T7ZrY9l&@-qf^AfCQgsC;;5R{Bydl-dpOVolB!n$m&8}6Z^2wZyl}nuoA9^+> zkr3-u0AFF5AT^N^xD9Qnnj?BAx5{?x3LR#p80)O&)1zl740Wk(Or{8Vh=uaXREgX4 z8f|spA<`^LS`jb^t=N@~;y52aR(Sg-LTlkG^O&mo>`#Tiwr7YXW48F7geex@dZ>m` z7hvLOP@QbphR4NSEovoNUWO40{=rcw_CA@687VFu$`2d>4IPy>emNNsl5+M6O&f%l z5fG;MiPEPTloD~{T=}VTVeaV-+y7Js=}b!@M6e~klrk-4qx&d$hv1+@5XA*Aa~}-( zgPIK}VF+JM|20B3U`jw&R%xxQCk>7Wrfn*umI01T5Vq4Cq2m_bsPxmSjit)l#o2kU zi#u(983fzr?ELg_@|q_@hjciF7mOswf7c)rpZv#*C(rk{cb-E~qRW6e%8HwXjezky zSAPldy9gK%&mJFo1BMgy1o*KIrQik1H=?BMcs9c+J1y4y&P05OiO`8U+e-6n2QJ>I0kPcMK?+2>C>PxZ*4K^7p@iDRy{u1 zhh77u4o%UcCatOTwnCoX(PsCMM2+}7MT}LrM(8^ISU_xF2jkZ_37b~?2p6ybKv8_z z=cTv*eSajLGl!xlN*QXkb{;sq@(ZIftg9zWXS4*j>yiDchg9~9B=2j4c>>jZ z>7gTOtO7h~%uV&7nBZ;W$S`4tlNP{AjRGA=3b~~N1TU7Fj zI_@@@p+Fe6lhteiQCx@%hu@u~3m6)p+|guCADx^Ivs2*z_+o1-?d)cp61W}%3&3r+ zI=BLP)5ozP@Q?RNNaMfHQmIYCE_YRQ#Gp#T)IiGH(@r19L|}*KAJX7ttR8Ht)j=(4cf~Quijg3iD+K8G&FJ-rVOQT=DzB9TFER>? z`oliSSvV$DKyYhoI6LZ}Bew%fBH<#FWKpYBzA2Z8f{TI(-=9y2O6WvvcpBfE$Feuz z>0w4$8H<~bPA9yo<`Wb#MO7bG@-eyDx?G

L@LXt&cfCAdSl%Cj%bxeWN6&%>skFBoK?BIi-e)+LkpGb&j$`wtqd1{ z)t|J_fBTi1fnH-=CV-plC>X)bS1LEj%GDh~>i-nxvV?8uaDvlSuHlR-XA7FkPsV_R zd+>%Oy$BtOy3wE|LPy*B*4AkDa>jT|3qa#?(<9H?>C*n?_=w8mX9{A5EjoeQ3DI?o=rw>8^9m*_ zD^z{S{7fS@R^sY;<#z{YZ^3BWNb8uD17aP-R_u4$M59F1-2ggug-$3iwnG$^8 zji%EHYOumQ078V}@iRsc*Sp6_gm>w#$iu)x@(_@1+;sHAEB3>XL%;mPkCMR@njV^~ zJ4=4A%gQN6jDSchp*95pYZd}P7#0*;UN=>(woB{Br+q3kE}TdU-yX%vT0-4Mi17F0Xhw;^rx)or0t8?od(7pv{#Q>oWlcP}|r)#|)de+P5J84@)Uw69T z9!CQnz!4mqUHjf&aY&x`p%=t2^nkCO$#jG)bWl{EHTQm$q`6zPq>{EcE=Q=gY9~cY ztSN@#tqzwI5}kHPV1fY#^_4A}sC;b=gka+$^3SdU3vb&F4|=(#lVf6y()Tr!oA~>8f92Hi82<>I&=>ya0FtCn>LIpYN=sHng zh|QlvMZh?l-9P@BQypFWl{!bOHW3%m-so?vEkJ%-HT*0PWYuJcTjvFKqMweA+x4Ia>jvSU+XDzIPOTSqTV}d zbneg>W#;x8*q>~%I<`@`R_!Zz(l@@|{R({O!NL9)xBq&UeX4D~V_mLF56(Lm_u;Lq zm!K6TeZT5rd{#L?Krhu#@it3)e}mcAUq}vKvXP}q*@S6yRS3cr4LH3_DGb{Yw8ECr zsDPPXbqz;iRo&?-YLVJAoK(>0fly-6qFgdE10jG>wd`nk+J{Nj>&?d(Q>dZCftEn} z?RWwUnggyJS&<{$Qq}Cb&yCE0Yd^v(W3iycT)B!NPH3t-BSaQ8zPeB9y0-7lKZ%}jJZ~PCs_8>zLO@e?xRwNKtFbW2zSAT)bC!m=5UE|Vt4uL>@G~?+qL0vD-2OP z8xS#Fuc!Snii(4O$%w-l#(Z@T!>-TsN||V)XV>V0?h;f*!=rn@!D&n9tKW9+^M^06 zTn-VBF=MBaGly%@1|!5q)XprirT{fvkUG||q>Zn)oV)%-+3#%>#S)lUnex~%0>Nv` zSL-0IHhh4U>NwxzkPz61Q&TcXsYWHcVOdh?ciMjwW?k!64t;HScom`GC$%7sCv!xW zp<05(fUEz|yowAt>z|(^0~{C4O>myx@i8(-A;42cr%|vDM#m(#F$zufjC#+>WBED zFscC}gFo%tcqZDoh=umG^_?Aq4D^s0PX7M*WH`f_9W^Q`=uT)ZbgrnbmAF=fI5{~+ zEU-RGATm@nk^Y@HkLFXxFn5$tn2(P*O86IiL&cq?EE`os^wShLSsFwM8BRPA2f}J> z?L5z`Tv3C}VL5BJosavl7*;A&2rN}y^IqaK9P;^lRVZbpoMIP@W%dT8`KScvE!mT< z!tgCD5JeX?7Cb}T0w57j1@oOM%vZ`$ZQaR?+#miC{U;lyRUpp#=NloH3b(qUux$bx zDe=G(4S0(u;)>jA5}Ly?juwR6d-cQYVkH6*KQJX8-;@4@HJZ8pDTdFK>4q z@Evxn-l5KyY0VEvJ{#rNJ^7mSB(aAwPyDFUz5}|NAXu?;^W9B~wRa-~NKp74GwSdZ zc}Z5_OV3i)-oIgJx8C%J8O`30VA*AC1J@_%l}UQ){#fZEkngL@JL5a=?hNj9H^KVF zUgZ(y?2evb;#l$PAHMdYV%V?l*Lt9I1h;snd)eJ`DC7tp5Kp*w=S#RR^@|z4C^7$J zf|b4*o}LRf8&rjON;EEe8>6>{9POp9NOA-9x(u7gyEyX#-@-os#zl{PGKimWTOLD& z$d?%w`88S&hR1yr=-OK2!UB|vv8)_~pv+~b$1^iV2!-7-B?tMA#DRQ^bhBie<_OjY zyiQr;Rr<0@psiC)4zM6G5sD0f=QztN&x{GNF1}^Ru2Xo87!{joc52ETZz`udbVO?=12<7LBx3!ISXvZEHI!EOUC$& zi#I=r2Aw69x3+}NzJP}cPud&y1X4T$5NLu|8fQyb3rTdbn(K}SY{&ToXi@s7$RHPy z=FR9X(48wLvn6R5en1h_P1yL&Bn<3O2_NHgA=b`e>8c`w#qbCHmSHW5GPR>aJ;1|C zRms5*VzF_@Vb~op#pXu2qiyq$6!)_6eilmTQzk#|9mkLoul~!#BlLjlzd5poHh5$Q z!!kG}#AVPg6wgZFv!t7WOy<`8b-shS6pdXm9b*_8?8`q6aEkF4{BW@O;r7=EoPW1| zTVv9==fN)WK87A3sH)ToK2GhT)0@_D$cB)9%>}A^*kKTyIR#+m>*h4P0`v&D_59~w z-23agC>^1_I6=b@O`j?}p>ALohIQ^ll!c+}JNjnsuBBoW6!B_^9rx zSd1?#jL|-e3?~d2n2|DnFFfHX<*H3yzL~_O?RzG5&aPs0#gs_XH48;~C3}LYn7Vf17_X;0D zcSI1z39~&n5Kq9eS)KQA7EFgUv=E>6HEwu)-=8w4^nqC}i*$pRQK<%~Ze%({g?X)bmpS zluHc1eTh=Syb?m+{T#n-qBlEAiB&wiGdN{1sW61awF%EpM)C|o;r%|IWbTA9T1RAF z@L84;jhTQPvPc*l*I`{TEOrr^*u2Oy3~HEueqhWIv8*f|n#+UkW*tTetcsc5v&~>}HOW&nJje zdcTiF;=~hG`Vo5UNNE6Jakw_2o2BzDYM3OU5#%Nj^=MiGrYg`^=?#eIq`b@yx=!vU zx`}%ObsE9m!Hi2wr|Pn~iv7BRg{ba>-r%@qf{+Za2CbX17?i34X1dZ?+zR1rVRuSN zCxqY>1S`75Is>Eepn^6lQLNRExBORtoQwcfXEZ)KoezfbJpYrRroDa|)T(Cc3NS0Y zX4i~qbSlZ2@+ZMZd|K@4V=$`l?ip??(pBiM#xwyGs^9h864*9S=}w1Z^E<*5t&V6G zh~KpkRWfEs2?`q)(ogdd&0BoKbAhOnmMGHS$uXj3Z%+u42 z@R`V_(8q6iic~ZZUc}XfZ{%#mhjPZH3!r*!GDgd;#nKi&2n!HG;^S{#uE)CA_zf(@ zCduk&b_1;+en^kb5-U3bme5+HU{!SWu^h-;1QZj|MOsk-j4BVnHS^ri!HXym!A3LW z_`Sv%ftDxFRk~M*tw4mu4>eZ@x6@!eo4*DkZA1;;PdqXS67+jM{nV;LkL<3(T9$71 z>`8tT-;&ECEgyEJBxNV6t7j8S7W22+9fCzC3p1(Z^E5?Tq>3jd|2Rr6Yz&#n52r_O za0^?^Sq3UpFcyi-i1NBvSg(jjOs)wMi}^cA6TE@Q`H2;Hd1sSU;H7FR@snJsk*<>P zX6Nzt-jhc!c5rLV{*&E@dk^s=$TrLbDwa8be2=oEI#JD|%cXk)kac>@K0_zFt=b-@ zAuIxHa$&=K)=}SE(F2lc+~TZPDhupRlFhSXi9eo>Uh6qps2*h-`I6V{J^6s+M%%UK zcBw@0B=YPD=%k zQ!aSIfVFQ=_>0~EYkch6lgO(A7yBPp>uvpwt{d3F$^`}mw?LOxF2H`mF~F*5(bpR= zUsCG4$S~PwDzD{m5|!27oD@~nTDcz`OAAb48G!i3DcP!e53*FiWS&56_2kHB`~h-~V>ple`bt^+w zLv6>%NyYC`SU5NTwiUsEF`q9DFr3cRDHc#{Ay_FG23HrXw*{^i;6WPEu5j)I#65#oFh`-_#KQAsV}m5#FTyVwPlX=@rE7V~^O zgk>`rj*l+)hO^1(J7C5R&_nK#JWypK{C<^Bt$fc%ecQYY0yL+8l=5~V7$QLigMf=n z9@B9*JOfl`9heG_`sbvN_&(q@2olGHvy0CD3pfv+{2qRU|NQRR_Va&-;&UH2>J=}) z+uqxQhvB!KFY%6l%2cs|m8GsIVc;omQN-IECSdXUdzcDO4u|jTDfKQy=AyBlW8ilQ zwWobbF^1pcwpI(}i?D-!1!0ZG_$bze5IGBBE|1EUu{C0PPwCPF;LZXhh; zvYyjzj2J1E9Gre!l0ZWv=6!0xYbjEN&0$M3tfBm>>Se;tSVxc}6)%)#28$QnnbJZyI9x%LHlk4#Ft{KC1>i@(?U|TBtS81d1WOYjccAWM#hcN}rf#sR zt8f!aCI3+2S51vwP`TfyHTW)Z^(o?CDT%qGFrUbtgi zI(2>5=*X6uNZ|$s_ylEUKxAXEDfIr^cAF=IqHmZM6wsc8+kXuubbc`;FTg?}%6Q;R zf^fu1yBy6PChg(#PJYwe;Pi>I6}g_XF0b*_2IMu+EP;r-6?`U5bU22UB?qh4Djz>8 z2LuP#f${B=y*(nYtkN?2Vzjj8z%)BHYzBv2G2jc7KYs)R;Ur=sbm zas+_BktO!6&Xh6|wHO~)1Xj`ex?zT>46kaQt9v@<8ybL+5f7^Dm!pW zB&9X+y9gn%ea3)J-EAD=)=W#u7D$`G2U#a9imQ2m*lt=I3G+Y#B@_^-`^ulI-i?Hf zsM;Z}{|ch&z9!9u7%`}iA|PCihYP9^-uzSAeHt3_8FXbCoqv&(S_N}uM@KVf-WKFT zhA-S>W#QGd%`3_=uI)6d1fknA9Oj+6cFc;N0)Mi;zqT@V1!ZNjt%`f66Qak{Fh0g9 zbzJg@jpR%o|K5bUj|ui+V7)<3pZSqHNGlY6JGww}sz;V{zKe)Bxk(aycLZ0%rNrc> z!>0zUME*7nHGxXh0!ece79NvDD?EJ`4c}nT3#0s7?rA8b>)GH&7ua^0uE`htV@RhQ zjQCtq6s!PvZsO^l*~O{5B3w!hzg{&?s6{lh;z+2d8Bhu=N?H+a+@bbf`*#YHWM{=cHpQSD^=IXi*R zPQ5?Zu(UYq zb@*tLuHp-af9EsYaaWx84JPI{Sw#d=;?t372gHWh^-H5bLMYZe6d$IlDPRoG_~a=x zC3V9@Cc!R2FD*(+*9-9r1J`U<^app^`4CR}=!qkc_M|Po8#&q*jGLLli7)$oEfiuD2UyFEC0MATz~k zozTgJQ}m4tL3WA_19#Vz2`f)J5HG7_cQ}eZCf@m61dPS5V}1@Z zT#W!sMZM{ySL^}66w%yJMvuPmSbxV2q9jwvrcdv9m>BTIMNfPxYW*`6!L)ygps}4PYGH#;2M<j#4w?Kq%z>#MswVRjed&q5kzFRrBy+NliU~Cm zERu;p)?*krsglV6A7pIPEMR61!M|~S?sGy|k^FqgfhtqD46;TgjcC~kgceQ6SeMM< zQfB}qEQ5ijGRWqAsc zAhLYDa^TSv!G~koEHube-RE=*1Sdr@=+!*~fCS-O+~x)&6D!Q@tN(M5GvtH!%xDD$~zz z*&~QOx{L55Zve<1<3xvI+f~&Cs{G(O(-syYj^;nrm+b<}*1pKTSju5vK)!AgNS`f- zU=Csd#!Ebdl8z+(0QzphhaZosE^F>OuiGOFxp`9w1XgB#g(pYpJ$OAD_otWUJKn%P zckgfh%jP}cw%p`N=vY^{^SH}811C64L~%e+8QevetfJlKpu5@0ag}m0bF2lN)V*|! z6O_~%rJ@bUQkLN-7>QLd5q4P^tRG|NlNETV!ZHjpLt9nt2oP}Xy!r}h9&R?fhrwV; z4Gl@~r`cuYz8?3rxZBQ&1z@pueqx!yqnJB) zUkd+nZ^6Xk+EoMisp?l_x5pPYb&!kv8tX;}iCs7tT|B3OFhGw~{1;dSrWGniOWj?h zCxw6*EIywH4&?5$hNwhxpddVjh?8ihf+%nnmcq`v4u!CBe2gMt@N~w-=Pgl_coSjPMMtf?b=wl!fvirq#tbA}RLgPq$$;))} zwQKa-89mb|+U=tl<#Y_z-2ErHA#G#xXQ)hxHxK^F>I5dBO}ooN$ zaYf^a`W;<;dX3Ayhj5DK97(PX5QOqe!v|0S5f$r~@*-PKQI$N0MIPa@z9}-cqUKih zKjV;#nT@bDj5VC1!a1r75$~_Yb+Wa?Ch@xXS1AS8s=qHXG_gJdzvtzV@|ji-Zr~2q zPdoE7i($#x=!=z4wCYC6M;vO^k4AChc0aiFS3Ms6sFP7FMBTbCJbf!9@`fY5hFORW zi?}P-&A|!toAen@aF8xM!-;CrQ2hgp0RsOj180D6-y%s39c_4z$-z7Cu)`|~8v9Wx zsOb*zPWZ85mFaGaKE&uqE|w|N<*Gj=YB6$@}yurSqG+cz)-s-*<)_)YT5yZ~az zd>Y%Rh6*?@`3I>|kn0`Aq0qv>fS1rPl5~IlbPp$O7YEQ%Am4`mZ*Z;^-Txz{?jsKi z2}(y0d#8OIKW7J8Qeo2)_2c1JSoIw^gn6eMk8h?I$>XJ%o^Z#}gj_M4OhLzF)OP+1 zujzNqAM!|6^Z)Ya5V?}$h1Qft zl4JWhk0&37M{_2@_^CQZP?u?pM1i_Qw#~;09~;Th7nf|zS$if^)1Sf%TQcAHk6*(H zj3A<761aGj5rEcT1PZ^z6zrooF?X?Aufi?Jc}l>5?by+vsBU_ehXnp0Ub>n{q9+EG z4&3j#Fk*@t&uYaMII?;pQx{p|h_SFSx5w`gE;@KPJ(<%a2||9OYwV0J*|fu52+zcm zizJk&(7L9ZXtZ_`Poq(!wCee=&FHX%i3q8l_O8>F=VHxx^+B3`hNXvAbC*5p@qFg^ z)&Y_ba_>J_9GnY(iRCJ|$DZ{Ui_QL%uRiRoeM0fJ4CPK2T-Q&equKbgLb zpG5$#L!Z$^p~Ooxnmr!Ao}XCWa5Q_8yZ6j$+3LJPss7=`p)elAFyLl^A1WSlnLQ0_ z?!Gx!18|a&(-D}$ruRp%%>O0R-56Jc_>YUu%e`m1Yl2N>hwAg!rBSmq&A#9ow!xsK z%X38h87?KA66&Gb!{n=8I*%<~|oB3OTTd|Wcltm3%`vy0} z{ZjEG@z7(Z|1=+JeGXjS?nTf;!h13wi#JLq6gduJOU}+OE+3Dkh_OMQg@u?9`F#75 zuI6wY1L1gk9XuxW)fG|W5>F!xtG+81L;tyUr30UwU45*0epOhfF$Rd_;Z1gNXTcb; z)bfI}T(T|M%LSk^*e~VHf8SiU%3eM`n_tS-d;}X`or@cqC(-RCqagRjzPZo&f7$kR zvTbFj6{N>07Gtz(oIJ!bP*sWKuqa$5ibx6iUP%W@`rG|D6=X3nyp)sCQ1mON28X3v zq6MTdVjg44_d=pbW?iZ_>qAuDlkiFtZ@=+tYl-4Zw4FdFI&J8C1@BzHqc-kg z5##`%BB(#wY*dU{-2tade1#DZWd1>~(a>%T2!srrv^Ek9pAuc~F2oe+|bA=w{Q83tBAs zt2qz}l>g1X=Q(S8_C4Gu<;7ZAY|=8!1M#RfLKIX1L>o}kautoh?y_XY!qQjgz=GQv zyqH}KbQQWMYXSXtJK)bq9O0QRZ)*(qL1(x`zfNJs7BzF`Azk@})lf z2@a4xJ=fF-r*fQ~Rd|*I9PDfAt1nJyq{+g}(yJX28F$b#l>hiK9+rgDG=}AL z!ALs25C6t{<5#*PsrP7cOVdLI+x`J^HPdXrhEfH`r=rAkI$`x6>W=^LXukauW?qw- z`UzW;z^+U^D56)?R5u#M;Q|lTv`BuekN;nzb0ZoR>q=aBdS&Y3iT~&bF%;H!5TPIr{r(N@D7-YFd zIYBgt!yjUnB2;up5p+`0w^*?V2o}c-b+ww1*D{60hZw~(-t_a{1OB^#%yKN>4#8T+ zj}YbXvHSQOz02N){Ji)o>H0?XQ@9(F7s>9bzL2CIW0XKFWm2|g9izofriWokNt!it z=w+F_07NqCakSTYjmmU>GzcBx?dbfRc336hc`;9tOluYSHTVUTOJEnCVZPP$e_$Z{ za=66y3xU;Q2Z!Q)4<$DpOUIe$c*3FrfKxXB#ZDApM-Y5emQcTsfEv=TGapl}#>+Wx zx(f;I8KgJ!ntsdX^8#f>J7@h%xX8(zMtafAgG?%8FzAw1Rwsdz%`vvlkf3e(4|vM z&iOa}LpQQRz{m^o92q|h$>?8z>0QxOCs1C&s5l;aGzL)nLXXy@qZ7-HARb|T)D zFt<=oSw+n~9as-NUWx9OJHI!(k#A63VA5HBlcpDWwNT|6AllC9=q;UUU?LW|-0vLC zXBQ~&>#PigM^ERd4=0&?h;$l=i<)deDG_V|TgreiEw{}#PL#vxRc5t69CqH|)|(j& z*%Of9{PiYK>Ms5dYjrri8)h`)Gx%%n{_@_vUk&aZefjHu`T0G>h<^F=pZ9-#{EIJ# zM?b&cKl;_Lk6+(E`sLBTzJ4tk1^qF2TAzMIFy;^W#6r@-Vz_z2S?AzpbQH*Z8nY+C<(gjj*mj}nX2*}#b_>Rzz+G8jFM4EiDf zaLEHIJs>er#gAxuv)ipW@sa{eN6O{9)^wI5FxheO{3EnIau3y4OgVAmnS>bXYmkw~ z_Di`9M7Acl643>tjG|GbP%)o9Q!EW1lCy?H10Z~`-tE)g10dpfqe7;K*kzb8kNx0;bYv?aJ;XrfWTAON_nxKigJ4|_!%H!q^%meNI4PK6cD z8di5DM}_eA3AR93$ zHa#`+{*O>olTnvSjtU(LOGx|@kqLmyX1)p!500}XZJQ_}JK^#q6p`s67l@^0U$K|o zgQOCd3Iy8_WP{94FUr>T(hD7OtZWpp1qnviuSLM5gN6E1j#k&epY-An+WjzA-{Wk8 z$`Zww%MG_zA&^9`xP!aBs5K5D;o(P#MkYQ{>lQt@HL9s^bPI^I@C4D4MTrb+El>9U z*n9WBxQ=68_!J$t3al?CiXOOUU0Y!_@ri@y|eM&19PMwG+ zPHYyxY!ImbEH;H`q{{upN3AEAVlGE#O-sZ;h^H&1Zn+}=>Ov#o&T;eF9gZgbHPdpb zy!tk(9+Yp!fo5^%dCdsX7j7Iln65I#=oX?x%@Crt1pTV)W}Z1qKa45A65@*!uR6(R zY*d&;G$)cIJ0ol59OWbv7j$)XbMI*Lk6Zf(`>VcKexZNyIRsg&jW?cJ{#+fDs>$VK zg}J_j8Q_IXo{WnAwqCC9ysYUD)xrTyx&@QNWp3ET&?`=1v-|ySZMzDlg+HQey@S!} zd@^}B1H>1bFLw5RsP^@OuMZu|!S4#}dXAo6?rgq1sCM*n1ScHc0t3JPC>LW0fdsw+|# zgTu)`^&H7ZY&-nJ!NQ+wpuIusC550>aq?s}C2yHmRmTyf#zps?-jKQ+U0lE<6dr-o zv8yU;dgW}1!&~egvqej|quo{u#h&}k%E={-W3x4NES-cES})QY&fcmvcpsYmO&FoT zSNf^IwO*pr^rBZX*3@=uw!!geARx@ytRj>~RsO3P^f~j})igAuH?6`joy4Wd9C$|#aaR)DG-be)%Ew@18{ufsGuwRcZ1s@_$lfB)ZvFKrC)to>y< zd8h-aw4C$~N!sgwcmeqeFin45>h^lDl#h_55?q@h_Ce-^Orwdk$$5DK8jp(U=E;Xu z$>Zw?INaD2LBd}-C@2t?ug29l-6_jr#-0+Z=^!-sN#<*f=W^56cHHXGz`J=BHo2CZ48#k9;_V`rKlj9 zIs0iN&Nu%WXfg=$-GJR+24g4dA=9>f|`@RV!g5X?A13%o8RHIfCsE)%OX3M zIGEhX&UUun2l1|Sy3$26K3Naz5z<8W$tA=<9Pq0~^spszv#{@d+Md4j%!siQ)zIkp zv~L}3aRrv|chbIYzhPciCpF#6(t$axaQ%aaI!i92ivpx)fSJ! zs3hI+x}Zc2rmRdm4F8bEXO@@flnMXiA5B=2c6H4@d!u{9IeA95ZkcF5#X*jTIXdr3 zs(mEz+-Ye*%gax5B)4Glq$-|iAFHc)#_-wB{-$sXT12lT4WI}?v1$|JM^HV-NA#9P zN^!VJE}yklN!{^+o$pyBEK=3XMXs;k-BA=5aK^L{{vo<52;Q%8A<_f^X`s$aJT>GX zJ<_)qt+s6u`z6F~7tOUdQU^D_og{^*j?Vkzcg)Wu<2izarzHnJGz$tuvh;Sk`$Bdw z0E4EO!q)!1D+w()Tp7OjM3pAUB+SeZj(ux7H4x80yw2?BYASsG+1AUoy&uToc?E`1 z(te9h@Fr8nB)FY4@f^5a#g}T*>EIGM4*2)Oc~<;OF?2jtV!`IX{hK7B1ixjc#LyB6 zfr!QtVI2kuIluPC9TwL(!MO64`d#rqueLT1xWJLYDO8)H13>=?w)9SWQ+y%n%NJ9; zZhcd@)nA@^`$-uFCWb)Vn)^J2dB>%Ul71=n6a&8g3W(^E54R z{dW<%Gh7z4DAjcB9=K0JU=jpQ#}*C<8lvHQPGAOkR+Zp5jclJdzhGYeWU01x(gG$> z5p7)xxdTyIRbuQd?O8^a2QGKU0|Y2RXC1yX6!s}zxD5llGn~6R=v$arM%PJ?TrYQ! z?CTM@LJ$C4w&rvJP@#1?)<~zYk9`<)Wd-hmfR%njvX*cKI&f(43!O;hHo8@VyvF)f zc_o}nHB1VEok};ZvtFK#dd5SmjT>6xEzQr-T6Gv1-ou@v@-T&sv_ml5y5#A~bq?1c zx=2e!QvlU1WXOgiQeG?s;u|y%UHxo3)QC}M81Myv(yl_@A1Z3jeM$`15=flI3qhi& zPNwxhPlA5q@csfb@QAChQKDX81Y~?(c2-JCTmjoYkr;eWXF9_%Z91(^OrPZYCtfgL zR0?gD2$~}pfx$fuR+DHx%dM~odThY7?XMNr%KREO;Pf?fkcEqLLI~>0WXlv<$o8J| zc-I&*nQH1o_d@Rl^0h71oAm9#-P}3^iPr8<5b)P;=WH_7GPn)7I8+XNG7#k*8y^Y; z^4@#$dlDJ>C>l`)CHPA67Raw*=ILzHAukVS!@s6<57XfhdK z@5BgOW4gw!CCPf(!L##FetBgcD`l3oU2&H2m{wbb_D?m9P#a(ac87rScgQUO zVn(j%!?*1fDfsIZ&fUqzUukRZah7;0BnLjr95vQ7yBtu?lf>nsC~ zL%!=jHG&}7(``uC@nVl8dCn`Q*rAT1Y}q+CD}V}rkLX1h2{M&bi#pErwdU_A78Xk8 zSn2KbBI_y;u&(Vi?KU6o*BC8mRdDns*LiAe&LoJXi_;69u(@5b`#Zw%P(dk1E6I`_ z?Qibk@btRP_uNfq<~3CB33sOiImn|IDtsl^%WGQ_Vq%#Wc=d8@fgPvp+hV9-A|bSX zs{Ws&^>+fKpyGcu|IzRsG=JSyuIbjh>PT&#sD~6{O!H!Nq53QqC0NE)Pz$H5vG{Q> zf+pjk%uIa=8y>*Y1ZheuroUce!rzB2Vy$CmGV1iob{1@cuV#Y-!u#95h|`W6IG*gL zM&y#w;=&Q%7JVyDG>w-QL{e;ekDL>KKn6=bo|K6sa#=ZzSMvHaNmdWq_VppGD{bE0 z2kRewd^J9M4h!j!_5yGlxe+5Dj&L*m=mhrnp>Ua|?T!=+N5FHZT{Kd~9VZ~FmCx-8 z%p@JfzKI=3JOJ~JQ)%W$1iV>uy*X$$GvH$xK!!jeH93M+;` ztLQ~xEkaPPqXzJWqQw={yzS=Ik)@uUe%%)~R4CdRS67>Cjqz*+ z!5?*Xah?RFwN`=72eARdv9a1)WK*Hi>S{7%w3<^6>c7%s16P=vMXb7=GTfk!VR3VB zG#*@DV|zRy0u6ay134r5v;eT-8|H`9+HYQq^!8f5q9p*Dtti6a^*K<~8x>#v4H2_^ zb=C)!q#iablvnci*fN$&BZp4fg#Pj-p;%f_+0ncqINbIIJ#a5p7yZR+iUASF0{h;A znL9I$hjy@y;{fZXXm2jI@7Nr+Q0*{$=YUyWPXE$~drRYCS`am%sWh-3Tnio9F7bEb zi=^_R{)c>VV!50PLff8ng%F#%g8 z9VVfMKo2z>76NTMxaDLO6aH!TyHJ5TdN@$1Wg&cvEJ!vCtT%$QVmliHNo)ddiZ${{ zIK3BD0fJJ4(EI!G1oyH1)IF^!RlPd5WM~5|A^cqrr4Q}SiW${GqiO)#cKi|s)AD$P zjJ_fBthEzg2oLYm29W7DaC+QimX!rPJ>HgugWX>6b@HwBpQ5aRG0=F)m9j;kPT zs$O!CR!Jij0=v*#fe{KntI4tlD+CrtaVZL-HBuHNrets);FkPFvP8QTx{ii~AJ{4+ zl%!nVs8!;flAWpnP|HuRhOkr+17mftFD$*Im%Gb!{b6flGgvd5={H_4m{9tZX?h!2 z^P(%R1Vb`(=vr*?8)moHh2d>UFR@q+zkZmcw9FuhQ7SLGLtMnf6a;}{4T>MF6mi0% zA@9xF5(t8Qh!@=pPB#JY?bYC{XI1glH)IBY#cfCkOaMU`xnB&mb)_v0PoI@zfZtB~ zj?b9-9>c{>*fL$i0!+Le2AQrAb{!5;)?a>_F0gM_r@b1s;JOaBK@D@oskFvN0CmUc zFWQ9NVnM#3)@dATL|M=}X`-*B4!s7mKv-{bBu6-EM*qG0O6laU`;H?PAtYc=8tVib zmsCq2t;5zuhykgv3E3>vI=P_oBM}Q~VHylQJ|R>FT_`a~i3A?X?w@vkbq2)igPj(I zHW`RH{0}m_bLUV0Kza*<*ZDW{bAISK6JWy2@f4c67wCgqv52i39Dg z92Iht;bYzbrefw$FIj1S1@lRzM_F{$|L4g(wn+iB5h8;jKhuAM@UoQD3!45ooDx7b z)qi&8vz%Cg@Tdy-ks_QGNE5dPRL;Q`9=a*UxiqqX*DL$O-lTeE98k|Ws%OJi#OQci zV>~~>yes6<8}{~gh-bL}QT-vgm!tj3**%VfIb|{Ny7LbIFc}T?PfdAU1iSOI)Zyi@ z;@i}WhNc-D+Vx6tx;$<%K_L zz=uOj^_ZvXbJ%jxqPgn#u$7P!V((3$)IR$a=A^d>&bpU4I-VE9&2hs(s!Yw8CpET5 zBc!Fv)?Lc?v6@gpE2|>uX%2VgKf(lXYbk48a1g!Xpmp~9XFVa6Z7`CxTQ6*G{+9n-xB+4t8ii_18S}PC03Y zC0AJw{f}@KD7y7sQfz}=>b>RSIPU>=B#@eE$x6&-OTfMBJq5O{OwGvL_<2-5YMA)9 zqw_xGw_aR2#Scg^6Ibo^ofo?Zhuk~*cIU;W3+*LVq5w2)NPdelK#^XXV2-s#`S6a{ zc6T?{4%T3y#+PZjTbsAYtv2($WDvC=QwQVGkS}$$K6J+eZrs*6k}@&V8q@>{q;-9S z3g7q&$$yaL4I19rwdvV54ZSoEwHRTL6)@r5Jv?Nj!D9v)@P8Q&_}F*5>OxDPk+G^m zdL0?vywW0ZszTLCZccz-6D3*`E&tfFN3f7H;>X@Q<=M$v{Bh%(BizG%zV%JE8V_P+ zQA*OL0U*MP1W|mH&OjwBYT}IzSj&-BspfODt~4M6wcw(S#xa>8t-JWc*Bk~s{6fGv zidg1UgWn_?8nP^gQJPI)I}&0dMNcJMNe{fiu7x0}@*a0_Aqqta+5*9>udRQ(d9<;$ z7wFh)r%-vBfFK=j!lOUWjzGP%Bf`X{f>eT~7@fIA>gKS$v%a>SZJAhW1?qi9hi>L*G~% zhc+hu5n@R;%C}f`(g8kO2g^@2MqX`}ZDY`MZfVIvn|-v!+r_^gEwqI{$q|~RVG|1B zMj}H+c)GgPdO0mVT0qpa0?SMRHXo`LJPjaDOQtDinT~>jXg`PuPJZPts1{f$n=|1Y z45=_HI_qa{Nf;F*QDFYgW0`W1_B-BP(!MpZpF{^~$G|-;5$Kr<9pJJX23m({3(5*m z0oDatJ^M)NH%>1r)YKQm!_p}(vhoV-u3El<5YJ+)fH^9x+Oo-~fQF(y@t_e*lzvh6 zhY4U_j#!dkitf13jB3n2g{37rrVHN(#7&4B1`r~TRvb>%zsOY?nTyX+O#2C9b(O}l zPW${lKGTDN{~(*rZ-4vSB&$hTSw@A0gKy>H(0(Zwd#a;@BW?7NR+n#3ASVf4cI5J! zP9V!NCrNg%@Vw2x3_(`6v&@BtXd%&@h(V(`MVzkBN2i8#Qmef}<{BG2X%H%FuVDU@ zUAaU|8D}QGj{F^oqL;9?qNB$9$=;llov7`NnW*9kf-uzU>j>gL@1B8wCv#oPMB{?5ymI^C5(1g4ww7Q!AF3@I+ChKipW)>5;Q0Rys%K}_ie6*1Io zFZ1KVb8=UD;Z4ZNb6wyCeV7OX#<*Y*JMERAlPiVK7z4ha_tHOjhH6~=l?=AXbzhVa z1zL1@A(Wgx*2U8~Fqv+>e7=KBLI;~K_qTRlK3w=ZUVF&u;_AcluOJp6zTdYOcqKCV zOm4FxU#3B=!g;OdH_YWs4>1xE6vW}+-RT+r?^0wUg>PX;kfxpXxZlG$ZJ}+~eP+~t zdj^js4|msn#+0$8E-n=c&plGFQRwh`cAn4_K_;(KxoK9A{-pITo>1aBrDhTx@ehI3 zYLDFOMkuCk(3WB&gKflLet&W6;D@7^J9{tIwja4_6NXG?CesTO42E6Tm6Re%Z3$pu zLAz(luQ&nKoh&p=O-sarPa-N+Xo@_rXO#8$PJM|>+^l9LuUyG_A{m$br+`BjND}qu z`%FlWGA;=vNxPE1GOC5Pn^d4;K!Ik9Mg2iHSW+k8S>9GxTZb|Iic*v`lU-QZ;S4}Z z0nl0wp~*QSAl?ZHS0upT$Ub>1hB235qFFoDtdK+*&IT+DP!o=eb9BbL#meZUAGzI^ z%!Y_Yr4If^o(`3Xk6t`Ns_Z^YhVP<%-hWblnv}&JANXHF!dNnYqgN(bh3U)|pj7bf; zlU;An4F_eA!)Y(8s~ejKo9hQh|GoKx*oL;Z)(`dxs^7}1wbH_`DKzgX06^|g-ZA8c z<^+%vN~xiT>twL^l6tK1>-PKpb(?qAA;!DNjBZw8EIMl?)PX8pCQ%>CgM^_u3)nun zQf9bL+5&%>3ME?_M=T>ouv~et1-hz411PGHW1iXQ^+0A-{lt|5hT)Vuh+ol(N~ETs zsl-I%StV_%3RH2sTyjQ`G8AqJ0#R6}olnU0oy0oPDVmN3sxUST{v$^^o!}Z&9!?~o zKVe-^#fQAT)zF(7H>v3;vq^hbWaZuGCqFsvB^Q|5jR=z!b{K^M^^`Xs@|w=hC2Uda zPVvrRXX8HaD=Ob|Mp=TlwKp=0Z|EEQsMFRj?&_vBz?aLGV%>O`Sp!toCIsP!Z%~V> zyDm9NlIV^^c?LnMQ@GMBG)W_vRdJfVOrJi=6_ebz1@>CaKlgo)g4;pguqw1wVD2sBlC@6&1b8zHaKe z(lB;yV~x)NboB3;uVi%?|s?d zwQUDN8Wcl;Dr}?oDucV9Fwm8XIoY}xx9l9W8bbn&WWYG(%6M>j(q`nF zSLR=ta!r#EXlOX@R;Foz1!0zfu6m?0V;wna%O+jUK^#ok>TPKHQCkS?F_3{$u2Par z0{;^^OqB&My*nGdOBWSH>i2+0qn0q$9D=%Tb0intT?n0ESlz^q)Sf{_`6ern(KfBZ7EJDY zBkEgVb7H{&PJ?|L`?k)i!J`?g2dQEE|B&^0F_@Fh^e3s#u~MCe3l0J}scPOCbQwwH zNb&I$7@c!AW7Q;a5EAiZTS@+g$DD5l$=|q`wDod}saBHip-_}E4Pm~%=P*a_Rp^Wn zm68c06r(ptShE(6u}drJ1fgpAP&b0+V8PZ&fi9?;2+g}6 zWo(Xqb(WETPnIt);YZ?47Gb&a#dw7{Ni!j8k~wWoFBkCI?k@lB$@2emw1Q9{a$^D@ zJ8us@oSoqr5_x3=pcQ!3Tpr;oH(4j|84B8%l0Zqmr3nPFTnk!Jm7)>?25hC zo{X->$NfruGg^otw`kFYl8E{t(fdLx+}Sr{i%0ecFjWL)05OD-w?cf5hEuEKSc05y z;|)A)(gKa&3@(#8#i`ELfLj*#6|uxMy-q%G>leN5e;D4a*AwP!IjeU3T*~j(_vyQ8 zzu&&QTc5~b3-1Bezk-Tg`*OGb@n}J{Ydl2I+-v2C(}x^Q;@-)fxnX5-@jt%b8kTw7 zrDMBT=DqIt7XttW>nZ>TzaRkbYS-W!l>TBk`0;O56#VgTzu52>h&!qRu#3`P3PZQ2{nXh4^vSR1(E z{q}-iV+ayAGct4`C86beWl^C0$00bC|4sffwJzx?sT}6tP3B*F#!wYHKYG{bW@e0l zBH+h+S9f8B%WMgzE`vEj6-cZIC=W-i#ObUf+r@!^q??-(bdk#}5<0s&!&qFm%$1@h z;Nny~O@mCP;uebV3`TZ)HM8OsVnx-D>8G5kZE5OwYIul?6c?_tx=ot2t)!lfr)!(M zoYoy*2Z-1dn;`ryAI4B!*O zWS50shued_qz&o=eBF;Lfv} z?0t})b^wHUeK~AtrQ0ysUBw-8&F>Hr)a~vk{Xvw_(4HslHY+p52FFjGt!|53FC# z{SyvHTi3K$>ipneolLn_c!)yMWMOt1&GnGzS3_KDiGf<_Q&J=Dz8EwiO;LoX)`(5WTHW@A>ECg;W?Z9IG@f)Q=ZGYJM2>(UCqqJ zd-fr8gITW;Ue8-lLq^ItQD~aj{r&C72iyCM(Bb|BsCZCa(AZlkqch@y)lF8*Vh*2a zlPAP6JSW_DJIt@gcH|zIVNCDj5|<1*8TbEsHJ||m%&k>3hqqVq`l>v(_W|#7kS-9G zxPbrLI%!=4C7$-UKjb%gL7_6zqfpauf@&CCa^~VWm`i>hw{Uy7H6BdfKWe?bhVw$> zkr*{~m{R0n8?W^VesLp>xWc7$>c55SZl~NZ7J8A=6)t9|x@L6HNIu71IbJBG=I3V{ z=XIOJ_~tdP7)Vvg|D`k^%w*aJVHM|w@v920A{j3a67vlvRqF=RK4v542x12joPGTF z_zDhlWNNI(BzCwlAwjnpigRR`N>p&S!giE5G5keliXG1Z-27X;%xM8w#G$O7xC9Y4 zhx^P(xg^+5OuW7ziq2pR`|=W`QZUA!AJp+B}1vs3~MP{+zc#DH>vtV zTEGs;xkXbYqH&oR=j=LzjM0phnG=7~qq4?(WdkYFe=@FU?+UwvEhjy04pksED~O5H zVAS_JS*hwL()S2ad!RLwi>mse$kjflMHE4!=UqBa9m5(1i*b2^z%{fBB<;rt%zTVn zTe|&=y53gE-l2z6tsZa59DJmOlmTj@-{J>{c=}{D_8II zh{UkL#KVm|J%li_6*&1`Pd8msP_KzGX&yp z?a0F>ecq*>q)+kz@lj)!`y;49jsp2h<;=7>^ooDw$sS9E;E&U!MH<#`jydeWw1$&K z+1I!amQ~kVeK#p+^=1d;RLi^5Q;oXB=|@iv2wCd80^rwjnu^W%7Eb+$6?D{%sGA9M z^&CJmhf6kPJuWH`3V^mZbphKTF$oIQ%MudQ-@d@R1s_M_UMOpvS!HR;nL#}I2>8=G zrntpWMD(1Hhh@zHPCCz_4nsA9E}^P>-H*r~h8Rag0P>&@yNHA;>pcY&e2_G=nnzW` zQuKyJ0=jfm*Gt-bIvAUL8Ry<$k>2J8NCipdYz{xl@%y!5Z@=7pH(p^9wypB)tw)*` zZi5B4MzeR1X~^6#uOod}K2|3`Ia_HJC&RW?@!xSn$wsm9lbGfO&ndRB7*1E;;|z;a z)vBmic8#t3VT2oO%r3;nJahTe>)~BC!0JbZiJW0RnIy-p#YcRY#}nO*=i^_L$E|c;pJ{QNRug;q&r{-=RC;B3qCXOK{en zUcIevs^1#h;q=zn4&SD=I~cw14=bQzJhr>!#}ZEJS`du`eKjNt6v3C}xBOJLdE3VU zdt5)3ek)pF*F~#bK%7oeGHF0L#a#+q_FjUST!RKRPHW1>>`5cA9X7#<4wCu`;quFJ zSEE{ti!zV}Fro;xM{v@GDqs ze9;>)*`WX+X^`cw*YV>Uq#oAKI1D0J_k&} zVKeTQWTuIGxj^2nH_XL^vM*Z@D4J!A`F}$3FGpK z=4>l@HhS0b6Ugf7=8Mg}Z#G}9{{Y;D&Ila439?Dyr~uN%n?F#FLpvE$rgk$R*0wkI z4(0%$L_~I@m&TpV?59K!|zf9w6A<+uPZjJ$#iMXmEx?a}OYVzqa>s z>*Y5yfhbV`lkVUc#=bif!pj{j!sg8Jt2ofh^LHMfckO46Ue3PPe|vQw*q3n8nWI-W zA@}pr8SM#a+i6=-&b97M@utczY8gW;w2c}I;uhE z0V8vgE7!4aqfj}Ms9?(awynS?hLhey>FoZM0Gqn^_q>z7waCMN^*oxqT|9j@B)7Yd z^#d)o{#bIjMP9*EC;S4eQADrXWR;QUFpIx0GfEd<4 zPp`YK86~!MV3U!5CF42b&2-ogamgRvWX=R~zfHerY9mPzT9=nXQ5{ejCXaX!M=!b! z#vvbv@ha%rXKnyshbbg>!wzQv90ceIQtuchyaXjkLA5Tza(CpJs7@Q%&sQHm)~Adx zoA#KQK$1z}?+Du0$q&ukkF=6-zVMCd)sXul)CYhI2v2|=bal0gKlH57X%uQ$r;c*R z1wx!tHE#H5?k?CF%k_boM3F0|){{xU|K80mI*Odqb$xhKh!344=)uYYHAMyj-PSeD z^R|f8eiz(vbf~;V+(>8f@13u|{OV8tI2``t@W&^=d)Qg|^y`Nm{`u*{pKve;?h1%< zxdSXO%P{O46aa7r7e|-fcW_tOSH9K)kSR~_6^U*oL|mQXUf@+f*W~49>g2;=w9I0^N?}PjlC`!)k}gW5L(_f*1FCGtiLM)hZn!xn)P?1gY?`o=G7hen zU3(=$Fka;Y#ff5;7p+0hIH zL&`0a*=PNkE@8dWLX5=)X@V%4cI>A-HYfHJ!v{}X@+UERBBC45iAZNyXZhk;KNPTW z|09y8cpdiXB%rMb{cXKBzLClP2gjP9j_H6PvHa7``8ig zu_XdZ{x@*uSx_m{$+Ilqw6XRvcPmHO;ri(aM*;si3sSEaR0S-s{!^J4L|_n#>LH^< zOnhNNIB1#_DTmCnak8fe0fM$`}h9`Pyp-$5%b@6Np zYwN#~L8VeBUsu}!k1Ur~KzPe@-}$qTjU7P`(n@gJK`VRu)ik29<(xX+_5F_-2%&jk zSUGa1yp|RAcq6eS2W&q=COv_A zsYawdp(x$*3C&N1sO)AsK=u(g*aCt(wlWhvhYB0?7lj%gp4wZ0EvlG9jyS&REN|15ZldsgYK<;j}e1)CT;YO z=3%rIHFQG9Gq@%#QA7r*A#mt<`4!_Pus-(uFnAJPLPQbx?&H>HPd@*Z{f9yx>dQVt zpJ_EX9~>jt7zKLmxV7wy6~;gV=SQe7y1>ynUZYFTjH*OEOz<+GC)gJ+LFS^4~A@&*qB(~8m1fqaqp5N^gtEj{GHrQ-1E9wo-OQ-nT@#3kY& zr9vFkFhf24nX*|dx!^W!B8PU_=vtF^ZyFgkU`>`9fY~0%RZAdraDH(%>Va8%yvx@u z9)RhQ11jpKXh;+~E_T*kz_}GHJjz#<7LT^56+sxs_}GN`DAmFQbOnxrqDC>YgIZH3 zi7sy<;{v3zuECK}E9ZVTa7(rxM2+CK%F*XTLJf1LL`HTp-!8XWak~TJ+0uv<35!O* zh9Df!X%WJIg4syrObaGE-B86Ypl=(o{K#JvPh@sE%fGlBL$5S%!RXOhr2UFsZGj=K zdR3?ap`5pl;XMU3%x=ltJmT*gC})xM67VrkMmALj5|$qztPB6H|#Glb%auW$GrA1oMs?cQsEI5VkQSazknq-$fsYJ z00O;;_155J;?DqON>ozmMfb-w1G^I@@CzaI+d;3tHk^E9%C|s%6gPLGo&T}@m-LzJ zB$+e@?m^lJt^VBG25JfJutZ=$C&yy;q`qMEk<*Sd^FE`BU#OZ4l4@f;p`b2)GFjwh z9Sffm)%WV`a-id&UO0r^4UM^Nb~^m?Q5C~Ug%5r~(dY{<3nEO`ri!~%$;7K&$H{_` z#~-9w#N19$0wmm(1&S=@>ndu;w2nn-F^M6%%J3a7;f%-K>!S%GNRRQbutJb7vQZgC zlYPvtU%=OBce{-$TI%-b^&z4dJu0+9NlC#OuC-b@#_<7V7?G%2qVL27C}HJAm(>3O zh)u!aky8QWX2lRtia+d45#K6AM(UcK>rmt)rY);yBLSqYs=__b6SdRt-6WT4@a}rB znS*rX-yl4-o8CEh7drw=(ndfA9dMT|vu?LO`@#y;FbyjF+)ILSy7jB1D3}pZ^>&5Z}T z8NCVvnSKvee_;&0E83jbd1b-IE~Y29%v5y-+Z*na5MDl1kft+q3yit zYu1l!u5xdovwKkp<4E-*)M`kvQT@jMCutlGRyh+0ku_W=$u1Lm-Rd&UzGBqewlY$A z*C{Z<>bQsm$)POUf;ll*gAXv^PmdV~=TenMm!d+kGw%d)B33lTApDCn^I#Ih$4y#9X)<@KimPk}T($1Vscr5ecrXpMDwK3BozB7c z8s$!7&o^V=zr$Nz&2n@0AahK?SobYQn!&Xro%nI6i6n2p!f({+4OzL68gO1c+`H;( zU#83hL)P;42shbEQwRSf4j+N@xf8&^zsMT#qQG&y^A1)`?~W&|Io$7`V>cXvu`r}< zR}w!tJ6E>0MGwo_S-)p>CK%I?CHR7y1B_R(I^NW|u2o3QexW~06IFemPID>%qbIAY zvFq@P$HZ?18G~;ci%izQ3dyr-FQZQz={4la@q0Mn*fQQn_n`x8FDE(yVkRd8WZlhB z8*^)={ep7k+%m>3yi4)7$Da^qsLFg?_O2CFCRmGE6M>wu9#7>6iuwQpj-)VdY@@S< zU^c+^x^sLh#fwiGQ(BB_242C4G2{{%(+2}R@d#_Kh#M>hkJiaWNVxNM zPM(dfhCRHmbKIAtKb^Kw8SXJ69eIj$W+xZ34HFs#0=Dad@SYPZK)zZVfDk$CPoR|8 zOY`-VnbXT_T*pIb>GjbE-Cv+wwFgM=@@)M4^R0Hqy-u*y-jQ5KY{M>!R21~W&fk9u znos=5UItcl+@!bH0S4VQ^>A9oq250g&;Z0VNPQdpQy~pN7$>ql>dxmjA3UV_QI`uu zYnyZCzRb7(zv7O(6^jTm!#1AbD+qG?J^vm_I-CQ!yR!L>&;Hw@xR%A;1E$M<=Ro)# zbNDma{l6`;C4)a_cz+a~bU6=-<2x#8EM;{I@xhfg0?C4Y(9DEjapP0k)A~#LiMm&# z_BHPFl#lnc&@1(+5;`hX{Kj5`vK;%Jtv16jA#Zmb`XAvDFAbRw`ZcvRTnIV*MpAXG zNj6A1-WByW5DWo#04{~^_0a(O!r_L#uC+Tl8ysKHt{z^pku-S>As!9qYW4m`4R&xd zQ0`b9zPbPk`{DQOu|iePtQ!h$()JZDON&~6tJW{N_mE!*FDBcipuhvR zb`BHgRneWbJrts}9}QKQNO_KGG(1l4h>)Ej_uq|bHIc1PV0$op-vBs2#bc`*8)8XQ zGtD2pMsM!6{M}G-LrnzdL-PVT0v5o&uNvQD&ZT019CvXgR{w0Y9Lv<5;o0hm9K+$L&V+0?fO^N zs$$m`QTEf63frTPkj%9e#7RURnt|W{5jn3DZ8pM4Le1z%@$+K9kf?efwZ_`n+34do zs3{vL`6hZdG9Z2E2VD^cV*qUm-RkPA-Ho+_&7+-{+dmv_Z|xs!Zd8bV(yW9OaO2?} z3^p2e&z{RR4_j6HlxfQUly0@`#_LYtT`>FOxeQ1y|YDdaHf?Fajoc&zlqn49#IMX4Hv}7$}rMEcV z5tdBYuZ8j|+hw6MVEbTL;FrLI?NfmURw;_Y-IEG7Sh-Rdsw;+ANB;^RYFI97)f9&& z1qWvJq9|0YV2qV3g&;jcK-iY7r+BmQYl3ZNd8N^kKEYVTbinw2{=av}NUQ>n)$!%g z@%1sDvShdw3N7Ei1YgmdXd@>(lDjpzW0dXm!rK7O$}3UP)3A7e=($ zlAT+0hdnC^+yNeje(U_|@(ND2YuuARKD%P-{gp*E-!2U%&k;U&x}}F+Vn@lYvf_fS z?eaZh-UF5nH0DfR{&`=yp>|{nPD`u=S;6%zv+G;7{m* zS!uoEym5OP8RN~eLEc9E;Au~$%`jB4%E@?i&Yb(&1KL6|9ox%WhAv6{8Vjv4XTzNJ zy-|x^oYO8OZ8zyDe{Cb5waXybrxIdNnVC#FN=g-3j{Q`anV7Oqb)qD%k&7j1E%CIF*2s$tf+S>uq>U=Gz28j;1KfrD=E;W_{andwuP-Mmvjpi%4*vp;!FzOW@KcO)G{^2r~|y?TZNK@;SvZt0JDS?V{5DF zyGf5tNd9F7nlLFIKQ2a=@$8W#4NJ7zq_g9*(Xj7}Y8sv76VqscLUhlwRpfb0+bB@g zA-PBAyLw#rUE8>kKw2yRhI+bAi_<0-Yn|+WcBr|EGIs+MW|4N%N5iu^4+Jig``0s z3WXF?-lZ7wJoqVV$h81cuD%&(1XneMB*b0JuKlIt*tEkGVfEHMc`}I30nr>MUq04s zoFvJ>vl~>;vQIx*RC@|o_bTC7ClYu=If3u4D7bB^^{v)%J9bhUdAyHW3lJtXwZ}CW zgS>;>>Jvm-UQE?tz>BX_yTviZj4s}JqPop!yH%QG_&l6EYvX-YjW$!^Ays)(m3WvN z7>4R7VV*{|;kWUtRN^74q#qC%@1jKFbhT!O_5PTYl&SaDRdU9w$Y~|^jG-xfGdP(6 zQ3LGqQ@l@jHSRy-qp?ufv2v3vNI}3P-$I;Z4?AQXM<_C`Ck11j3l5Q_hQcWfH5zB+ z_*c^)bu|gXQg-rOA+_^i3iqd<66vOBq#v4Dgv76PQZ^{t`;%VFh6ESnF^?x@Wp^kz z8;#zRn_#7w_>y=7`;&%z>mwcc(ZXk%MIP~mB}FFNQMhb8Ohf_Pbe!4QKbU%+ z;rE~8K%=tY0b;5L6u|9ozy0ooKRc(dmBHGgS=4BC0A-s74T8pxXmZDT*!!5S$x|g! zRV@-|#v*#7EmdE{0tX2m+&d9pHOidnDN9g-7#JDrLP?}@C&Z4kwAGWNQ>Hnhg@%o# zV$wCE!dz17Kyfs0P^X&O&^g1>r4Pm;?jdgQ2NiX#d3Slr?)C1FMiL{Y(Gb?AE;bn6 zHSC`8;T#%eY`;z9Ayd+CVokw&rsP|f@o7KLERlG(S6AikC05;*99J;O4O~jqD+l!M zaxaGs1uaU+fJUQ_h@ElBz|rW=6ItNf3WG5($b|&TL|hvcNU5)DVkdB1_`r6sP8&a8X`*4Xa42>3%vvysEPv=IdQKF}jVFr|Qap z*f$a7<<~mNu61_x>_rUH)|75N`;>~mk0s z)Bfr!YBw1uQs3ejAbn-kX_Dw8A0VJy!eeXiNsS^?J$zgBBir0Yj{*=*WEi0O8Co$H z86cxZMy}dF>6Cv>nlEA&CR7r&4tC4lNhCpU9x zK-g{>=w*-ZGB*T(Ys+g>M(6tWNiKUQIajVDrd=5acau3~Xe$M2U)sl*2^k*+{gyZ`#F1SS+E-w#9%jg514@eD)_47Wk zYUr0QOh_UG(R&2zKH-{!JU zSP@67^8u{iSS?97zfwWE>mf)I)%grGqI9&*Tz#gD*W-V24UY2bzu_{~;tB|Ph(SnH z$ZPMa2oM?Pn|isB%&caLmT-a_t#-lF0n<&30l9^5JzAnKChSDGaEM_glW(TPmm>ho zB_H89(}QUmgc6->eq*Zt;{Z9>B^1Rbh%6m^Jp!`CWE~9C1y>^jOjLOMH2sPnghkrE zSqj}(tn&UvR2J%ip+kuw$${C8A1l_3dilr&^DZ%!z5X3~)%H=W^05uE#ZFY7i5hu@ z-L8e$26{AhBox@IUqow7okIJQT^fIS_g)f)$Q7h%#3Fj?$Roi?I--q*l2j?$(#Zr` z6GHPD+O*{+9Nv_-&~jiroka0fW^EM3)-6T{qy4h6f+T3nJP6zp%BJ~vzV^PAl#u+uwg}bxy80d*)@d&IK zaJC_2G@2l+Bc7s!J;=!76J+YbFGa8YfN64sq-vkI85u?(FJGGHpNShzS}R2i((jtsT+Z3q9h1F;(m;{Y?;||4jTK;I6^`ghYEl+S|a% zx+w?KDd*dyOP#bU%%yHeDl2lX!WB#!;u8b--Q2&N1^rjSTRObX!Wf!_Bn|g57ih30~^q;0_H4GtGWaN`+ ziv&d>yikLU03u%RtMh#X3lGlZ?TXrJ;rsJ5K8}_2rNKEGYaACW9Rg|u<9DP=rKJWT zhcVUTC6M8bG}A+~N328D7$6oyL@JIdK42o!B3<%Wf37~J(3?u)@A*V4bbnHiNaVx_ z7q)@nz5*e=MkYqtzNEFVJLLFb36_yJ2Y&tW#z0aI>!N0u)9=f@!I&oxA^`q<{6b1q zZz+cv+UtIQIl5vB$oR$75W!n?B?xOVgSAa2d`!ZFRKO?$u-fX;06(bnYSbKnxVaKM zsBteTz*ck8Ad|w+%7Msl=hskte!jr0GALmmVJ=Sg3mHOd)LMQ;$Kq&sF2>I8_T+{` zsHa!3ea(~v9{nw~oV}^Ik0(S)b9_;4QlbdsE78@MX zPLioR;b*eX&S4t;9&Q>KWP|7s_-|?a9FB&d<#MxN1#e{njX>PSu=#4p%Ui*#+Z8Vr z98fi;%hp>s7vcCF(p^1$N2r`ih{})V*7ufc6Gm^a<*_Y76*4#SpUW11L+hUeN;CJXm z!pRNi(mf&sCL_cL>tl_OEUC4Q?XB{ZA**)9U*1kx;i6FIUdl!Zdv2+A<8UWtl7RB6 zPGCi$7rA4DAHI7AF50^m-vAR%Df@wAG94fMlbJX&U$$xj(TuXLKk!ieA5L!ZY~t(DgXk zB4B)_tC7kQ*SR&h#?`R%5O`#JWu>*thnL|$25}c;e49X|Tllo&4iC;&a&lx~M06BE zs@DalLjLdS;{L_Gw_<~!+|k(BVI zk4z{3Inj$|JG+~EYX@69FZYi&|G2r15wug|Q{i{TYLdc|%!g`?imtlZ3iHhkDvhdG z;?Fe|nkCJ zA-T@SFw5y#*X9@T6wbh?nc!?Y6i2(#6KE$J+!-<7@ZJ4@6>pqV?Zd0N$ zy20w3xLc|+QgnBu4J&@XNwZ*`!{>)lXF0-TTl>stX;aozti|H18NR-`M~Vn(9&8>z z-y8!v(=rZTru86ZbEcO1kZA30$kZcC5*U?V9WwG1|D!9SsN@Gs*s!u1YM69vtM@j& z)`D;*1voQ0*NV3a!w&+b6fnsR18hAH6umR|{TK3psqH}|mN3Hl6LdVsp^tRHQ-ce*PY%$=J0Vd?K~F`1h^gw5U0+N zlN1dhunH(hlGF>^RK3Mxc+*z;C2Q*o6Y{DHoEkGBsU;#@u2+x-Sx}i=uzCBz(=?UEsQK8D3x^EA$UK*)@$yA0;_`WWUVurPElRg*q20+SDNdv zICVGZK{569g?jdU{~327flaBN?#I}oWE8Khi+@gRk>7+lwRZKSGsBv)7E z&4sWPT^~q0jkJx-$c1gx_Eo`#OSS>BaR{!3$)O-&yMe0GQTIG8HlrJ~x}%Zk8&9*) za;xuwMvTxRbUwXuRmFQewaXx$*ckv2GSxL~%uy!l*YsXz^*~~DH%LBb`63>XTEVDk zdlQMMR}YiW4=$a!7U?EJKuAbGr=^_WgD6mCE?SYq0^eeAO(!fJ%rFTp!7>$H8_8B$ z=-8$(*qtSUp=>N2b_yJ6wIo(4C8`oWjUp^ZQfQKq0&0BAB?wDbh;)}_z=oAL!3fA$ zf7$@Pgw3l+iiZ)tlMr*_+Ts>65}!xksdze!0)#TaM9q02o5#kjGy&zS;sL0{C~o7! zmK=fc?U59vY@CykU?^*A+eF51>{D2}wAwidmmp*tTQ8#Bx(>-rS||rM*s^7}nRboZlHNRTxg~So0y<5 zcuJ~2H(BsfWN;})g#>xQ5aR_kxzZwGaA||c%B@8+&=!W}DwBymOJpJOGRn}yiBN8M zn6{9TOf|NKoIUpI1X8P@v9T#$)cyb48Bsgm6I0_tN-WX149NcHbwu{j6z?OVFrc@;W|>Y(<>6 zDTh|VnMR|9x0H>Gmybk+w?g7obPD^OGE5<%U4=;SFVusmO#q0F)!VZineTY^$i zACrXc$QRp+qkkv4jK@@_OzX;&-^{S;PB)T)8CE!P?FdoCsLdmuR+njlK9LGm!eO&= zLwohZMxNWQTA@Oh)hfUetZvnELsV*It5(`XfaoQ%=E0SBd|KCwr<4h~gOG4EqPI*) zibY#%O19gHb4k>XMwus@Z-!<|?%LP^lnh%nQMeM4sDXMzGDah$(lbXhb~^zfly~0p zE+lf)O2x)8${`Tvt|KJGWO|_YM3FB4%C{nIvXaM|Dju|}f8D5a zXh)L1!QNG-&7?r&hw!F>&%;U@$0_HKB#DU1jQ^8_57j4wGhAuI7OEJZA4Ppn0@c``G3q%FFED9P-QoX*liC!3d;zMU zOk_H;+@Qx#5_;A_Le01kXM+oS zll8oN(P_hP!q?(H_Q#2vWI{WsuR^Js)l*ginZyNEo{n$?2}t!-oZcQpUqmvE`Zng? zgXSvMfx2fY_-sS|QJ)l2MJ&Ev9-8)?Aq{UrBARWw`GZ4z7c6N9Eg{UrMk^^}>U%#+ zIxr3{VYo-Fh@RF`8~}@5oMN;si9r7frc_G2Wy!8hxh_&Gviq{$_W^wvb}&8D^GsmhRC zpw$Y`x$M6mTwLH{iS;k+hIi-CPfJHxX^TKL+a?R`PqF{0=Nx%>^j-`j{^d~^bm*7# z@`qV)90)NBVsH?nL!2pYnw^2Hagp1ng014DF@zcfLI1b@GuaVhs-?j^E&>{1s|bpC zKXb?&=&b6_``gxk zwEpkad3W;O9Bg8bE=VU;>uc8I{+59=#!ZFc39f$O`k7>q!xc244_;9t1~lM1TM2<# z=>^!*Eh%2)#V#^zFA{uKaqdU2$M?v4Jo*TsmCj8W7(m~cZ8hGNVU=a z`=9>LgU6k(SFa}H$AJAY9(QUz{2B$X|Md9Hr;i{0XJ_Tphf6=58@uF_NzzjAXfPtZ z=XpnqUw`&S_H+RM;`BnHk2+1TzNON3>OOKSbt<(4?Fi~}g(0X~p?i~u^YDqOqbu>* zYBHVSB(A0^8CcOO1AR3d{K$-`KVAn=Ngbw^aK8XkzZY4gTdqzLiHK^gAYZF&9aR@9 z8!H?AmZFLMGXdz2*YoKL-B!|qFQ?drYga@1Jrz;P3m(+NETwo-3=aV6U2DgL?ZkKe zA@7m&O->0mB^PLkNfjjE){SH^OA?u3&%G;z%(>gR52rug__HobX_#SO#ok)-CQ zcAh^!93KuBR~8mwT0UcZMQdYgf9=`!=Emms+7I~OKkm}Fw)4{K?Qb5mo^8E6I#}EL zX7k{yPyTQ3;>qL172&C%_w+Ejy4s%{cafRQsh=6j77yFVrm*PU{oTO-sdc#cN$mHN z!^PGo|0$J3mP|oz4W--{YDMV*jbL4xrUpSA6n-v_gr{O*o?}0vS#}_h?>t&yHa{ZF zMv~cn`Q?|pdpm#pVF7Y26>5{yg#~o})!|a-7)gQ;m;V0blh=@!=;{}A%i-%x_@`YdVfhjo1_*=`vqMwJ4|MOv}$=d&?^*!FkM0yn*!q%_feZ+tC zDHUUd?EVqbq;Pbc(s05&XU1qGBtX>vZ~}(a?`0kO+^^qB?d5ag0EM)jazOw0h5qbM zy2oHH3k$>)s{Qns7~v``vf=L+7ZwD~aSOy%P{rxXuK}W4Ncdl_D?&Do`ypDT*%D*| z`LGns7*77-UPFI;!oHq~`n4-bXpZ~ADg-Vi|4Q|};c1_8oa|_`aHpwEHEQAGIxaGd z&iY$Bg*mDj1y>+59`6|N;}|+jBb$nzVhGD&{4-#xx$>5ovXTrTO|3Tk)!6GjO&mWy z47Z!(U$<&)P=xLZc&Co6E|}Xj^-GYqYW<=+elIH$aCo=ZzcUDCW7e-#-6(ogylBWC&z ztOdcD6+=L3b|r$CPGdrs2hB%7=} z0%zYV%ijcx+GK73j+iI9e1e7Nxeg;loqUca{qC4aCzyZbpELI88u7&STil3Se43Zyi3vITn;9d=Yho523UD8<#Zp!3F#3{-|bYlRX`nU zXGTRV8E==CBfVZ4=s`b1UfCcx-iLnC15)ZL(cupuJ9^pe8@3qXw4nS1Rk-biCT|4U zZV9M<7FwT$8snj`8YxmDP7t~aXEF{2u$~C0tkOIhcV|Gc>N^XGd)u7ZYvXrUj9ZVd z&bMC;-wzQ!8&1jN7DvNdq$@hCOvAZox#w_?P???MwHJ0?os4E)F5dPHja+96c3bRB z;@p3ygpukKDK6OM^3&6>;qs7CTqSD6A@AL zuRY$A4hS^aD?ee)NdQWOGvUONdKx1ryb|S5Aq)rwB6$HP;>)Ws+(O(}V)Tq;maa~N z*2_L(gSj8FY@#=FjMv9ND{Tq)YsK^@WaSo!2NaP@q96vSS(z|`V`PdNUR{_nkzG|_ zLO>OPievr)|3p9Q+6eswfrg6;)QCI(fmqb6M;=?zP>_OC8&#H?QIO;AE%yB%MsLNI zm^k!`?u5HqS=Ogys9}&j)ax4cs1qRtkxC*j;-voBGhm<7kKgv&PF$d16hVav-cxr32YZ5=^En`axgq>W))2fDzTpPd|3~zEAif ztPz~u4A27P`0C*4Xy1o3DP(n!vLQ@A6ssHt^mYOD<5 zC*?>gq3xu5F*w4Rn$9l>eApi^$7e@sHCKB&Y~&xl6?;3a6=D`xSh5ynxv)@eoKKLL zteKEUCNoed8ANO9gpR;f9G=ep);C)(4=}~FhLvWqf@mAoGHeylS~89!YbB_*|3 zvo#YaE7*1tX!(v$7((5=(jmWCn7ZbOUQ%kXwN;pv>8p2Ju9{@*V+j^>*m;uUNmM^Z zA^(x;WXSvoa(|jIN&-w2M7)PKA{)aoPofUs?Dv;fGt?52xBVNH(5wmG^&^uDD_?= z`#xie!@I7Ykd3S((gVolJ^4Y3)e2UuCs2si z>N8(E&vhN%Qt=5#6WH$!3Qen#E`-HZZm1T^IgU#X* zGxl`G$&yo<|6-_}Y2>>?ibU8L~m6Bn6Z-ujObz-WpdwZo3JA$a3 zv7Uvc#?zjq^7Y-Knzb@^YSuf$UF)RKu?34Ru>h^gE3k*EZ2Dg%Www&S*Mp2^n=1!` zzyZbVD6?d^uaJ;{t`X?!UtXs$JHQWtj)ndC{|9+x79-^s z%no{|hsu&Pjb`dHPN^?B1E)c1gVFr2u!MjD8kWQ@0~Xv4E?Sdi(OD}VAR`&l=p+GGKcWn&K)vMJx3#wmp znxAG%tAtZ|D!dsAVBC{DJ4kh%l{(%yBj*AV9BGrA!}}E&Z+?ify&t%lLBMfzInNP8 z^L#YM^9P9ZfYE6%Ld<)t71$&YN7s(hq$-p}$b;HUr=h0^6iSdFRA;BwDt4-e^`*=1 z7)YsBtyltrDZp(SHcE2hATFdkucE)Q8cIFZR_oy)o3vBzq3hqgbV&zX0z)dNVGG4a zxD1glwX6+@S>c+ocA-)hhf8{3+e#XY{N!&!;P5-w@uqxQ$pd&Z8i@R5^{fF3PoKQf{?rF8;DWbHTIP! zrvoua^u2J4O5jSEUsx_E-&0mK%rL2cCr0b~lEyXp6cXOoxHNGmF*v&Unyhg(QNo*f zf~`lwZh*KtGAf46AuXj9{H;4zm$>z@YXTEGOZHoUs}<>#PFYo9`sDx;(B>DhCuOoc zS0k$70P60G_Av#aCOi(eU4#H%jgh7oPCdhuyaS?I5zcZ5&(%$ZTCgpDVwFD8`)_d+ zZ!Csy`yn)Z#oiewbp&cYL#%Tr>4XYI5)zZ@b@)W`$FN*2R2z5CqvLX+jjPZT$WmNA zi=eBh7v=Cf{miU;UiD}=8lQK0X}4ExmDDGXMOw0SFmyS-Ud9E~(`71~S7Tmzd#cY* zVt~`j%L^$`W7tCbynlI$%ek!6-i3bOW+jBpBN+?Jv>J}qa$RWoQevmN%mShc!s8#8 zKYskU4D6mS1An-uk6Qt_iG*Hg%ttJibi-kf-d+vPdPhiuGR7T2#S+$up4I=5224-_ zYLf>4^=JWoC_Yu)FojrYL{&WOf^X6BhIuD(j>n-4JP3JSNCJWybb-xFSO&u@)mfH6 zDVKcX9jk5ga|81vnKZ2nb5M17ut-&mwuZmD6D+7UcTIz%P+g)RB%>X z>5B73#>xV-@JLr5Yisx=6{L&$o4YNlZL(}(r>Oi~`9|L<@u;K(BLl;8m9dpCe^4?# zFI3`PWdp&65f}p*ot`WbMv-dM;1KFmv7xJ+u1JZTXyVfLP4S&K^&g6V+jtXa!T)!s z3lwfgD$o`79X|aUHrv+x?DXN)qJ}5YhjZa(2 z{Ub+$eA53ZrF0rUulB~5pr5)_wly*Ji8;Rl&!bkd4mg_dK9s`49YL$%@N#vvet7IV z--sT$!XuFX9nPkeV0^537LkBz2CqR+eTSHPvf4F075KY3 zFRI&K6Wvil*pTJ8c(=Q^c~m?hiPdYvOuZcJDU)SRORmG;i6I)t_p4t`{4QY`TXy=d zixO8Z)D)PkUu)x9lW}dE55lcHA8;Hh1&o#S7vt_tXi&s+ANs$tK^pb#c zVb-DBtx2#YRhKImWSS@<+K6s9wh>-RbR4RjUx+Tb=d~)wDNfT>ghY66&d##Sqb@dX zsfkR(cG0CxViwPuBYT@CZz%?lmY&<_MqEo7{Py7O7Vy^m<>3WtN1B+&RQW+ zOv7?C<4aXlE3lZ>5v#{W`IGo|w^Z4I1Pj^_jC7X~?00?E zx5PynmxfV8f51%+^|uXFbF}_};E*LO{nCVC-+OmfqvBpSJps1J@0^c{Z_1VV5NXk>&VlP+UARxuOZ! zx3cVKl=e)Jmo!Q`1H$JKS>|Y+;Yn>|e?Rk)5VYSk36ByWj^?_VZzmK8xE2iIN>fM# z)|0#W-kVBoCrwX{mPw)py{t<4vCk^mE8djqtsYiC9EAVBLH+^U`=WmBP&S329)HmZ zGv_u)T8rOXbQ)}BcHygIL_obtMwqfoY<4oZc2V6V0RwP?H1ClRgRwQmOgGz_HDxn6 zVD{HP>gOnGP_{FFnnL`9)Rvi(ve|IbV;xfQnPt z@Y4X8O5V+Kw4R{jq=aR9fPxjvF_Zomxc%jLDP(OQ1A7D7Z~~?O7#9&uKek;&j^z1y z%Vw%B#0ks2gn?)0t<>hwZshTl-62D7r8bFh+U_c3^6oTmshz)U@0Ujm-ldxK?R?>f zXFZt1n8Q2NDi=Z#Q<&rCH1abdKGzpjFueMdc-kl)ubB^O7CK%&fg#Zr`+g>C65_d^7vEyuhjhO z6|975Pl(5U{PogLtcCwe^*)sxE<}@g$=ZVTn7J|F%Blzpo{pq+7tezw2SOLsTZbD8 zHgLde!P;~pH2^2-P8Hsu|5QKenn^^L)!IndQuBnOboZs4tC)L0wPY$?Uyk{u zCR+uZh(yBMNoOx4L=3%I%AJ!lB+^01g_d`gw3VmF;3g1jX>2Ob^sXHl|{0#9oXh95@nu~&0&u=$jpjqRuM4Iu_1BF>CH#@Cd=qO( zl(Xt~x=`UL`>B_Zps$?DF0B-}aA4(eT^nYgW8auL7!q@F9A#08cNJ+6q;*-SgIvb4 zm7*FmgiY06Em82(>UWM|x{)t_z0B=eu2kXrvd@PWn&HwM$fzcyDnw}< zzLJMT!|$TssZCAf-l}bgsZLs}o}hj^?E0?Jtqp^C=Bd6rkWL7N(sZCjJk1mzmuwBC zkLVq`Cr<`rUwtb2W@T$H5m;EB%S{k8 z=l;K#76^0oyYz%Ykzkc zof63`({}xoZ^HQ905h*G)}X6cb)kbL#yuE^n{2ap6fS;NERJKhKjsC|8(io`*EJ`- zd%Aa%2+H_p6Bw~Zc_Ai?aBaAFqcT)akfsPo&7bk!jJyU#sA*|RZsUwXs@SFvTCO0o z<&@*S#B+rF4JuqLQ#WTIiY^_6qC9;i7a#ijO8 zrBuKW`lx{+bU}pOoG;1-scz}yu1>tF<5`2YUhW^PZEs_-<*JTzb5oC?{rf*_nr0kI z+v0}l^32A#BIM(~+TBDA}vy8O^U|mNyP)ppt*n82nr@ z_s>I|HR92E8=q0pHwvt;W!wWufe`c!H>LBpWy|f2uP1!}q3{l?mjp8_=QZo(>9(p6 z>s=X=dG~9jR_0H8Hyf8XJ>DL&@HnkEXi{(qAwG4gQ!3R7j8ptH{VV?7)S#+gViTfd|)WV@msFW)~ zpScHqGJEqTDarzZjK_otmwYlp=p<+@f~>3v%W|h>&o3ZD!d*j0>rM0RUEx#^2Nvrr zY4|K}WUB+o(-+gn3&c`%-ywPsg5gmsOpnq@X@l~@Idb@1lXNUeKY5EIKz8)E)tIH= zZCIcK()0SC;lgFs-|f`Q%8gc2@=P}zkN6Cfuik6}e)L@Hf-{VbH#}v7#z4|AgYYGW zN#IaZ?wHi$okpHLY85EK29v(_GoL^>S zq+~dPyAqLz(B66Y8s|aTdV=gDe%h?eYL0j;luM$sxU1yOC}NG!$>JRS>V;^@D}0z*Ux0bwfyL#R z=kL)`L0V6K``h0Jz8f`EV{eXSqpkXDe#tOYrsmLJ@83O?8&@h|7X!Mk1#l}Han32+ zzvpSj6p8`wVG6cxcbEDzAop>mfgA*R$!dRoeRm$0%ObBh8Gr2ebgNM?RiJgt&fSrE z7qDs|yNOdLU(aNFlf&HH(3oijQ!P_-svy%f3$y; zA+iU@xI;~=8GgIHZUw!k<=KjIf&P#m#m5v&Nyq6~Bz^J8p4zWpu=v;gG8Kdz)F18qT#_I}* ziB_Q?7Cx86@wzk_U5$@Tzc{xS7i++%}R$%LV?1vImfHGJ8M!?N*x;xIG6OMIuS`c zYB)j|mx-(dr3;QzzZ&z1p=!h_Q*ahSJ(;f%Q?XK-FSfIU#{;oOTh(JyP(k`)G{!8| zT570CoMEHk*|mE=FDE+;XqOyM*g&;JZW9CPJs+H5qxDnl2en0q1^JZSb9aP($1$lY z%0qE4-Af4Z!&&_lj^_%!3HGfNYcC-LUTHje?G5nhb=%1C4YE7N7O$Nk-$;JP^n9oHb3{OT+Kbd^_SpMKm9ccs*B3(XFn$-83 zs}MzQn!y!-!CEuJu)~I1VMx>)@gr%5UC*n55SB)!EfOI8Z^(M#{*zTg9D0^e#ef(u zCv!+4!0-Ak8=_Ameh`0JqDVp^lt>r{vD*3sX>H|NTm%I};D=(pKRNDR^i`J8LLj`9 zkcJl(;<&h5$8sJ4r(in@C?b4XP4myy(|~4T?jwq}y$m$|_ICEY}+39sO$70=5C(@!d-Jf4+0^3sU;C(4={_d3LaR2P=n!YglDXM`S8P; z2i6fUq$4{is=x))>t7(F4U!fkX#?!;LwZ}_qWL&%eH0%wsn1$>rdblervqo=+1!-JTEYgK~gwhpieT2B?>R~Tqm3Y%E zT^^V?k#^+I?$hy>f2NOY(&tiMBH8(5C0i&r0t?Nmgb;q%&lEFswQ!_YI^1}I<|jMvZ?+K*qqUOmQ$XRB}C;0F2v?v^iLyl^dj@o=~ZdwfgO z!W54A%p!llnGDJ|xgr1DKR)fBcMlP_Fz9tJM|Syezkk*{KKeABNm}-yNh`t`l8chR8GL+!nFIEs>kYYcWN4D4gc(lL%?dFR$?2TX_ z*+1CZT0e*%U>z~b$shK2ULL)Axw*f-w!67;w7gCotOf=}>^I!ia470xT0uN($4Pe;Sa`+1K1o3wfiYg%>;~ zxFRfE7{M~dtc084NlA;5x#&(Ne078@#x{Rt+6Bzu*M8>YEQw_vYN{4lF=QciMZ$Qj zrLLg1sc1LNG#lTkI6MC78j%;zSendHTJ)Kw61 zm5nMT&qlp#kwdNi8Q6)+k%$zmY4{*^ zWQ*2&t_MTEBD^bL=3wU)pS;QylTuL3p!ct?!Wlp5lgD)g7l0~u6a`TY9ef-Qkhnxt z7Hqukgqbk$=#S|PzxvhhNSO(B=*Gn^ZC{*TU<;o2&qw2Hc{5wEkB6XYvHu9^E)-OM z$0Z+3&b&G)p;{cdm_G;qJCxiD*>ng`a3i9M?0yys2(~xaL&+8zgYh8sJzoHu;TWO4 zw7SEdJ2Jb`(YexARZ{@@y*Vz?5Cjn&mc{Bfk4;E1Vs8W`oU|ZA%g8wA7=7UMcnla} z@b|I78gTlDkQdeczrI+|NJ83Jq;Ppz02p6`sErzkp4}@vFQL0*>*U1Z7`y!X3~)~ zunbGk1Q``ej(gPWAHzV~2~&gyLexY_E|tOS+bn9gTMZA( zJC9Q#YV(;L9c^sw9UVbd=oocsmEc&-hZ4&nnR%d1rNYc^u#5T#tn4Dt_in7 z{vdWnnJztrO6@WLj9@WFr-@d`VC~ z|DD(aicbP2xI43!7oXVb*Pnf^W&{gi@3ck4toux%aiHM`ecccm2XfQn{dnjkR@7+| zPKikSY~{aJeuHIbe~C?;VkJ9Vw;vzAKAe1d$a8h8{rU~pja^`Nj>WS6-&y}BssDFo zdh_~mm8C@a0sC^knXbfr;h&LB$U?YF&lbk%yu}-559g88oZ!_^tsAd~19J44?JK9) zw3B@h$EheK(}9Sc*kKx-2rV*-Kh6cq{GbA0R%ioqX>E5!yj#H^fc@tlSrOAva8a;s z#q^UB?^;Qm8#SD<%dJ}3C=iRiK6Q%;EdJuil>$I{jLeh5BaSxr_ICD;Ha2$;zFl2d zI-k4)>f0~6KMu~X&Z#jUzo(@dXXXBn$NfGZzYMBW*$J+`9c&)G*xG-wcCeoG_6lBz z@yXffV+%Zf}^xPGv=eYF1V+Fpb#>ag&th~=ww?53~Q$>{$j@7){X zI*z>I|Me+GGR_PZgCslIO;}qBM#yHP3k%4*2_y52U_cXdTLxq+vcCKK`_-jyr_ULX zoP91YPBb&;bXQkbS5;S6S65%1cHsAN=gA)VmFS?#Aw4dG`U2eW%R%ChqvKobhUIm5JNd;;6z-+|+%JP_joo-7O? z-dU;PLpanC9YFkmSl)LnOd03%d{$6Wkim3XNa(|dVoFu%lki7mZOs4KIW128q}4Je zeM=`UJj=6KzC4p|(tFOP)m7xWD9rS+G14G)i>i6yZG?3oMQn_tTS97uOs4=`0!j>V zw1ilFXszV&W8Jf|+4)B9U*~!aG~<{B%?V|e{m%%Mf86=_b0Gp&Q4*!GM zSqaeW;@4M)hr*Q7)+yHAj8O>h8C-&ybBYJ|{|(-|-q$ei1r7eXdzVaj3ky4FJ`|Z< zy~fMX+v!s80VgtDSio)+?$g2$-34z=43QkW+bOmphh+8asnjy`m$|qE4(xX90)z3B zCLdw)3~yJA0Aws*OS6f-EzK0L%UGBeeVEYgg+My=Icz(iP0fc(BglXR`~w&_EL!r$G+tHJL5;h)CC!&mQr z|6%bSeZ$@kMpGs`A)npDEzBfOcOSgsh>;Qt$Gy*cusqRIGlbque!()=>hFFjC6QT_ zh426J_>VAPzm`Ql1C@T)!=oopp3nx@*&l#hxoT7QWx?SOjffJ2B8Mo@{t*0w1!aXI z1Dv_R60HCI7o@Yo147sa5`Dz|pqFdg!!N&GH}{k`V`Zi7VuW1o$V^J;WoN2DcS=dh zGD4~TO2TF&m`em8VFL=oFP>jvuMbI4t_Aw9k_SR;$op^zM#d1oH#D(I2ye~4lsnb4c?K*&_7yCRjU-ae+^G>V*` z6&E4LwQhvwL;@+x!mX1~X}|0*KB5v$x9@PuV|spd%$-O~7{1kIcFDry^(PzHox@=E z2ag8&Yw;H61^f*jeSLa$@_KUd5I=^G@cWzI?t|fn zMHJYbtiS5*-W|Sr#20vy!s6O$GCn0R&M3WKHSIY*#r2gIg@i+wu_Q7%4t9UMLtXXi z3l!y--md(9eRz6tIlA}CpI-H-$ySF?Oqul-zd-F2toV@xYHzg5{;lwz-*Lu8Pi9}p zlcoF-fb`H2c|CfSfjb{hg*3oYEB{XMR#+xd=rRSjGL62nulSp)!EcDYq@!p*xjy zRx4tUv+YpoL+&uC@7R8T`7qok!G+1_R+&}2w1_ir5RmyrA&_I?KA<46te@24WcN&W zJU{z!^nS4X#p3A0!e=w8eg{`>Z?qoO>hECPM-q#Fw|8=dJC3oVfT%PjSa0zD!(z6> zX>KUTz7F354iM?Bbk#zwxYY}cY{RStS*1c0>UtAgwe&j~PA+wH3DWyvgNW zA1_y84a^G)4uM#5ypo0=q{`x4%5j48aLop!vacFYD%%y+Si(Zg80<;7J$dsWC~rYW zItHKz%=1h9U{rI6p)ryPXQ+pqPFJeGCs;*fdv!ULqS=Vet=R+?v{m1g<~6m1t6!}O z{GI`azf9gait1r@L?uQPT{cwBSBF{YX!46N9fT85`{_qZSAdOlguWxR(ttRS%IisMICy<$5b5Hy|1fnL z_=X)CP%RNi0Cn~g^R^iB^417|F3H{^WXHIbea{QRovtX!5diysLkbj)2qgq9G$)k0 zn4BMv_a})F|GvqHTz9AKWBo0xIl*F^MgZB9oCqdK>62UiixS(3tpWy2#D0T5iBv=@ zy}_i}n5WB}0w5R+3granEgU`UMnIEc{fFnVEqw-E0}O;Dv=l2b61n;}IUH@@UH;+` z`C{~e9k=PgM_hPaI2{9Bqxawz6mBY$wlKi@XSvVIO2G8cDEl+@;12pQQA;%$ALv9` z`Q0F*4XB&U;vu!4W?iEx?I?OS>bR?oOv3d{1W8!~hCe&@_Wy{wbj zqD-Zbh{Gu-tx!AbIO#|#H`fs>E7O@j-BjI{*JM@0IMq&rTiQIrE`P+v64X4uy?OKY z#J(b#l6}K%*!FGom|wuLUibcR;XZ3Vu+J~;^DF!Oy?&C%lkv$<#`XU&gf2{8gR3w@ zUD?SgQ<6~n32C@V``Wdi(k|&I)Y>Tq?c^L;f`qPvg)xJX{l{yu6sVh;b}%$VT%--; zz?7;}Tyc#r<8vN-z&HuVVx`uwShlZTKWE$k4nV@5oI;gj*P3=MLw%Z{>R4zDZHWju zC2vp5bv9XV0Nq@ZYrD6x7e#B+(GwG)*$~QyR-n&7jHF9ir%KKc42&9CQ_UudzT~a6 z_F~hv$D19XynqwLs8`zeEp!_8huG<|i<}cQ?WdJSN8JmmVE#y6O8Q)BNHURKvl%Jd zXyjk!H!|D5n4VwGuGx&0ZZuT(ra_Njf*S)TJuep&I)wR-6}V9;jKXpy}h z{`9Ch$f5cIpp$V%;q`NWW+V-6Uu;l0}A2WV7U4EOS zf~l#{dGaerwlxmfWh~0EDB0zxhQ3tdsQo7c!Ps`_z|;k&(WSJ(csvxR?an_c0_1IH#ZmHNTXS~kEE6d z2&V)eDq={FsX`*=q3oXA{`21>ov zXVMMuuCPdzFoLdmGGRqP>JK=sTC6F8!5&5>Bw3=|Gq&19SL@55FyK{T={r#-_K>Ln zMDIyH0YMMz5cwnzXw6h+jAsu^I-NeF+J^EgMp(^!OeU#Dqtp&th zV~3fb(OvGB2^sB}q~MP8aw$+t6q{FcSt&{i-6{aX;K}f`D{LQzR&6W|=&WFKAek!k zwBTEK5kA9mBzAeZ58_~rgDK&+cO7uHGrxf;y()i1Y``s8cPFj`AF#}Okf7f_VkygX z4p?4L$k|1`A2Bf{2rPr11@+V~o^>i3AnrfC9%(+tiPJ{CC}*F}Z7G534jiB5Zs5SV z9!V0~E4OgOs_u{$eI2ZfTwS}Oc8*IXN^uQxGE&!xmk`w!{3cXt`NU4;Qd&9nCB?Qa zs-Z(N^OlQ?o13w!3`~E8T?hxA?L_>)V}1Sq!}v1mzixe*8IUk;u<-t$SlaB0@%KtY z%iU(-GOK+xWL2Vtqe0}QRKgvNBt&kHX4?kv2-1;V*(wrs1sNA@s*;~rF7LSm;y}$_ z)g!O2K4jw80ToRpmc||;qi{^Nx%R1mgB_%sdusH;(ydrHZpXex!Z|UjY{tYyY#FMg zT{wX6zmg0B^3&~7kU~POLhVHa+W12)Db_}K59n$Lsa6j2#2RBRN?WA=p1Y5^eIFpf zZV-CO73@y4rQQ&6MA(wS=}sg413#I>N2%r0(qwycz-8$X{%JNVpsvw&x%A=06s=qD zgd-M+BQ}TFh_-c{l@vaS@u#?012*CGVD;kYihB<6oXZ_r8A+hcN^caJ&^9ds)&g*s zdz%;)I4J1Ky}0aQktV0JaVqKvX>P7wFXL!NIIbSQ0Ld$yW5?EsWrpQ;YeN7&`%zZs zYLkDop>C8{p0W|oE%V%v_Lvazp`v6vJXq0~DH7i3Z>|9HdAnfKxY3y|;uCMZ36L8Q z3Km>yEhqX|yTQ$txNtJ_DQrYBAqQh5)VL8Fqu8OTTN<_mxWPiwxhyOnuTCX!K^J^Y z)d>-@`w>PD;$=<>VJTN|VH)|gh4*~Q zWvwfBrt4)T8W6g{r=s?rgw z6x4ku#=j}TrT7opPr!u;TDP()vv>@Pg1HXKxf^14oQO__`^7O=Z7qPNs zLE^NGV;w>a9%I5t*PME=|0c(ilO(n$%{ASMs??D~|F*E^iJ46ICD`Kl&T_I{>)ujy zbP=v$s&1=!oz6;MmV2?kp|&Y|G%VQ3E=4&FTmxI#-mpM1pB0)~TnH@0MFQO)#9=ui~GTAk@fOytel^gP0r^p zbYF9&`@HZIIU&rXV3S`a`&XA-^ukD6>19Ab)$BL$i!C81-1^q@)u+EfdpRg?HGi75 zUERw4q%Kz>IwsYZ8A4m@YcDlxMY6a2NeERTIzrW#8A9K#KHlSb;oo3%a!`&!{$k0 z;1ya~skg&icF>dpO$VdIFGT^EPg*3U=K zlFaZNna{=~9rCXjGTEDh$=mGr#o3vk782AjbhgWqfP?w^8Zii$@3N{$nzxPfI@ZdT zqIzu|W196f^KF z5Qu7gAI1qUZCI28?V#7TkK@1_hiqA0P@N#vYD@qzy&~ShnvVBeJjJn|m6e5^zi#4? zE6sU1Jq1-9qx}OB-uEG?Ji2kUe>lBhe4rHmAXzuGHF**nw~v595UNc|wP?5k< zA?E;B6vV2IVddmqpd1u(y{^%(QrCu1rigmoNM|xhuA2gBJL+$y;#V2pYnrnASB zx0T8m%#NmA@+uXhwALHcvyLjEmFkJ~w$|S?QLa>vQr=p7OHXoTsc4O%qc&)*4Qh3C z9#r$UPCenA2HRm>yjAE_NE=TQHhH0|7?3o1g7b*(p@^EwO#Sa^g*Ry9^>zhwk z*VYm2Pw$GlVzBmRtrj-|Th+T#_$@lhaW?PxaM@IerB+}GnKiUGYvHJ@B`)-q#QA_9 zg01R9skSXi?j@0b0Ohv76*HrZ681-jE@Eg;oZyb2$^t-$Z@cCA7ia7FkUBa#;E2KMsecn(1omXwLkyaRNzY#YeOI4E=t){4ct0L^DB!s%oxD=?3xVzDlK5(a5l|0#nKT2i(O z5=@a7yDlrW)~Ysxkz&Ei$Sz<-DLkTD?Zg{JZ!m_=#%gFE=C zR@t+?w~BP5xsC?V?Pl?|ez3^7UsfBO16Rfc6vJI3HLd$VzD+rf0?}d`Rw9g9-vsP z4bWpGyyU@XIIZU(m6dD*_x;%klAhp}wr&}U#o7RE+qI^10L5Z$fWWVBkZ*ntoKUI_ z%=XnemFOHWp;Q}~C&-J;1OnaU38mV=JU_d{WgYus+RmSU8>QR8u3jLn@a;ssrk&!Q zm7D_(`z4g}xkI3h$I?}>bgRj((6^Y^a0gTeAsa7Ib{5}IjX)q@q zR-NS<)65=n*22}ranfRC-NgAYl274|w^wGP z*V`DhfgYJeaJPxOSjg{&?V=6*KVFx&iEO5rNsw3jmL^ozI^);J6Q+ZkX`Qewm4kpd zdd?hP5)>RcTwT@H^W*(}WQ!O6la$rbI|#npI6aNX4Hj{uhhBL@VB(lL1U_;Z7uWKM z8-^|GjT`V~$@zEz4is!W;~61);QpBglOafW{p9@e9o+y<8Th$CBefyf6{jsNh2&C1`@kuhqK{B)aPBt{}AhO5bx}A&Y@TcD`!m!D<4W9 zGYJYlPx1yY?*tMWfOLzve9wC;If*I8$cAD3xg@W@oATUD`+MhsdLfX*DTCBc!gKDQ zQgR1^aHy0RiX{nmb-GXE4Zikd)Ro2k2uT`7?R}S%QfXG8DEm;uNX`pWf|YA zSIcIZa?V&lDg~K90X)|U0Yitglqd{;xw5kTmyON6$1ira+4nGT4t4ktJA+f$eV;tt zyM&M8)vDnu&d^~+1j8VUZgIPz41H)Rmh3x*K$N0rb%F_q$=j!AXXiyN2AIm#y<`*+ ziGS%#F){Qx6(keEfnB&8I+wmYmSzYksWDdjK2fb4lf29sHoxQ=O5s`zx>^rXsv`xn z6Wx$O5OR(kHW7glwyf|kjalQ>%Oe0OgP5N-})n~lxp+BUR zmjbnR*+n_Zhq#=}IL8Ojf@gP?-E+$RZy~t^zkGE%{ckl=61;~Gu`zIU zF#-SK2ng<7Ue{~DlNKlzn#V*Uh0buzn9RiS$->ZQw~9%3G_EXTiRbhz z8wWbrF_707`XJLAHP*}dR#4OR>Xol9se zP{OZuPt>Mbm8EaJ&F`uBjClv&c>l7et5_cHFrIoM_N5XkS(wI2P5~sZOw)n2K=)HS z%ux2V;`1agCJV9H0w;#}E6kTgJCXJ(`A+4=Y7iU<#9G}|=qj|AC1%oTzif$)^mFYV zClzXJO(Sd%!62uTBW!=;3+OsMf2z4NnenoM z1X*K&1iF)3T)*Cwh_e$i3QquZG{I&QIB3(P2t$8`X(zSKJ9?tsFFN zRm>QSLVjctK_cYOAGW##g0i$|`F*h{@H z(XJf#$GubEEc|*;u6s)*lWPy*9c%?kPL`n9d^Y=pojzeHop9Xax}NR`g$=fr>8PRE zTPBrqFKx>u3A(Czo-@ugkOmW$asq$`cdvaWMOgD&QEfw2=+}>M{fWyuKWTz#vSaV} zs4{C?4JA;yTQ-+A*Ch@$%6vMAPNnG2Y56tGEsk0IigCHoYg$JPiwUgKA zOWJN*e^vu--Y4DFq+IJ?X+kwAVN+n7UF%#LX3F`nbF3nKs0H-}i&-%{$3a`9p&8BY zO4b`rvJf6&8aXmfHG=X?jNkiGbiDjVG7MGnxNHG&Vnb)4I5W3(K&#*9ZNlh8F^&Hd z?PTBPDfYHIrBU__d*6SzxAEQc7hCIlxEEmkzc6<_kXwOqlVp7L-P0G};?0+!9sit^Plz=Quq2 zus0&$m6gA3Y$9?69;o!;&gzyvTt50o{rT&EeA#%xVQgYRpEEAoo;_(`f<_DY-~G*+ zEx>>Kw=X5iNVIY>^5#l&8|F9i6Hx@z#v<*a>(!pIs}*m(Ir1pRSEmpQ-n{F+J@BsX zY={qzHYhc`ITo~(>^VYSbgk^qd>@E6$j5dy8K(|25-Su`-nB}a>K-DXz|dlB9ZQO3 zJhr4~j48xxZY&9B(6i#$FWC+uSI@XBO;`(U8gIq6AorY>NHM}u4oyNuB^J!d3I%S) zI)_YWtS90d;=R)f=*8(VlD^e}sTWDwV|k5q zoUEj%@S9Pav(RnBXn%alRR$#hOIHfzywSSWF5((;iJzXqVQ8?qiP*k*c&pc7N70Mu zE8=LckDhRS8!yRatI89pSr{G3Jp}*nzd_XB%TrjKk_8Uwhpy=A%(u@hcMwM2cTI2I zSv$KrKF}^h8j-!sRxMOBDu`PPm?%y6M6xjK4jx`=IKCe~Ji~3FQBB5zD=TYT>#IBK zC@(gt)WPygl0aa3_{|h07{{FDs(L-joO6)20_%tIte98m!e%Mq=1n}t?mZ&Rv0vy9 zpWut^P+=zvcRtWkgDVYv4)NteIFuq(?Hd5AZ`VW6qxz)VioQO&5+&+3gX5E5fX%r@ zy^%ikYJCwmA)_LSQ!*jOUq&H~%a2X4S}^0i9DAr#U=M(5XkA*NEU=|AcicC>08bp!GT^c8#T44Mt8jRqljw= z(~V4ga7PwU8xC~78^y1|pfB`xEHU!?pyMOuyIZ#^JBjAoOclC43N4|t__J|QD+R`q z22xE9p2Cpj9%#CXu*^=f2GBiMr2GoUGUYMuN0xiNc{g%j!_4J5!4)}EVD-aIBYB`Y zA$p+>ke-+a+d_RTMwsTU6Qq^tfM{8L2=GD=%XI>@G93UzN+1Fh5)F|nY7S5(C3Ysx zE|6sM?d16EoM-kH+ll*l{RcR^G%1Zv!0HM^_R4QJo^I^?m3b_`eYy@Mao>Gm@!i%M zp8uiw{M+@NRlIpnsj~WX^Lu$$sRm?F>r2#Xu$g6;!x3YYP+q#l+d&|*LKF3ZMa&dy zjKWp~iUf@2Pp|?& zdLwH8{5jY~kAEXmVhREA09g)%KpUH40v&@M%eG%Qi6^`Hv30;P?98*pl6Xk-00-`W z=GMGa#-y*hbjsV%hj5pw_odX{m=D;i%BfL;>8Q0CC{n2#5$=G%cCqK6bWVB_ zKvoJBh)~`0BQs7$X}812AgB6*98IyY)KgHtMiK?rS8s z*K42^bjuie5Wj|*hPNE}djb~HE0N<1naGo$yqU&EFg_*q6SL##+0k@AtRx5h)!w;A z;$YQz+B>5o;WZYG2M0(!j141*Y zNQ3I7MsR3NO@L8oz!Sxa(-fLfC1HXREIVzb+FO{;+ytp~xFARx#OAdk!P?sZn3o76 zzM8bC7rfUu%Q!pXN%8S893v3WrMkj|3zu?xxV%t?I-hhPW7-0aU(=qfCRuq!5u|nW z_=u+xB_66`j3bli>;@R;TC{Foo4|rNhQbsOBG69F`f2>A7gJJK2?xrm#twFWsTM*B zAC`EQKfKBZCmNk$7DLG=a5HLCUU~tV^c3DBPE?|{aq4M`ZO`#L+bP>r$_6#FvhsLi z8;KWTh)Cwm)D_Aq$|t&>3j0dD{U3hEX-e5$uBYbt}CZYRkj^(DHqEeBKd z#DQm$?7ump;f8&CY=vV5m9FE!Xu3d``O!(BirudSNW0I@PrDZs5IS8wwr4*GyX4(Xk6GZhLV8E+(d^T8{ba#ejI#ur^=EGpk$;V$rJDr*Er;!Z(30#3{HuS7_9JP(Z>RZ$fY21_?jpCLh?9QXT|Gq*J@*KQ`k=qEkOGO$)yOkzn#BSsokhBR1 zh_cl0?vY)CbDmFO4Y=dNT$ukBA6%UuKsP22_TP+8k0!DvDc#eqk9@x@u>{oVMj6*{ z)$xUG@lu*Ynics>0O$h(aExFjmJ=`-cTLfQ-_#Pspo(quVowOsEL8Pm4UKX`-*M}o z^oX!kv4$>kX1#4;F7`0>iwOw!L}uU!opKqCatBZE=?ps)@EaiymE?@Y!RwQ4eU#Tp zUWftD-)SY5cD!-@Zh-m<3177+*9h{R32>mHVi`|!BWnk>k&RS24*zt#u@Gm@l>RFX zUM1qocSaYZ(>tUo4iq>B@pPoVrd;Tl@%k)Bs>mmXb%p`E-O!Ddg{c!^TLEa9d;;r(Hq=_p`WeObchTlHOpP;GCBQ$b4XT z`^G)an1HNjp$J@9GkE?pj@X6EjYnh-+ACtPVkZ-nq^loK55J;YiJSM2G%GtOOVv&+ zsa#8EwlOFv&8w+|`=iCneIV{w;Vo`kn_h^b{28~xGPYkwk+BX3`d*JF$Qa2xF2lk3 zV)Y~spsT`=@6it;pyOz0B(`9Z^%}Uaty&EXV2JmITsgW0Oc6WN{Mu2Wqqf8V_FjJn zN1G(TLCWSrL;^&Jkmm@)Ci*aUWZ1ClWfw^kKA$1EPx+2WVb9LV5J)oMbWT+6LTlr1QQHM*rK#@!V6dm%r%OUmX2_TCwU9Em?WIc z9*#A)V)hI|?g=Hc=Wi@CWcE;tM&DxIrmZJhFoHSDREjNc$DY&=?`<3si#V9V;H7O# z)hfNlM5I3xODrPkFJ+Jcu40ZkG}g}-83xAdHfsZy;L2f+(C9A1uLPfT1kt;!m_x5% zmceeKmqK+YRUAFrtkKKZCc!T<5kJG>@^AQjML9cflaFmiN}CX~w-svPFEZp!4!=FVIzh}48p&c*%xW7YHx3qoDPF=p1Fy6cYvZ|= z; zdcsgX1i=#2RMXQB8Qg{vFiznr(d;gwoF#yf4?J@VRoJ<193;CAx`RV6gcN`~+u_Cm%}thdXSl3MDVO9AUP`r{Dhr+8>o?+^>OCb*=AXkuBnrXY2cbRR|* ziVD-l2&rsjj2mdC)dE3cC5}W(9cGxu01y#+y~W327rT|T&_0cTZBjfi z?5y}^&6(~0t7lN&giUYjKm{_2BXVT;o49Z-}TVLSBtnnl*os+C1@wVX;0OcGzdp!HK->()jjI9a7Ug7nEo{5oeL=- z=zxYO<%nYfkGDb)M+0I5AqQei7w8=m8J*!1sR`=VB)Q!q88!N$vihR;v(m`dE2p&v zj}gMYxAkQ0tFONLlXRod2aWp*l6GvjcKBLuJ&+bXAS%pB?vcYJEk{8Z&83qO{BfL@ z@xhLB9!oV*8i~p@@ujZZNy0+01><-CZ+pDQvV%`+6RKU+URY~=hfk=sT|)i>D3I6ll{_m|RVNiVYJ zelc!Z))CDL8fqcHI`QVK$*p)Mk9A4Vcq|l=n`^CSNtHy$H|in&17X^0VNSQ&N}O_S znMPduUU72vFhU7@l3uBck_jd{8pz;}F!;+e%pP`qZUll76dMT^v*|=>f){Weh3IX4 z2uZU-N}-~VK8%GTy-+Z@s5d~#QB5%qMM~NcQu$0&2&h?-5=!N9^!BFVNIrAMw;eEa z9aX9B+|%l|*5zZRrZZS;%{W4p+L5k~b*<}7OA_jXYFYFoPtO?@N^(+tCqdAy!Ehe3 z)Te6VL1C;`5Dz^$?IFW1a6FtIU0opL2DeJ#70=o7{)Q$xp(Ss44dn}sn@1}p zFQt<}h7nCPrdxy>q!?o*lK{d)+2SQ@QP7btKyx!fpp$yYqL9I)K$$AY@D){Sbg=h% zV``)PyC$HJ026WfCFLipd_o##)}MkxFDt9ObC~i`Rcg@EfyF zQ_nb9nu5VRb+8}r4zLB2wt;|ExD0eP6b#-TZhXH)YV!RF~fiO@V!;~ z5Ymsy=#a~2cofP(kEqXu({;m%Y9ohiw70CWM3MDh;c15LElZ>Zt?IxMO1h4AVp?;g zro19KT^ZZE)^XlcR6~kTVhe#oNC zpd}s3Si_121kf30A@8^}D`!#RBnWt*hJnU6Z!ivJ(w@U<5-?T^gtyyG473okAg(%H z8cVkQv|Cki3KE}lY??^@Ix~9z<>^nS$cG<}IdMkEv`s%B4v6AnBuwWBRAkcjJ=|1| zAb6KR%u)OwdnKx;K*Y#l0YulrWzz&7Gq$m`bqBMd;bl-AHEZAE?duq1y9c`Fb0}(~ zLYwUN?NL@3?4#LvoFya!!4Ij1#|e@3C#x@??(BWLwfcPR`@Pkt8>`#Gt4SlO#_WMk z4aqfbPSAh^Xhy2WtOK76%4M4QbW}KFqw3O)d{5~SE-&$Eh6!ppn0yUs$apqY1h`oP z$I75qJ+jl=qN+v5VZ5ZPkn%0;mE$Q~fqK|Z7_S(0HRp75M@QSlPg|CAmD~^toy;nT zjH)LCc*a7yVb^e+IePv&RZ`L(GB;cx0a976tr>0;>U|MV%L2d$1qNjR~dyo4F@LwlqTQ$)y(y#gcoa+uI*9UWSN5B&;$@$Z$((?DsBwQMsbs_6l!Z!Ep;1}nxgXpzHODrPkbdCRMj`ho|3TZbu^`~ z!*1Pim5J;{vz|8BBU{YgNTMU0RSkFLkY(PF?oiG~cUF4K%gal>(H$U`{0hj}vkyz7 zQ!Mjz&BlaC&SIuCZdK+51nTQX_A6K-!aNzbZO3zL0h(8E5~5b64)^jZRfM$l8gVE z>HLLKjAHQ#%tl~ctafMl)}5QO8yQk}KtGB9j6OB)dkm~)_6ekSqS*XXa30HNeCNfh zT*uO_Jm)nNev+sx7pcno$ZwfAu$gQtx!`VxBFT#k1*~&`n8rE&chs$P4AxFZZN9~<##N?;21pGv&#wZ{Wvd*uB=VTROd(|stK^XX!OcOg@Y(Vl>KF0SQGziZc{zcYGCbRU1f8(jt z{>z;;oVYG@MY{LREmg_y7qS(Gq!MM0T_`5@I{LtB3Bm7D_kq%^WKIO2Jp>x}Z>)zMtP}-^T(i zDoBq`VAGa+qsXgtdMO%+7s~Js5D7O4{R+@_wJ3ES?38u*a!&WD=ZQ-~byh3Iqi;zZ|b;S?b}`4Q>wVjo+Rj zvy1ouc-RCv29|p3doQ=ww~+r}b8CGKK^>1pk@-l-if7vnl{9-Bm0rp6nL{f~16T*5 z;mA#SDN)zyI^%KN+G(p3c&9`l1ZYGKC|gg8G@1@EzoFAf>DESJr)wlgY*hD01C*ph zqs_ng0gzhh);2v{rJ_?jQaaf`ceNU8F^s(N6edK0c&yMq=$M(_oL$UlcQZXg+_iW3 zu|T8qt6DdXb1WOWUdoN0Y^a$q9%UVR=5aW7GeJ$Yd3;VQ_OU<%`zLnJA+#6Ewqc)y z(o__vpB+f(;g8T?!nB1%#i<|9{5jn#u_(};%@-O$E(&)0HCA6)(mx8P^e*#IK+9dUHP`nF5(Wc+n-P5U?`*(Fe zchf~DPG1a2(5hi$f|ibqf!VtpwzO2k7>;JT^)?OszhQ`GF32CXC!<>q*(O9|9if0_ z0@uFoh~Y2uI79>U!5H(T2(zy-GFN3<2?I~1L7A|6vfjv-AOeb85X_7c7a8fE7q^Nu zC0k%D8v*>*ou_P8#8ZWYFR^X1p`T+HU1k&w+aJ4<^$nq_TmD7Iq9pWi>^xX(yusO@;NAQ%nhYl13NGS2*Idy`Of(gFSBjMD-elyV$Z$$_whJx{kv&QzNiFERW3t`!RG3Xs znu}YYbkdmUB-3_mwM9pLLSrgpYeA~QE*Q-Rx7ZqmC}`L5bZJ;i>=332MqwyLG$JDz zAUwcH8DxwTVZpEHa~)GLz;<2g9U-}Xe|ZE&O3`AH$vhlpQPM39n6>Hc7^b^iq0}s9 zSC_ExV%&1e)rn}BN(0~k;k|=Mwm9p)O*gZK52au%hb_{KLsLcaDL@-kNI8cyZo!Iy z*~*T9O_LG9uR3Iv$ud@O`MRd5; z5R;itNv{tGPB%m=T}=yyUZP2)*Wq1{FhFsC2*PWp^o$(rh|qXDZUYwF7><2uxq(wP zZ;ID0f~Gp-RuHSU*@mHwdC-+kGsBESAmf{;)0AW~v_+>0(&cCq&fn=T-QxFL1E|$h zl=5ENMq%+5Y<4)OgoGapF{L3=2NEK6mN0t?IZOoFkg>bw8q7NCdcR6;j(^Ku#^i8( zb$l6)cido4&swzEb5r7$>ihB9{EgzbV~86VR|A+fPNi>V&_;hA8licIkQIgmP-qW7p} zz<`*92_83!=qCmvP448zpOl#oJ%^FohsN8h7e~;?iCuuZFgd*6kq9i1l34j zLLx2@JH?|dpU;+io9avDL9dI;-uSZj=JN7^=9<#xJl2t|qg4 zu!NSe=^l(n>6`6p7QC%PI((>Orz0Xl_3=s%E|ZYpLHioG3*2dcRo*dOWex%i*bM?e zDPx~5VHKxCOBAq9vl@6-6j>AFt$4WcVzluh5TbbL&DMNMMbVJJBE7TY$#|c6#EYU1 z)h0R*SpE(y0w4bM3hV6HQZ_-iC{e}o+4vM!UzH8ZeNdYhXGldqTmNOUkJJPq390M1 zq#@;~kRW80*%$PgGMBrya1E!mCaTI3)tR#qb0+>mQ8Us4VH%n(RhY4B)*qZ{)25bs zI`Z!6Udn@~>G{e6vTa3DJcGVuJU3M=;5BAgYXmavv$A1FJsW7^ z>v!Q)zEQ~!GkH!C7AT(J$R*cXc^7=Sqg$WFm?HWA8I`cksRmBjOJ{KzlLqNUF=b1v zPQ8$D3jMiAubI$`7PaR=)DHW>bafZ!&rq?yw|9Z_1ABW+;umHO8X|c4gRV(nk|j*I zvVwq96TIhR9QQmDv%)EpwU@XSwyv<=If+-%4f=mYxKpr_#K8)e0U8!sF%Q<6(O!Jv zmanbWL8G+qUtrQe3P2+Yn1og4EFv2ainS?#VflO%GaA#i{>$Q{0oS#Qj~2wgE~8#@cNb?W z>E+oAU4T3m-OA#O8};*wHqdz!I{tmho^30uQX~SDTq?q2G~0i$e1G{qq@uxz;f%eI zKwHatughLJrYvGTuyuQ=Q9IkG-e1$eG)iXXTte7J~rtk zanCKV91L!%R@6~4^2g&D(<4Y{)5;Q{db&S3JM5*Ff>0tY7aNgNR#NuGp@s!1)J)XN z`ZStlKkEl)$^^JT^cPUQiEfO`U2Mqj;4UVUCe_3holrT?uH2EehtTI|#hLhlA)uOm zWhMA#!g2ek8>@}bumhWM`!Bho35bRi@$a4-kB_oF8jPb|8IU~=E-mF0KMM`j7XaTB!LJ@tJ~gL9YhFS zN?CXK(7={=`$+Yr?8&L_WfG zv75p--kXQ$JdeNh?651Lcaxsb9hv;YGEs*}^Q92iEf7Fp8IxujQ2#H?qG>@e} zLSjNQuE(Q9i9?xWf6l{1m=;(B>vZ2n6=nM|HkiY-T@gxl*ANDbV3j1s7pX**l(8W+ zpXhF~e!}u)nUcqI-lmY0HBw$};c^Wh5QH$y}**t+3p+9UIkj!GcqT>_Cy1b4gm^6a&$S0SI4Hc!FESOa|$wFEA0N zBX>Gb5-+e+cTN!rBeHtCVsOSf17V4mcFyi#)c*Z<5DrwgIo78tvBoXj_5F{7-5-}m zv(H}LT^u}G`E2*c9=<*5-36Gt`U=0rMkgW$31pV~I=qID+`-^Rmi1FCHOOYz_y7!I z#_KBIs!bwAY!K~{8Uxu;Hfe760XDi4Gzdi+R~TC6OB`L*r>QY4dZMzntTX}uMI+>a zu)ITWWH%$FMxQ##_U!cFbgnyEGavNb;Vs>;s7Sdr0^0r#w2IMdxsRhY_S?n)kCxSV zgxwSOh*9qn5t?9(lir=lFJPykQI$BJ;oZeBdwjlSvCz)yAv21|#1T z2B7RJcqxcW(?W_j{m>pNs?!WlYVS4*U?p>7AxS2b%s}K;g@X>H_cd7Sk@CaY{;Pw)WEG!a<9CnI8XoDEwM}Y!#sbOR%jk6+T3or|4 zAp!?Vn0q01FaaR5b^Kk|LAyWx`PCPX{)_{`zQ5D|6&*-SjC7C}c|Mp|D4~lHGI?u! zxv$qkNZ=7LtEnDvk#h{rIp*wRvemU3aZSVwLZ5WovHA*89uW_c1zic7 z+VM>aEyWhz+(3U{RAW@c_)$6(AQS$JMAQ6&@i+0f)x*$6)6kMuGSa4uKVxFx z=q}$?7tX?id_chR#3r9@WU@icaz+tCg?n|m7mnR&zXwd`GWRC>;GEN4y6t6hX=6V= zmIv~7G0N9|~flyM6mGMJS*EaQZaVg)lgU4iFVcIxrOfNay@iCy_ICdbF~ zQe?tTUV8M|V0Sz`yg&R?m#M;NaK_faRseSug5JeT^I0hp$i$w4>UzS|r4LOkt1td1 zHl@0f#1%U;=H2qmApcaEE6N5d_g(eM_dHMC`N1!vZTw@$0T8CZKi$Ery2`s#;mhaWzIgfk@g5SnZSQQYZam-FMj_l2oz}#gjkTSk%$GpT zP4lp)9Uho5= zrOV6Qr!pHsDG|p-W=wtPp;lXyqscD=&6NTl=4!V(>q- z9;P%@m0xs_d@;2Gf1D5s0k+3ixS@<8yCDDYVFOE8tH>{c?K$xcJi{Il_PHq>cKuaL zHDcz&Ri}ft#QfPX%`Um*GmlHK#KZBwyp38#G1QskfgUx7N3OUgeM6NS5>#$HW6sf1Pp*)Pg&R@qGkI3S5^d?&&DxI7bw7q+L6 zfHlwE<9%hi`BG`k$H1;ZA9Iey*4Fms%E?1B-H3j0+O%ckzl*ha$60vVpd7DBjcA+2JLyoA4fXTo^u^9N1+!O9JQw_qG$* zon(CPaQ?I=yQv_vdYIV@y=$u3&mD_rp|0S(i0-jO?#@Lkl**=eeM)8!1scyt+lz!v zQrpLXakmTV9JE_W9M7Pv=m7BfGIm`#&tk7j4pcMktmJxv{aLkn3@|Ji$u0(7N#!i` zO?6D1aM0A`WIV-XLp?Vi`xj@}+R_b2h04$w@2M$9eX(oUv49@8Mx;xamuqFNNfy#4 zwk>4JtQd$6B@0Qk{6N<=>5^ltDqq#FB$YFiBYmt_#$t$k#zwDX9jcT}`j*M1Uq2BB zec5vmR4241WK3sChfhUL?@73F@8#Ch>-IeD6gyt&wz7itSv$Lj!*9-nyT&Wn-FdR{ zbO&)_KOm{1Tp_alY<1(QC{`oY912KFo!vKF^1hoE)7rSk7)KeI+WtBc5s;@EmCjpA zlon|>IpUyR2fRSN4qPEpH$;OaNY<5`r$OjYZeFBNZl(Yr#)!0|)I&AkS|fb;^ z8f`^cYT5qPKp0SM<*Isd(x6hw1QoAB(iv%`APb)16v)?-gy-Vny{~x@{psZRVVABg zL5c)Nd-*0p=R)?W($$=@+Gwo?Ln36k?snoPCQCLKcT!kj=LEj){1s{H77Z;v&RSK) z9g4Rr*jOYH@VbKaye}OJ){7Jh*5LX2EtJZvM|-MC=UsDBAu6Z%9lG{D8Jbl88IV-} z#UO^NEtxkv&!h~fU(2L)uMmTa)p)e}73(p>W7m6Iw<5dBUXw7SP=d^-n3D#^&Flhq zWvj%n%8{4A>sD1*Et~W(mr5%g27eiJuj;-U*pCxPgB1F1-C?I&rLw5gE}Lb+NXn|T zhvL_O=;qYI)#>XqBzRg2u^Hy)hz|z+Z)n*(xUYX2tPB?#cY}{7v0Q6K3ED@4h3@~9 zAkV6Tg6rb+Cq_7nf~t9|LWxfO6ibPIvY4~(kkHPZ8iSAofExZe`&Kt^#FMBJ|At!%WYz zLBqgf@dDw@Qt#{(am8`dM{t38s-l#d#;Akp5}BJQgJd4K25RvAa2K;&GpG2#L8SXW zd}s}}n9HF-_!_Lg5_cLXUSvOEC`uA##9r8j2uC{;D(Cfl3ra!@VmVFv;Y0z=Zm{_j za90MajboDzV&N29Pf(#ZBM- z(z!^d;X?_aV8C=k^9iWio3`f_!w^xhXe0=sZqFu3y1V8ROT&N_`KDzOj z?{K>-eO0&WJgIYsY3xNhryks?ilnH#R=P3uBwJ?G7PKx>m6*P+>qLjmNu(;{EecT~ zW8d$n_w$=~8Rg)|HiU&Ms~()tqg*jUtJf*IZ5#I%&X(L|+CkiAVI{LsiGIxDG)WBq zMFP2OgS`Svfmyj^nn9*I+Dgf)e5fZt{Z+RR+`n z*T)exuQOEEM9Wxs6I?S87d@XsPL-dUdQs^`Boh*#uy#&5E#?#iS$lqf`@Ou+5~3>m zX_Ee_-f{P?ahHj*uq&GD{D+h7c-r`39R3;T1G3rid}l zxnPeiT>SyH6iO`jNBC=M-+4@kkQHGxTToV6HVM6OrWN^58Hb+Z1Z+CL`cG62gVq^2_{OAI)Z~)U&jIlgIxI|TW z9WWRsbdOdsYsmkNk9|%`!XvWm^^m8cqzO7q>C+igzJ%+XxmxSk^e>On%gGK467q@3 zyf(S+>#A6hMk&7hS`zJQRyhnuu-I*9kjm0*4``l;JwXO~#jT`VhB8&(f{ut+nkY?m zi)1jftY!duv)pplyUf7R2%oHfoSf5^Wi>_aQugU7E!4f3;6po6=t6PBmbzs#sIpT_ zr+2e)8{PS2}3L%Jw74*mU*jii&P=xR5)WE4wWn%;> z$b}xyfmyO%0AI=?;p~(j=NIxZzG9Yq-XFrFVEp{jKEJY0U0Ix+{K0%n(CTaEP51`kYnnTY z(|0hT7)l;aNrFlcF^42$gkY;A&Oeirk>rd}pRxA2idf+Dlq(}LX1NHgC2_dI*M;N~ z6M&@e0nz~$J)c~>8K2K!gY((AH)NJUI3W%>K{EXfvN^WlNzd{tIl&sRdC!uXT7769Tb7=o~ zwK$LtEdFYr`k}AnU@q274@RS}hNID!qY+tIlnfZKXduNXX_^*albt?_0RPDYB;v@j zfudEb57^0)0$9rU`KF-#eKDi)^~jWs_{xh*>?VLMaef4!aMm4g4aw8C*(7k=W;+LA z9;FmUsGH0Je%{!8BE=R7dLRui_ZL1~k+$hf3)hz6XDdo5HJ-)*X5kGwT|0Kr0w%f{ zR7kyf)h4m;f zokUplsUvtUKV<#(1vC`R4B32SMy{E_b9!>^;a4w1fYv3t^9W-hay8#VfZyiS2?ff* zuSFQQ5gv8BLXJZlb&0v|;NRZIo;>bzZo}d?8Fw)tu_BNdeB}D&DH+VM@IK?f3{wgJ z(-|W=ssi0yze6o>jnc&{rJC_m9WGyO_FiqWOay$C-{&u-9hF5VYi7H@%d5dc-J_$xgIw zX6yav6H%B48$HXXn5cQuzL`~#9C8#e?XzCDeroHN>@m4b!_n3iuAQZB*cuYV?g-z6 z^|~gwI`0YFn0zM@^j!jPrQ68+an=PrTh|OejFOO8ig99_I~^q5l)<(wEu1aie*=A! ztO6Ov4-=~|;ruuchV&4^lTt02`pPD8YrL$;#8bD2;4hauwVG@?+A(a!`YeC~$5>aANFT$ol}B2WvnWwZv%Wn!K9I42 zpXze7iL5eMv4-sY7)kxpR28K9M=|U9tiQ%C(wdSjh^I>AU=#nmAm?q=WnMMmsjIsqWYf~(@Qi6IQ~~U z6Vqf9MTYUE9PhPN<*(Pw<^uCpdibAn1^yGSj1r z45?^XGDFLLV9n`hr-fDElEfM|{3t`MFZ8lh90ffkzq-t-Hsm(qr8DfJoto@Qv>8Q#hcJ(l?3V%-Y-mXwSxfiZBzts3)W#Ow97_Zdr_^A9 z%A-A~4QQ~*KGp#|Q|;Z$B0-#7n603kXgZ3C>E*2zHIF!$MqPctVdK1Ni!Of`vTQc; zKLk7+ZnbFIPPKrr%TF`xH^-hC#Kf|o+z1OyPL&0UPSLZy^WTBVvZUs&ZY827sC2!q>cDTSR9_#bXA{E%;9_uP#m$=j{Te^ahy3`AuU zAyPb&s2B#{C=*4`JNi&T6k`INqa_rW9GG>%((;6`(_u5eTY`?S!EKx!p;$@uia8_r zHfuyh+^(3)-(o6j1qPD3LoaSDj+*y4a36?3dWW1(56)m+r*3Ogg2WI@O6VOp8Ic|c zu_{-{2CcJe3~U(=PtT+%u5e`%A*@3WppdXZM4Jek3#{cc93or5@)Fo`#JlAA9icxM zfq)NId1MXqvnDJYDU=Fc7)jx$L}BG*!=6fZ&xKd}_MQ3FgQ5+3wj6t^P?AG*sEKT3 zFr8z4qgBdb26xGpEWgG@F*-OFP&7geq@=e|fGkY+N@^MZ%m61?FW+3@hh(>{^6bWL|e`O+KrI6_eXP7rF%v#JD zcE%q#y}j2BV2~brCLd|CUhcW2@2DfS@6nm<@YPe$DPxjC%h`TexI6CVF83gmy*vKe zq~Z?ay$Z2_!_5SuHj_cCLH$|p$uD|UoJPulHv>}#oe>$f(1?ZPx58F;y^!c=Zn!srz8UR6D5q!-egWNy(e%| z;x#5yBaR&fu*nqWPedb<+4X{W9T=1_!{s933LhANdTYtjExub>+4<|{`rg+1f4tn- zT3@==5IESKo^q@AD4Pkw3wU~L*d_gmsf?5=Nw_ZgGVe_?)VPk8Q!5Q(r%5n{n5oaM z*!|5qWlh&RrZ4l((O}Nd%jV7W9b~)cj9OaGZE0Cyx(d5JD>A4?ON>*89Oy@;5WLl!V~M5O>nYMrzZ=St8!&s0W8-}sqBaCK zWlcmhk?j9^_8J3O-B@i3MYhH&5zJR$uYjJ0H$kgc23c`?diFDpS^S2$;RK1C+bD=< zxcS?8xS5QkazP z&&1FbyZewQYnO55rbLZm>gVE3UL)?xr&KkDyO4?^9kKCcAkc06l}wLF^gtxhpK$8f zdQ`RMX87f=jSn$5z=amlHo_9oCsxcx2_;OA1;rG7`>tF>RS{OA8p2PN#!$&wGKc{R z{tVwD<0-_IMMo)<3mO7=Z1ckSd=PeZ2KqV}qamtFY9;ADt=)lKk$!?r(8&dNA26K^ z(c0-x{=;qwVd5ddLQJ0$7;rJiGhM!ui+JbNw_B^v*S<$u;f>X8Do$rZfq)0-r1dAP zOTA+>C^+KCPAC!XdgyEi`;@xCBVWwqWY)n1X~=L#&pz)wKw!LF&;u9N-{D81@j34O zbb9tPBBl^*DL$<&vgFAbl9G*a5d2b>bjiK1xLnR$my-Mpi8$`ve=vMKo=x|MR}67k zJ~{ZzR*dj(V-An))A?YWUuiaLD(qB-$OYYjfWC3A5)JxaMSCn{T)UO*|6ae63WGeJ_q zn4h+sdLzQx2zn75kf<>i)Ud$xTi~M31J`;f;ZL$g1ZXx^3(&N#OzpC$K<#LHqNe|% zD!}^I8n7a<0WW4s!yG97G>CSJq{B!iEB3!phq#4yxpOldpHKZh5}{eKp|i5G`Sj&? z8_)MvH#hv!IZ)e9T)Ve0(%PTUpBGM2ms>cvrCgBr!l)j6gq(+NGq4cU2diU?QSeQt zrOb#VAX)f}niH^bcGCGr-XL=$AOfp|DQKI@-AD$gT9#TaKx4yc zQ@&f1-hv&Uxzm=lmU_Sc{U3Y33;R1OEC0Uz;`yH40l2w^BnukK!$olo|Ov2vvD9t|JzGw303q(x}3aF|;&?+f)C69O}7 z?KcQ~m*j$4+_KGb-H|>B)^Y~h3cEtl!5%)G&Y)?KQxuv&BAS5SGSK9iN+Av zouH&?#LId~=YvAtVQyN72{10ryK`Gh_M(kDJqYi>x5Zs6#w&cT@h8-QRxo3Md z*oZsZ-(wb5y`0yYR*gvUJRjo}>Nm8(kNMT3>(X(sCYPx`G%8tqH;4m(Q z!rn*~WiP&wt<(dUIUIWwQ!G@)3m)Lo7)GLu$a=vPJ(^-45)m**mJZok-I^TM8^wzW zLgj_I$)YJ*kFl4Py#KdbGx&=3-yrfmHq3FCIQBkUUb$s)4l67B2n#%2$3@7IWtarQ z#Dp}*W;$0#yh9yHoKYPsoTJ|G^g(Ypgbf7s_s!=pKKsj?`BwbO%4D`bW;$0z11K&o zhjzga&(F5soc+8#Jvtp@4{JhG%LmQc<;~tOTF;*Um;_9Sc?H)#FHE3^*o z;$Jf%|x2fU>;Z{Xh4YH2~L&cjxTB^%_j|6WCoTh(TL1>FOlanycpqIxN~vVv%e@sB6$8+&ptVT4bSjfzVL!{(bmR z_niikD|PG1+E-tF^{2(4@>%}|m)L;#)vzL)Q#*rzB)(_OmO0O+Jh29F@~6=`ri|oD z)6;`lde<&iqTt0#tgc{W$oYMS#>5A5M)&a)0F$Sw%2Az>y^c~@d5jpv%F`g;m3P`B z3FQfR9Y7^e-(Mun{bu}N?ac&>&#RLLs9L)=L1blBN7z7RZ@A-+vo(() z4B2yusDK`nML!i&LR;i1!4#N86Xvh6#L0?>goenxXg~P3K}FGpfz_mzB<0t^Y%c)76B%h4bN^GStgb^bfR$^ z=LVUFjK^p~z)r$u(#lGs8pqPtMA29ed>iIH&T9q_$mI=05P`i=#!9DAl3}JNAtuHg&$@=OqEUUm27pp}Sm@J#L%B2}O{A{a> ziCXHiTHc`4hB4AVo+51lu07}gvF(0BapUH2Lf_l!6Ma{-un_!f+7W*y6!jl%@Yp3U zle?9Z`8+lgOlE;+xF3uCAf||kGmMcQ4)gu^>n?Mf+~YNeqvg@^BR=#;xMGt(tIrp| z_#o$CdCb(fBH{pB@t%2-At9CVUFRjhzCsoSnvlm8>VkafAd5n24s~Ze49~Zwl^p@s z(g_a!h$H0RJ1_|lo5;+o4SdYFm&KRPz~CD&d4f-Kk`~_SmV^r2CVoxGZT$qMik(PU ziD9%8u7Ozsr-@O7G#3+0he(}wahy;}YvTpcz^gVG5G%!kFxrs7ySDm)8o?l8{GhFS z8u)}l#3~ePgB@@S)mkwOCE8#GJk0{))tr(Bp@E|nAcj_?4Q9a4VYboF-p_>tI_vIY zu4~bbyzyyA942@YRE`d|wNg9{lqsH*d!eATYNwWZY<6iw8fk43%r!VYz-W@lp|nv4 zBgfk4Z^`4ViO2If-0RmH2Z%Xs2O8FSn|OpG*X3;D6e?BtIs-ZfztrT%${h;;oZgWI zK*HSwm=+|WY0l;Z*IDt+>`3 zjJV>X-!;2EEE%_CVW={YtP|@NF8Z0a&>Xu2DNBY&i9rNuii3J{wZIV7)L#prQd$vaZX;y%GEc}moh zpANq=hAH$Wf)yB59Je)bN1&Sf7830I5mEE+9e4$ZO{B`&RgBy@;Usf+MuL$)B1Znb z1ET=3iBVa*iV-LsKv-}L$m@?1yppFjj$Wt(yA*m8!33nksN%SBDjpZ{**N7=KTd*W z`j}{@zrBqLzhx~n5v@U~V){IHzYP`&e_VoT@rKA2k2>%z+i9ZQ0;`JkYIJx3W4OH% zG|lNnXPBTUPO;XRDuvX%zw!Z9e1^bG$XN^OD5SOL+^G(Kal-R%P}#Oua-K zEZAuqxw~%w1($gL5>%Uh!Mtc&8%4bevPFqDKw)u5Lp2y)h)Tbxtwepr8w#f7xXFMh zHtxfUpD1iu$+`DOxNwQNpB}CJ{PWMF<;6!hxBmJHd-RthhGP9XK+FptW5eyu)$7qR zc58o)h4J)wtWOv3ec&BtwxYw-PRwCe;&hwr8k>VrpSK|D%fVQ=@N5kpef`b7Ut!~a z!|Zp5yv_Ul@9%#YJi^n3MMWS`s0A62T%uA)kR02H6;Wp7-*u3dEhzIBm;7t9v)jI1 zu3~G;jA05#u#4?gX63R#_TYce8w@MRwxWD@9H&h<*1YaO7816^!3bb9I1aKj=SwaZ zC}^bqZSn8~zh>NMLEIIJMwh>ovq%gBO`eKJNbI2m*ywk@ml3u$H5NlxDR^azu$Qvd z&Nt-{X{0;Ice+yVrAZ zLz_nU8{@)ofNRp|`=gJ-1khpKhoB~5hEa@E&>;k5^Z@;^H0oqFvc0}Vyr4+>1)_;w zA*83TvAGC-c{4q{WEM1ga(;z#s@U#eb>UCO8iy809QiNoDzHI)_z4@su-Nr<_PZo| zMF=yYO6H%Oy*^}ab=&(5B_JUn5E3Ol-q_xJy873>?H4b%)_9`l$==43y~pc6fD-rR zrrByCynRr<1*jbYz`HLUgHmBZj%hte7>1x&DX=N4@w`}DS(buU@O2ZKmYC+Y<~c_n z;IRJzhDHA>?W08NYfi5kMAC*46;OM9^gr2?=|IA^DB4P}Bh82~b*;D6LEDu&tzAWN z^>fedkrtz%&Ig84a2a)=sKAn`a&FmY>hJ1=N@HPeKY~Tk(WI5>?gI;5uLmrdIsxZ) z;yRsmbD)dNDrAk4twQkxA-Q>-1xVT{f(y75pwL0<9ZYBNHN1OU zldEGilKoaM)z~!zAA6WVq;9QL;w$O`w&@O`(19AT=u+u|1!Ypftp+bT8v?vG4dhGl zq_=HFvn6=|T+@l^sylSNEC{U>C#FIC8m1lEYd&$cLXiRUQn3?dV<;APPaXG4p_ixA z|Gts{j!syORdgH3y}ipb%aqpD1r?TS6wH>%ZH=O{jfJX~Eqh!n06Y7<<2PpLZKNGa zEvy%#T1%FSZCIn0=}X;+no~{`boU%)5IeD`x)F3*$rN+8+NDKyGkXj&rEzszXJ?nI z$?5(OV3$2)R%$z*%nB8QtQ^TSsHaxN4qCn70w}H#&eOdNu7s*FLUNv{vnC0`zY;5Q zVsdIS-*+{zF);(res(~5Ry(TP4zgLi19q4jcn@7`1Pi4w(}ErF zpeV5UPXz3gaeM#sq8%_fl}(T4;OB+f;D|P_ZWAS*EZ;c7d~a?9Xn1Zb8);vjBB|5q4B+NtLG*=sabXE`L7Xj)H=t&uab2 z>dU7)em?Vu^{wrV7tevWCbGGUWMF1A4KZuLftSXjcjr}K|$ytKPdrYE6m*} zW5L}?ft<8EH`wsVL-uADOZ06_D#oPEER$`+qg3fK5vIU3Mv^s%%KvC_vdBWSEd?B*lRw)L+ylAP3cMjVjRk6-Y zHtwhden}{uJ|>mZ-wwTkX9l~8WerLdN6zlB7LB6HjR|&vJ!S5n9(7=3keawC46j+m zV#{|PLx@IX{REr%kd%tQ9Cn3H3FIbbO$b%&R&C8=L^7dE@QPyS`58Jea%fF-3=Hdj zlp>8T|LT$vQv^)$ndIJ1v}$nBKt$!QLdAe{+bX^)^Io_~)QX^W7EVXe3s%s~g`%UN z^J-fWsGQ)Nvr+fO4is>mOsJDu`c@6ri9_nDNrafWH}F4&Mjmw43O24ahIvI!Wp&jF zCT8eYFf?2Hg*t79W(PIgDs$yBcDu2oPHNdj3Ob~&eie$_D(Bj|vwoYYrr4I;hS9Fr zUliRNc#`}~Cz_}p{Y-AjRxH%%6uUY-My4N^+idvI@$4$Xspp?u-QS;_tHZ%)G!9Q3 zFb*GvOh!2c;cnvoG`Bg!3o5d>T`sR>vz*_!*PGqK8*0f+zAMl)eI_?*lMb(}gcg7f zu6A`x{Z;R4DF5&?gM-%0%J2jnai`qUHB@_>f|rnzkSQR*37G&wDv#e}s8tgc$@qi3 zy&L_oKO232^ceSjOmG4>;}WFoe~z7*jTgo9?a2uWJ8$V>Jr0OIH@}QNE^5xdCO}Gj zkIO9{BL$PDjjX|dV&b9tbvaVcv&b3Z0%ZFwE-eC5Z%_GC*_#}!EN)nScLgZU;hu0I zm?A759zyrANd*+1f)LDB;=N^!=4_NYTXj4%z<7Gpo_v3)M$X`i)%Fq#f$Ch${D6E2 zywTkBV){W(44W8_QTNsu89Fb~9L07)#nhHd_2kB)B2jVB;5guxD?L-uY34+|y zoRVx7@gv@{XK={d)KD8{lhDK+DJZ0rsJ;lptk=LQmXxL9deriG+_x?z?Fe>c@M@+ z;24_&Q@(Mju(aOEtKI(LL=t)I*+cRpv-NbB(Y*3mnNYvII+k!am=as6Kq-q*I4na? z)>;GWxNAg7iQQ@d6d9t9=v!7g9NnH>y+;0=Ox6+sUFr>g-^^h$TK?1ffBhggmu8p+ zI%ptg_O3(*;)_Q|NFyi~D-I(M6zt+%0`4#KY;ism5q#*#?duQgygpaRdXv64s#j>> zWqt!%IH-aq?v+VMFSKe%jp)ooy_@8;O)Lx~#KfMb-@)v6a)R_ao=(U|D?ZSU{t)sO zs1z?4@@>yIMOGSbxgUNx#_2w0PYc->BwYEvijz(UHhmO1fNWqxvGK+J zEo6g({(urj1gw>U^hS&jAj(Sj+X|LQhVnvt<6{iCq(ANBUT|yX%UAGZm;B`%h*!-4 zQsW;l5;n30K}bFr+pC#v!-uE5n&No+kIA>fPO;CJ!5i3=C_f;|O}wn7;zdaD7G_^3 zubM)_Df;M3v7@3iCKQs8^Ma|0i-db$EZ@B+qaX@hc|epG>s2}M5!eUJh$QXspU-$I z;Ot_Gw3okLor4!9T{|ZG1|cq3C}4C$^2X4D#KHvnlqC^31#u}4wRWbBgYH5%6?kWCr}+w^E=Go?zjo( zX!$}M0~S`hUlm91kIHGZe$cJPpyhKzIbd%8!2%>ww~gBcD^5d99tkCJG z8_;yZ`W+7odV6x;cr+SiYyiTbNTGaGl2SE@RyZ3fRZjmz%(a1rJ=4m>^rv0dMnL+f z{fjBCARP`Tzl`@UhoWEc>k>jyw+<|2uQeZ)hdof&K{9U)GCn|Ot~bgR@|S=@Uf>bV zx4|jMw62;FShS}NiE~|Z9aXHN=NN?7qA3yzs=lk!&J7(#9Zye!NtCNZwdvZ(`K?XE z+6>59nO{|W-AFM=U~M?_hfG8u3gw5?wN%fMo-NrvMm|aVx^OBk%SI4oaaD7b`R=B| z)!wX6J=!3q!08z{n0FjK7gr>Aw-*k?TWQbTd8v-5fgE;H?LrR}RN(Xs|LBL*kB2=*%Hp6s>a{W;!Y?n$$0o+6(9!)SDRC!PO&k~B@u6zhE+L-OaPqLv)& z#>SOmg{sI)yS6dlW->Iispx_yy1+5XXmdRVc`}9JV}|jZ?Vqfj92{f4gCFjq%q=e} z%+M8#pQ@GH=H1U$qq}x-C2m#IHiojp+JCj{5MC`Ni;V25#taZxt$&9{8kVi)JN?C5 zVn`@l-~<|`o{1l+D#R4%kGI5@N7r+0iCoUOp;XO$>dJ-JEd^f} z1$8CsU~gju=v5QVx!~^~^&a3D-Vg`TfVa^A3LANLnZZ_)JEMUG@d8uO;|kmRCF|&5 zY0GGaUq1x@LpiSAH7tzph4Bsb*pf2jLU^W=fH4Y<>uZj20AR?-XhTDWla07(_T+%R z0gM5<9{gfv{Us^~JlGHG2r#pjh$whdWe~j~S6YVcOxH$Ni6eoUO;1RME@1&r zNI;mldtc*L0g(uye-MtM7`Vtph|;f!*n@_-^sUYB4e|kqia{LQxZEyFYtMtU(j7qz zElaX4FC*g_#SJC3tnJ7D$KJd5wRt4#qW}9-C>}BriIJU5&iwWwiG2vh32R})fSs8M zBy1UEtT7-)0y|Ee_1Vw!)TM84zmmYt{ATZUvi2UMw{O*T>+0(2YG8gr;cKYWQVW>6 z%rsM=94|x4dDA&wpiccvvKDf65Cvn-&#Vt*R3D$O*qX88;sMvT(4^WQ1M&E%ard1r zONrCJwYOAK3x`5ayfl@O`kn4Oea;5|z+1o+)6>g$=LltacXTyj0@QcYtBVUf#QW}Y zbny<(ne$igEb{{XeLXxKU%X@T?0)~#Wlf*06C}QXJTce7=b&W+|GK1Aj$W`qNcd6h z;|6c(XVV6=_k-ORGKg8e9r^%>)}}c9F6M8$P$;HsWl&9!WDgBK%8)qSrh%=`j5)mb z_NRgAVk@xNu4cB#CR^k#mFG=GSXd}IA8Qd|w{+|Zm7|^!6Y?S2oI089;jLK&nr0X3L|8{n^pyi{s6NU_u}O%ZBf@FGl%m^i;Y;+ zj)Qsu$K}R*=9sLo*mz0JI2J!*9LXYqaS(sQrz*OC_zHLX@Ap5stGX;=nkr1cIg|n& zKa182@Z!tz-CqBb<>jyLvadgI6iOYK&mRL|#sV8ORS=@ZhMLNNEsVDId7s{191Zv} z!O-wS3oBNU7k&aTBImJCmICbdY*AZ$74|Ov#CJg&k{j3GSPzq4$fUiYh&EtQ?cw8p zMmUMWvL29Sye+F@M1;)2xjsB?y*Q?dk#sHk9WkJ`%LfQjOM;Yk?AfdMsfhTwc}SAA z)PKMmS$L2df8ow$CBO+wHEhu2NM)z(6AiRv>bMVxn@dT#FWfp=D zE58w9f5B|%>#=?>Z5%>vR5bN^d|F{Y{h_AUyh;EcV@eMA*<+`c*St=3wdefgzcTDm z2BPisy?eh#@Na}=4L`&Py%^vxz$XQVuLq|H4dMdAa_9l|Q+PRjM%ggTbc?#8CSz>CPrd!0zFzGhNBKVgp<)&LueJv;&>Nf_lSH2R$IY zIV*nD6qnUi#Alii?P#Qy%{mMbr`iYGBHBUG>E*yG@FCRWESj*hJuOR3>;@d03d3P5 z1jx+!4h;vL@8^+F@{W@kObqJO4G#apq^z54(?cZ|4oJ-@MXyq$4-Uo3$tFJWGm!(0 z0Zkr*Ct@^xc5yPn1JBH?X9ujuJdXnW@x#-x%U$x3wRBIORg9-6Q0DqnK%-G@n@va0 zVlQq6k!gQaGlQOhk_ai7Kq5$6ZuNgZ7=S?tXUb6#8b!CR;dW$CBf?oPR(uNAZB~uE zXiHo~HoZ;~sG(|nbyeI@$}$-ann)sy)*oJCVmJHuz;85gA`}EyS?vQXe&Jg4+uh-G zd!5Uv) z)xTRRKE$(lUQz!pD#m3#NkFwXylk)Lx-GUW>e@0Nm(PRvCy1;6Wo!Q)vMnFMyN(s! z7j38xyQlD0pos++rdwmDVRe$#C-fle8{(DuU%B?BVZw1D7%9zcf}aQ)ZrDT6s>wG< zG2H{QRH`>CgB-=>RMaF*x}XR}pJv**oN^e^ampcX&I?^rWDPMtUK1nx(EQ@aksi3n z_m+PY-Oux#h`L_)>#ud4h_y|($)So`m`buLB43QokH3K*XWhdbxYkKu92DZMkAm17 ztZTy=O0m%6A{}zG?fI1el=&4+l;gEEgi}Qt{FAhn)ft6EG-xEZ2JrcwFA#QskVtBI z0iijpnrXN2YVl&Q*Ke4f~Gwi(wCqj-e;*(RzE!a@zUR z1gAoildCh>bay%@I6~qo-r2t#Y1QysnKgnbw_YF`n7p{o&K&2b{15Ak3+ya6!#ovjhr6ATt$;?>DA&Q^xpTQ-HS z%C|BlTs#xJqrC{GUng_3*#ZZp8ZK1GjT$-N!7dnO~{+J4^Q__*|`U^3#Qhh1$CyM%%V^!k+~-n;rM1L zgr;z?Ih%sNVXh8gBu65W+PJK|GZ`Ud?eZ#WnKWT*&-AS-A*uOjE| z<_Y(26jBqr_C}br9aN=r9vnnN@O<{;|icUU-t&yCXz zz)C*KIV0as8+ec8_KdvmGbp(YrXX(|9l}2aMH}6wLW;0VQWg4q%aZO_`uuClL{nb( zDin(HztVi6iryX4ZN`K=_)}8xI7Ftrg8>>?NQ@MlRLNQ17$TyA7TAsfgvgbdh?^;P zI8)B2P#kC8*7u>nNNCOF3R0Rp%TbgDIWU8RZ>iI|1%R|w5*s&3!|W>{1zMkgPOGb> zEAoWXrHR~Y(oTgw-^j6|+=E6qiCDDt(!i-5>Gs5 z(Mp){0x>|$4;dD~nS@V?jd=#sh|EVVvNRncUK1WU;~J1s9CE>N(I={mOF2c=ZRS3z zHRgu3^97|}h$HI`-`c%*@bG~|HP;&L!;MXrHqBYBVjIuyJve)Q_1iern&J}=eiDCK zyE0KY@QIXKiT05mM~0(WK}oF<@DoP$T{Dmn#Z9$|gg{18!0ZY}wy0slc8Bh${}+a8 zAeuF|uDd_UF^AQz^G0Hxg5XpoZU&0cDm!`EFp=AB|6(N~?~yBTS(GB2Flt(z1E3qJwwTiFh&>HXb&!_q`*nphF)&2#==Tj?c(d++p`zr^S9X2o?Rfz&iUoRj#fk5 zCEuK8Zkb2W&b;Ak4({(>zCOdHgK~!p9E)4}C5mm*%qF9v26-)e3q^KV8!zO}+})O7 zQ48f=Q*%meqRqrRh7!*)U=@?CZl$Y6_t4Nrw?=Q}0LoEU&yg%4DjPN8IH>u%6NDK= z)plS+L#w1hA}4a1vgK&@l&o&e!ZcvJ4h6y#5WI8T>wfp$_m2-PNfEThgHHM(JSRu#~vWlgRtBJZIY1HVdHZ!$rhI}EOZCv8n1;Xx?LOJpXEULO@&APt3HCy4Smu(l82A(hD`pf@HWAf!-DjPQ}%C=X-U$jt82UG4=g@R~o zi-5DbJ}IlU`rXx4EF0J{K&!EFcNKQ8h-Gz1$mVs1^nQES7_&0VN62%{!1&(Mi*xD| zn~<9|eq)}c?Vyj&Uyom5D{wS8#gitdaB+#p0O5(|?IFs{w~!3N>W5fkCWjpEP%9*z z#O87A$`sZ~6AaFO(%$(yz%QlnR}4<4!`NGc?t<0H)e))A_VZ>!*|wOoFxPZfKsBO0 z3HIQgd%+hf`5C_D7j*Fg`PmG>YjI_a2NZh7n zd0bI&C?@|g&=M;Iy3Xpzmkk=t+I50zD2ms*E$k)b3-!B+n4Yu|SWxCImO&F!VPa_; zSQX>vhmf+#rRYK;S=z7MXUGu0IZjcJSUAFKgy;NTvzO-R4+yWYhQAhGs1P$@T!46l zJsoZ%N$hlrCsD@whhg(h=a0OdF}l!GW&IPAHrI`AI#c`o?oW8d$54HLA5Zx7asG=> z7rbYBANN;(`SI6#cRv4Z`2jvouU@xQ zJh3%;{*Q~{Ne`|<`NKe=SW&BFA*4e0v%x>cll$0Jlj87d;fGKh2meC0ydNx9hQ}(| zOwW6r{jT)h)j3@kX9A4iY=L?IfGq;WGNAsK{6jke-VO(x|F2*CMhxIO>4+*WM?%rr zb+N!9ZiobjC&dE{3){SuEHIZLg_RQ>3+Y1mIJ7@m7`N3sy_N9<#x>ae)6bR>rXkDc z;q>ZE!&MH?1{b|9s=#wV$BEpu_vVQm(!!z!ADHy#3*U?>ccE|v2@nWVOaUPRPJXME0pQM#eQ>_uhslX7ZA-~i>aT&mdM3B#;XvJqJ84Kp5@nUQ2&~H)Ew_cHm8J`fYP2Q^D;*?LzpgrQIx9xQPB))yJ_VAx$gwOLrcLCmbbb5r?HQ8xy4C2?(SkSCSxWj&s$di+_@v(Lo-ZR^vk;Zod947 zy54B)&2`fH) z9gQJ6$?ucX@$C*k90We`20IdIX*Bkx=OJ=*C$N- z+SXmkw15G^`t0~eA6&nU7FN44OfhiiC8Ghsf$PnL#ShFw-3E*W)YS9*uS6G}YC%Yh z7U6)!0M7f4M=R2&gza&j>i75A%E^2bI%8AD5yAA>8pD4Y3u(+64)`&4R$wZ|%w>#w zy`i#08v$O9Oci=!BY{hbQ_riMhbUC6Hlt;%WU*Un&nVlLL;y-s)VqVjPEu5ooPbRd zqV=~KLMnz;j>*`U!qeai7f;iAy;3=bfDl@*gVE{s`D0wZ7@eQ2)AKC%nnfv~cr;bu zbb_F*6i|2snL5Xr947THlK5ABF;tG8X`+dAty~D2C1~DziM6gZ{tQx=S&HM*jB>|qgyZK$vfsYWX!9o`p)P zI`7|>T`xi`E%r-rzIHJ(t&~Wvb95{eDK8sFgpfwiZ}Ef2GAoBkP+4? zxk2>B4utIT?H>H2=Xj7!392InVN5|!Bz-hqx&H>?hQp1?WDMu;S8oOr7E*7nwRIz4BCpXQB&PO(4_j)h5(K#R})Ecd^<>$8=xBe_XL zB!ARk-85jd;Fd^Z@$cGv-yg5tKCY?d>IM&l_sjqX3RDZ8zFo#GEnJB1F88}{5SKfB zHM+P!@$=!)faV&I=R5=PFcHI6BuGk6$?TO}&D0o+bYBK_kg)T348_RoL*m@-yd7Un zqCsLnB8CK+zO4UJSX|1a&wl6c<12La7GeX~FUKA10Ru;Mt}d9Qu=8ew8|9Sh+mans z8E|^LGnSNM1v%}cNmhr3(p8eowsAZ=+n$sbb1)BghB|;)hr00fX z;$^7+=>IPU%X##r3dp||!$2fSS}IS_5azL>-dUsb`KSHPPSF8lMK!ZDL&dHAC2wm7uy*xaq+AJ0KyRwVHi9@l*&A6sPecO5S5-t`|PS{jK?do;Q zxb~@Hw-pdfQ!Hn`ltJoMKfWBihRVdVX)s)nUsvLOOrhfLs!j@-%19-s(kt2e^fl&E zwHr!Okw7hL*tXlfzb72cs`sAF!vU^`ZGG;dOfk0ywf-lbQWz` zFAgB{j!E%R3>SAu?r46IXkZv^_#NkK_6U%5hZ#f!Q(q@PSMDRq$m%s3l9-{hX>vn3 ze2gQckf9m18V>)6q<}HI6}4v2F^a_h)O3UnM;Ec*XK%Sqd`3T^L{{scdaN!YzU9H_ z=>X~r)gqp!UvLIV^_Bas6G@hY0#XMGfQXmBiEc8o{HRCp2t}kE)70~M`31NSkYo@e zV+G;ye+~if2t0!7^JpPLs6lh|IcsPLZg}9V0T^Nr>}fj0@dMdzXG|V~e@gGo7X!*q zibWXJ#4&9j;kKIo*noAG4~Dz<0N}R(B%IZGoIS!iFr~ri!Sqvxe5@W!kzPf*!k1pR zFAXfcO@I#u+wyN`Bhq;3Gx>^y0b)SV3w?NbcG2xyteupa%LuGYT;VL2NHMel8GI^1 z3sfWD>K>|h{!oH4UJoW9Av0JhMr|j5ht(c#?jAO39gmRZ4hI{G|Fru0*4o})vw@O0 zR20cE@N$IRMgHwizB+i&L%h=RgS&zOU?G{-5LXvPi#Y?r3{QJYj86=12Y1uDz-7gWeM9kK4>5NcN`cvZg&@t*ht+HEBTv9yhJ zIAgD?yi)P<~hOQ?geIH9#43(zs-H-|6^~IO~-v4vC-FPj?t|d^+kNJAo zB2(eoiE%Z><~pFfC;SRqE?CBKk=+_@%5b>$Kb75-LQS!A(I5&#wineg{~m-j-iibi zRq84=qkKHD3W2Fr7abIeInI@hg3ei8ZJs7mnZ^G~e|*&OMORQ?b)65a2EVq=t#nV& zyySk>$k6R$7KrABY7Nt_`mc~P&UaAJfo1+HwH&g;ZKgZ_=GnJEp~0c!sU2?MqfEbI zOXcU>R@+x*tZK?!dQ>ht&mb3Y5Yi2Qu^cT|j*>+eYJ2ZcoMXjNG>?OT>)1iSgf2Gi z6w36)j?g5~+leFxbiBL8NL?}T4yTBbJi=B-XA=%5(g;;AgQs*WO&#C9^2 z@;i$X&Q2x5jJw|lXMvjoJZ^K)MI|luH>a4gf#^z;?}?Dg8NUaM?NM0eiT}& zvrGkc9sbEAUPBDtqa-l^Tb4&&cHCwC%XHM*N~GM490NOyZHK2Np0J7TL`ysl=$dZw zyNp@duMM)}9kGU|XVaB$WP-S%5O-9F{OX|5E< zyLO1#TSmNXTJSKwTaA-mutmZbp=WZ0{b0xnNfo)r(JbKS8Djp50|U(T6nAbX(=}v{ zu_#$AQb~8p1PEV5J`U!NDU0F>4}JX!ES`xbaH)Bo1M2p@FRv*>>BdEU#n|%-m5@nn zR$Uc8IS2NxujyP6Kt(b^Y^4{O6l=}{RbTA5o?`>-TUyUuB#e-l{$r3`GkJw6ymik8 z{c}_3EY0DSy7)rfk}~ugnv@4qsN6CD9A(h;fZE<792@vMt|D;P^0p+nP`#4N&ef53 z-b%Bm!!UXrw@rYe)f;B2{ZPC_DnzNsmZZWdGNe>X2-S>8Ss$F!dn8K??kV#SI)a8t zBI-eWla0x|VDe9s+S|3w(d~GGj=R{?%NUJf7{d)X5&Et?lcRrA=Ld>xKmP}C#xRrQ zqs$9(X`OIA)g&T#mM&|lQ<8m8oDtJ%&ZwAlA>#YTxzW3SW_ z!e|jmh9O@(LjkI4(Y5XiWs$?h&N;oX6G^W-g@e+PAV>4RBN03t1zqwW>7m_Hke+MT zZo;-`i6=~?oOa}f%o1)69lgS>z2Y+x;TK=bdUZGmOUXWI*skBi@nJ~=8w!7-`#JV? z>y;bGw()^?P0sMYL!l4GLjOAyVt9U+Nm^$%J$?=|^(|&OPcYyk!)7H+GlDXs{kq`- zT;H(!0!3!De>OR-G?fW?s|I-}H_Ba=rs<^L=l;SB9>4CTG_qJ=_{k6=a)w)bSgl&Q zJ#e+%e`5Ra?MT~41}@23VPCmlm23c|U$V0H#YKGqMwM3NECVZST4X}Pp$?l9uY?V> z6Id$zW}VJxX_%|D4LG^pwNK#i+KNr?LgcRwIw?%2O^~H&0hJaA`ttJf!WE$3tO$j$ zBJn)nba)I&(Q!i25tiQZ**YIC|F7ZOqVV$VMMFjMNd9NLnMjseNb}`W?JVWcf`?KF z@sk4!XUSk-3eWMf0E6XylcDl2LGh(yvRe8(R3aoSR8$0Jb-2L%h7V=A3ajRGukc+7 z-VEm5NeEf#DW5@GKRA`k`pG6PK4vvX(%zc*Y3m)rxt{S<1dMPCSxPWd%~ zHZLH&`4`zeR{0eWKLwR5a4OLqx+bS{u1fz(0OHAne1&mH-iL(>4lN!gf(9umNK%^fBM|@zIo{>FEd);V< z8w1Zrr^v7?xw%E_#XiSl(9`pd6`)Z#n-Fz2+JeJtQ&f? zT9cnDi>~aL)$i^oxYIG*tD6$@4;zp;Tr%}gn$J3X=Dcje*|#{?@Y$ysKD5qoo;Bch zYf4)RZTdSXVI#Aa8^ui(S7*$M)q>DtjtbV#zl4eja%A|3tE@`ImDh@2LPf_OtN7oc zuoaW6-CF2ct+MU}0SEV2)KiXV)p6Bc1mD#Tj1sMF*J^@T>8XWOt^GQ0tMpbnENN*P zyf(c^&q_ZD@n<1A(+e%#r0mc-F;@?jJ|e%gwtm*8Z7SVeo1thl_>rIKBnK&3g#A;_ z;dO}7V>`s{L$pF(j=9*P`xmYsxS|4e&gHRvTNdu9Azz5NV&T%1*Aqg4E`{wq_)rJ_ zhb$pnV$6}j%UegZC|->4%n+gSn!%glAGp`@MUZFwYv)rufv`I~8UCCkX_dp3tVl-V z|CF+jEj|Ag2c168YEu755hO7+2L)D1-M&EMkPFx7Z$@vOVfgVgxuu#-0F$46P zhc7^dm~`PNe?`JzG5Sk?PG|YyK1v*+kanSY>e102sYvOT_e_m83^U5wCmSn0)^c2k z7G`S+CYEbwK)mAt5E<0Tuq>@MiopkzC8EwZ78wdhc9 z(>J~D4pJvVY3ZapZ-)r@!THxYqBw9bOw`0$1sz|ScxrX7O~LC-+8eXzygKnzFw zvG87?+7lczX(UH*G_~|E7eZZsU^`IMsPS!plYpqbUi)w_Ch*7z1y#AiJ2 zn8KX5{mt7j47dggJ|w#Ebyd4;7CatYplFs(FBVj&4Ecg+1`HJgDSRM3e$WMU)agOe5v#3N-c*5U7Jb}`@M~?n0-F_e?sq=J z9klK=+<0AcQ|w-k^>wr39G#9DiI`I0 zfsFu&ENA*sVQdFgeGXJjA2CQh{XCyG)Q5uA!}LVlD49f?K+V}lOgB5n6YK{*Tc23b(U7G&9aBmMs*+lhz znWF^ig)HntwqYTf7_hq-yfuYk){5vH@C6aEoKwfb>umW}ec6ptDp#t4;NfUE7J=cA zyjBzq^c0fg8n~h?YnqCe;dZn^cr+CbM8w$eJz&{kv3FpWx4itpGk&5rA^LD;AL^$l z#CF1Z35$y;^w()VB=lR)#gp_2rpVgV9(Rk?b*7Q>6*X9{F~S5oOJ*Z6{0SkS$SNXm z<7u)uC4)PD@EjXy;~=GgNydm!MyW>Kyg@t zjr1)fD^x~1VI2oq)PT%^&Gm7*q*tnmbd)r{P8gbYrZ-&q?zEjOIU_(rVDk>n=>Ce$bhjVg)_I2= zwQP4ZYjF2hTql)RgQU-8xj{N&4nfZ!jXqRYf#0tsfX<0Nt3>ova%`EjT-NFHd>}!1 z5sk!2_h&LOOjxwlEl>zIk_8Qra=qKSx3RXn{@vlDXIooOH~#ii?q(bQb5&N~A+UaB zz<@!%G#%p~m69?yoOr%LR%RFo%ZwJ%0HsuQJRCBoprjU|wQ|eXpeTMqMU0=}OplXP zK3RdyQVa!B`NM@yv0Jjd=!%iTu>+lR`Bt?cZtEULu(G-fAq)8%(yp^9S}w6Wp8VK# zovw0ld%-aj^0TXo2iKRTe{z+6N3k82o-j6bpD1Hp7z`p9rjn3Fgd%P`($eejHg_2Y zfvg?nSd6z0HH0IcosS?^ifAzgk_u-1nEKB$j6?u)gDC4R;H!zTWM5_Qwqq>ny=*$u zWQOqqEFDF-vu^OM+=uD3iz!>y3fWnqhV8JdR;luoPF9$*SZbrn=mR-6b3+DtC=QGb zP%ukXnZ$YI&4dmKkg^t6_MdqxnWtEr`DBEnELmDzMRRKw?*!O1$pK<@shOFo)CF{E zcy5tcA(tDon9YiiyYpkZ4Tp(_RZ~6H5>a>O^$l7N6141Y8I=hB?3`|5Jl6pUlTEcz z4nn&o%Ijsy1&WKxP&HOwPZg%99mjybz8VcKv!P|FjV_SjvecXx!el#UN!!0oS;A^0 z?1O)?a6q+6GZNg5JA~9`T{$PW@juhz81D#Cr7`zlP^n(fGOF7SO|@W`0_V*}7}W;f zr1QWBY^XF?36ZPXOmcV;Y|9dKqw}$M%u#5gC)%+w7Tf5$LBr(;NhET81#Dm>O<_SZ zI4e!X)@Y*Yw_S!6OM29q+lN`tv-G4>KZgaU^*U;kK<2KpwJd71YmcZM72}2lqr&Z} zipTIp%OJw078eQAjVc#hl6h!nCQkm(>G=GB|HF8-!lP>Pcc6)A4%X-b)xT#~)H*?I zi^>u_hZL2y&p(Xl2*rQybiEwm^@U{33nys;Oq_G-k6q^p+{x91AtbRz00-z!eY12j zmKlXl$KzL5fFu%5L`6GJ4(K$2aj|(!kK#mtRL-<s9CW80^rkrF`!AYC%(+o9 z1776rDy4(FRm?e#5#Ib7LP*;Wmo9%w+WNx@4>RBok zBAlJ5SGlxptviJ@b!?jiM%WaZ;KL8=R3W)eyYw?y*t(lyoqRZ&$O~F;U4tr!svR1K zP(=$#oJ0koSX9L%Y=?Zx)~`_rabQT$I32wnh7mWBo?1^g+0Pxmlh#&cb@kEq*4Fl4 z5BL85cx&^?f9(OjB_tK~Bw^%%*xWnzfAP+-L&J8y7q&*&7{`j z9UkJDyu+(w?@oSscX{#)o?#l18RKfFUGnB8W74@eseH&!JWhtEzmSVcjLoBDGaGDY zvYKKKEWooW-W=qMX?9ePNBh{XNu4j090#b+u87~}NS&`3Zj4KT_kT4A&@CT(KNnZm zZ(rT@a=9dQ-bMb?;qZ)E`(7ed3ljXwY`EsdXNnIIrd%S^;T>5eHg12cJ^gO;$)oK}_s8F-97l zdm2WfA@pdcP`&QSFYs-2!T0hHei>?X)5JCgm(;=BHF=8cyt5uP52`E;WZASOKZcgG zZ+dkfVZ^Cs3cU@LnhnuM!r-tP;Rpb6lm}$%s|#i%8y@#N+vlfmJ3nmh$hEGgYrA*x z|NGzmaQErAKcM>2OQGntblRJH|*0T3=b;A5hTG+tfBx-}gt_Si5L z)X3TW?jN8zL{;`#i`4So_Pa=uekoTQ#md2BUYB8e$k~#J*fY`?;C_S$9|*Il-~keV zM1x!p!2Y+oQ}~~*&a}~j z#-zs9c#Pde_I*cg3fpa2iK)lFwOXTcwWz3By|?x-Jg!#Mj@OP3o=y;pL~R~4UQst- z!4D;qr)An0V^*8F-3yx6PVp#=k61Jb7>98R(2=vQR^c(8XobUW@EB(LixIbb8EU1I zaJfqNp6jOgcH`bjR=?w^?uC-?;p+30&jrlcd%DG+?3j`rrRG5g=|eT>&*T?hMHE0o zIX460M|l@16kyMh?`MyAWsEIDkP>dmk6ygB&cjzd0aM&0mGP%MRyf6@1Z5MRA9yj; zu`NWk#%0S>tSF74(B0Z=tgG_IkE$|eg{t;EC@Kmf!(7TzEF;+9oadEJ2fw@x;FkdU zvVVK&GFxVd$h=etP`HM_t5w14mhPfQ;?c(8i+t{ebIUxT?!#OjwIq{5ei z@wyakw1idBXh(yg`4x@qx#dO!wY7j)T97czVBs+$rx+ErIOy~N9*Vx>kMZ zzZ39uAU$jz9CKICMBY@P1tf#xl`%>+oxKtxi!rkqnZ3WmE$1Zj9@F)1l~M<@VJ_<{ zVvlSQjaD>oyCGgIuB=&n3KWTg%UbbQs6YeCAf@&0zT6xZmG*MAg1cfDMAJi8c%N*$ z7qSeB5}^ybl~5nbogJ6h;5SP`-C80Su^<%|oslQ~N~&sLDUigiei7p+8ZZg%O3z`T zl+c2Kl3@!WD-BrAzLO>u_F5!_QPu`+TUvG;Bq?qhL^%D01}!2*Duz)SN#%Qa9TL6^ zSeR~LIn%ts>q};0T6iHOF+onq`)WR(i0`T)3>ZJMD%ljtbinN;Fzdr`$_k-49)eXC z5lky~ZTj~7C;+Xij|G<@6gHBD7o(h;VG1&=SG6iK5ykjfW{LHQq%1>NqJLlU1yM1? zOeiiMBLU?tyA6H|RHO4%SGKbF`ZUOTXro2&)X02n5SS+Noc0_!k|wjo&uEm5PK^^I zO;hYeiltu{5kooV{)or_k&@)|>&BiYt^a3wQYj<|@|dBlT`p?^rnGc7NwO+tdRtUrIQ_yky;O8mMDa~Wel(h3gDxirMS4^b zydvT`XtT0$)YLE!GS#Xe+cbm16>8GEj`b!cssQ7J%|oJWT}ziC{CN~Tl@CsRv4q$% zJTE$5SJS8$V*GaAu~_M{^3IsG&#XQ%&2(q;U=?3sX7ot>OK*<55?fMOvEIT`D3MyJ zzKw#Jeibd8SQgo;3?t8pj(elFxT+p0{Zoi_O4jzW)V){i&5@;XSApc^@>+JG7kRjl z7M;YvW^zE&3CkG*;|r*#1TwU7Ye679^nhela;a|8?h_Oey}3_H&-#s&d4GyctjtXx zmUf(+>}-usMn}Qj-Rm}Y_Q=1X?`6u%e59qPb)5izxb1g7?gYfH1ks`|6DNr0@~REh zwmf^u<22?w??^FTEb#hw@RZ-ML;FX)abo_6m5ghOu>S?$f{>KcNVfNwjGH~kf%;(q zHL|#I6sHA?ZjO}6#DgLENE8}1k(ogY??nu;+QIsBbP=?>Zghw{gVS6a>Pd?BQPxE& zA=>F~xbcCWbxKr|ur9Kua5qOXx@{~%<&Gl3Qgo>i7)_AWKYj2y513<;}6J|HSAWNn<=@pDx zmr{fRa{*@E2)$UDkB&W1c)srT@df#x;3Y3`sbe-2TE4$m_Xj}$&1re8JLg7SeC6NUf{7HpHen)5Ni=Skihtg@;x5kf zJcAydfIEYLn7*EK2xEn7cFf#t*GPx~QzuQ=5Cm)CtMet6BLjZljJOO_m!yPrYY|uF#y^u_NmmMJl-Xqs1<1JMjVyMRF zL$!JmmWXDluGj*4=C`=s1=4)d=BB1HNm-$JM?Eyt3+LgdEkogV>f}trVnhawbBy0+ z3?c*x_fh;R{Po+B$pUD3r>Yd87SqfAp|0){!p|nlw9V6N{_az4(1AKW-kS>==1||Y zaXrS3yy1~6w!L(fD+~AS+PWdKVn#_3C_t*(J&?SMxZMrD@F{gi%f(D1}Hhq8F}lsobavQ)Cwr9DBnFbm}9WWqVuvqiVj zFqVUwuxU~Iqx-R3OaGYLmya3G`7uM1wQR|6!Sg#$EEKvhHete}%Z&7;t}$lM4(mki z^cFLqIXyeS*G~7lO1Y_>muJ*c0vc_Dz!OhVrW)OdSZSxkimviDrgO>b(d6=Kfa7Xi z6buCQKj{xs4F-;bAPkj|C^a2$u*8L`BD2rbJLH`>A9Eh~_MRCAA|`a?Xn9j?{jh~% z;hH<$O3Lt{x|9nOrMNKmVWHj-m1;9kW9V{2Q)$x+JjN6QO4HIXPxcB#pQ>3Os6ucoC(QHkc?xDSSa<%7H>b>t-@iE&wDU4H|=o zAZ!|S-fcn~TxYkVvwclX@S~>4v5%?JGvw6RHg}|{<1Ip@7_V+Y1uQ$omy4URm4B5g z4co#j24yh5N@bREn1u+CTi;2sO}G1FxXAwm9ND~_ZL_U)9oVCKf7&6aq;W8y6KK*z z4+zJ>9pkn_Tr)<`bET8@=dPYc2v4ec?_S4ev|X!l&pftKu21Dm%<<7cmvd zT-6E7G>%Y`K1c$+*y}23xNR+0Oa(pB$hGb7 zt*~Pe8=%p~#c67aI$eX9zb!!E6&>zW)NHF?#~b#D5z3AG(2AC(?CF-g@@0O%XGV3RSGGt z=nSLz$?3pm0$<}t`|?br1jcVD1au&=a7Z+SUKm3t3+{@z`x(8e%@KzOb9spod3sHX z1J`G3Ck=8lEt#_S>}tjQO|}Tkopgnyjt7%0AHdX+VGIGWF9)ChKYy0n+D%lMO_$m; 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 tj0)(oW=6 zTS_?d2S9pWW7J&rwkxhtgtN3sAFf$Q=k78V^|>OSbh|5}jf>!xjUU2P+p1oV-Ltbya7!Na@h}$&JD`XhRTX4Eo!oDNG)S~(5BPUmtn8mR**=TL7(yH6_NY!V7yIsv1wLznX@q4Ec-#t)AO9Hs%8OuG?mkx&!T2LDt}BEl5@VGkG(_ITL}{F!cUfbRO$ zSo?KU0F4|IYOK8mBVXej`$VAop}-|ZTOV;6L)Jp;tNiv!@n^qU$>r13MMJ3DsdcrO zKGgGDxm$b9dg}GT;MAlNmpS=e#@z;>`Y@w`w z&1+oM8AcT1$8bDQuSC%E@zT|{XgC!$`H54Lx~~^@nx_Vgp%kNXhVkkk)8lD&egYJF zjqfXw#$9k;Up$$4FsrmlHoJVYL;JC7&CJ%;;T0>mm$IB>Xji$eB@AYN;X_rfdC8p- z_0mHZrDz!ZnCc9L=eyNj?TL=zYT)Cc;pHXYHTo2YkkDb-r^;v~fm~NNbmIfQDB6dc zJjmU_Bhd#UsO4_k7xJ!PWEK}Jv6ng_mPSx$m=}Hbf2n(uB*pcu+q17xoMxtBFNFjG z)X0VkR7o}wf}ps z^;_~6ixq_7d4`H0NY!>r8XDI_L7$Bdy} zGSEv`f_qzU#*2F_Pe;{SmT-cAAPtyE?EV&5g=V3-0cityD@QYa57TKyn=_KCmsl%c z)?08PT!R~4;1%Oi9*GTc@piXuZt87|KUs5yKlv6YuMG~(r4?`7!kBiOXj^P@P!kIK zQ@$X*zO#hvBJ<<(dO*|q(N}=BGyAJVqa;jZJTlyLg?QTdh97KT+MqgKFmOAc*@JiC zptbBGoi?}Hu1+jMGe_~d1r=3M8y6{i2li=_gZjPGvz z-LwDh$&nZG|L4yRFa4idV3hDVocAIkIrq~e8rCumfLDTT)|LV%<<2eu0oFlUox!)M zi)T)D#1DRHFm!?@717+gaxK6=E-ORM1^^a!aUuvv2N6TgVnMchAl=1V*J1aZI#sKo z2nKJX?G-v^QkS?X@{zd)CvvuWD(US5>`2aR3|7IOh|K7+1^hIQ^u1t!Fk8M4)p7^_!QDu_i4klTS&QlO zQ9sWSu+lP(1EQ%z00MxLPe53kwjc6L ziyq>XPX}^+jU9-B%^xs)BMr+kY#+|U;c$@FJ3ia40qGlu7qvDHh{bR9dObSiytj+( zwQ!{<4@=f{Lv$xfD$}%(E=_1Bu5|H6pot({XTsqyD#3K!Zqs?i6@Ci;gaE@>BQwr| zoNhknorIw#Fg>HkHQ30upMz!3$kzJte1Wu`w z3KYF7wrR^ei_d%QiP%l*f@M68GWsTh<<`HtEX9KNW7thzK}aTNUe0To{oO(5pK_*N zp7bEDUcXZ}kf&gfyz`Jh1Y3dau z@HW|cg${#p25K?b5)lpDGLS0pI_&;X{aqU}BmRK0DkkmSfe)5`szJVZ`cDk~nQQe* z%o={=!u*VZ0{h|h`+KR{i1PW(=4e=BgTbIKekwHiq%C~C$HI@%1z+e%KQ}-^hV{9U zE`qI{Pw<{VypWP@fm3qq0AZg41aCW6va>Dd9b{(^IU=Jp54ms4%@-({nx&B8-5Q5L zsaCb9%Biv*92W_@!?so$%B0S>AxamvyTp&>C~RQKobLr53MCaCk&qC9<`%Lyeu*xJ z%);BWCZ=Q#W;`_8=5Yft} zn~r)?HTM_FDDBc+CGiR^k9quEn{8k(&k_wFb)%T_56^ z0^5WPoa@N0Ad@;STUSJnS(@dt&q@H)wx>l0*#9YK&wEX^*n!9Xf#bBEwhE-2Y8stSk4 zkJX=<-Zhy}{2|j!*WHHR$Mub@ciX;-lM#QcoO4>XkW@AJ;zEMbkr3{XHgN1R`QX|1 zDFQv3X46k+M;`zgH8a*zlLEfIH`4|HXd#&pdE-g@iHJ)pljU2iAI0qem!%^@wR@ix zqLM8PtpGo2KVN|Md(?!>bfUNAU3P;_INA|+oi`dFE5PPw3>JIZRrvSLXEqCV3a?wla0#}eA(n@XxQoH_qzxxc% z_ib_8hR1Fup4$VqYSK*Uc=P!9UGe`pAM1Dd1#d`spUQu^*6N~0Hxm$#b1lM@P$A1m6G)f*j4y9*cI|ms+z=7w=Y$V-+Xzp zPe76n*wxER_+Gv857`WO$W_+)jzI|XWLQiSkC_F!3vOwVW+iTX>>>2pkf-g@7?3J%I`^y0EMRkUYA>=N= z2Lg7C!pl7T%pv0B6|TKK!XaJ+rWkc>9$uK%gIgz@6^qI0miZ_hgV2AHbQY+&6vxA7 zL9!6Vk_#iRk7$G5!Lbiv&l|Mf++*O|9jZHCXKDG&NTCK_!mjn(VYRV6}T1KNvoE0}QDcbi{O{a(fE7h8kx_OwX#NMWwQLa^tG<4*wL>31@# zr{*^3CkEzZA1@tN4l3(|nHrALGo@{|TP_m3T~aaqi6k$KTs|EQ;kU)X6|Wmvy}w>f z3<%+EuhV4+zBQ_Z{45l`H1_@qbHbSd3_I+kLx)tt`kZ>g>^wzGv$j|>pWz`8;E~v} z17dP)XbAiU$)(XnUxIToj0^%Zvd6d{i4#LOuF#v!<6bpS& z*HM**{c04J;jp{ZRc_@z%grYi%&WRKU+W6Sg^=rU8QmO*N~W(B^z;|E*C^t)q7xw|z7aXWX z2T`_ z{N!-Br~plGX8;l)BPeh@G}!pkGn7nMgpiB&p4=|=iG>k8puAvUdnJ7Bbgh-CaEjBH zCF@U0C_>vX{QZYDwDXpG$-`7Kw$W9-l_TbnQ)rBP!F$NZXZD2hl|4bX6fU`C2jJlw zAel%Se1!&4IT*Ixj7?DlV-32cWX>*Y2C~HA&v$u>WrPBJ#_oBL6;AXT=D`k@XXqQI zRTuYRLW^`-68#QKY$F?!)A~uNPl)Nh+)#gcl--h#{di{YRd9mI%YZx%xpDFsk$cg{Y(CSZN8F1o<)BSM*EzA3b;Z5J7LQ4xkB9QaG z4*bY}>csy#@WVnDD1OU-|HS_~@FQQxE1$Z@@BLF?N4$OKCj97`Y5WCGpck;`sap~j zLOeoS0}V=}8-;_&jP~Z8zGgW`FT2wK=Sc3hCuLSECDL?5#cHs<%l5mVJh+fQWVHRE zR5lwBOy>gNGeShZ9mPayaGV-fYb)S9FssYV`FVDMTBP{pLsA|Avm`qbV{g8Au+}$I zfJc-bsCQv@r09Q}fxeKS_P;&`({7@LjF8-e6ST0ta}|FwssK#@4@y`6Lj6ZQk?6DkkQFK6=Q`2$X`i@5a7DAWv)-6gh3@ zG>l_G&{A1a-yJdBo;Z|9#NUH*`%goEc?zV(g8>6 z*1yx#>bJR_UxAuVeH-HrW+(qmk;L~!#*dqGcceA#al>XWKrs-B zFlvrq6>};er=k+I9Z{y&6Qo#>gIi<)^gGG;0K2yTbO2hcrjKxU7W^|0e&FKJ7lu$n z=sG0($ulD)vd8O)dXuuW_XGjosR?)RM22SPaMY8(?6oZ2(}+OsF-Syx%u z#S^uOi{mps9iQ7#HTZfZD9Rp3$Rq*`9LO7RSvpU&vN^!g1$3%vaohklFu z{6~KL9lQB&5@vqQBMbzvz-o`9r=v=PPYIO1QN#6 z9_)EG$mK8(HaOGSbOX7R1vbF1#RYAORkg7BF^*2S{;WV_p|+mcpW{O(-B&$0hQ-jQ zOZV!toxorUUwQr$&{l_G3r@9bafFgeUm5|s4@Z5V9l(SJ>J!+}I(Au|o{%XmNo?Js zrVH*fP#8wE7Vb3C91C*qXUXMZ&xnq~%$f^39;6)))o6g$dTv2J)IqcwJqr^SjS+Eq zPM8h4$kp|{ZC2=bhso!24Y?4;cK3*NvI1=FzudOJrb#dV{Id9hAtT^?9sd(F=~q$<(2@Th)$(?cc>=27 z4OkjSy8F+=pljyMvwxcp?Su)6=WSg4zVZF=GT{cQ;@dj!@D>S}c$c+|=hx>FT%p5p%0WaK*kl!wGJw z6A?^6qqdmwlQ$<;@%JxznlbHlW6 zAwB|IBT;|Kg1YQ{k1M9V)`5%q?;?XwV_*5@RAk>*OkghTU)UgAjti<%npx1F zcHsduU_t=s0D|!GGP|m$r@y>jjk|y~zJe&2H;eoW)Bl#2^PUY;ygWtEkE4RBe0uW@ zh@=ZZxga04{sOV+Fen-W-e|>Rz!m5BCq+I#>%TfCFlHR?2_-OXwn^-~B6Dkdtg@m)yZmxCdm1(0axscs-NSsQOn1w2Un3qQ=(PTrggV z@9u;^?A_&g0Sj=b@j&*O&QbTIu2ha5x6}Q+&m{a6sbw4*C6`oOUvVvqf`p93V95-G zcb3@)uIKx)`=AO$*w92r_Jm7eWdxQwki^m*w(rSiGow4eFW(p+!=4>_za-!k%x{~k z+nL+F%u_?QoSXaokj$0Ii%K*h$@3Xo+Tc0+kcd`>zwJXWgY%BJtoFR=aVx&!$ZG|G zhvK^3RUoa<55e{VN#iq4OOI{^_92S31*I)9q>eq2+O^t>(k%m0Q27@$PQEG48geF{ z6!hni*!6)5Vps}J00IhACU8Lg-9m(bhv>U);8Snw0%h<)W6Fu%KOGcT9dgBD1i<8g zqWlS>{B4jeKhS+PdC2`b9UOy6CmKwXetadW1AXJ*_h#e?UdCi#1JMR^augImLCm4C zaIt^|qe8;!PvCdk3)rsG1j1YGhmTwyC+8a48ByWPiaC^_B4$$0EsnWT74>v1RL#%# zoleS8*~SoxnDksF~t4LCJ%$^T6y6R&5Omo^>_Y>}c2HTAiO8 zPmv;F-BvbMjD81!a^%i;6s=NVmU~UKX28!mzm-RT+SNj0GJZUtAmA=AK)iDGD939R zRraULowpCy@$&4=9D?7k=Kl8-Wud9l0{;6;Z-7+)h5=!yzp!8R+J9`L)mIyxeKqAU z)avJk#~;I#tz$oPpN7#0>txWZMjc4SKnn^yc&PXH^6f^rZLQhj@|q9->8JVp!vOs4zwp!i!Vn3H(wF!509U_n(~wR- z07RgysG`mrAi5Shpk+2SvF$JdO=WU_vn;_9MHNcUl#$=e7$Ugv!zw1{jF3> zLCo%Vh%UZ@pQi=fWCiIT3F|J@WlKB^6-Fw1o5ze?wOHwJv@@)KsE9w~Fq??&9y zj8-eXUu?qt!Jsw}qm8)Op^Cwu$JFI!MC0+X@rf3~$T@6n075jo6OMVt*M|n1wG+nN0)~|P%kZee z8k4UQ3QFGV*^iAI8j988+oE@==5u3TUX+6yvBBF(Sp(^8E3VG5ZI0P71qq585V?7*32q`qI?j$dBS%ty%@fWpWe*X(so*lK~|9Id@e*M_OW=znl-_x6;i{Jh#F-opyV63n6i zG=bdhsTbDv4OMn#bA9fyTM6{Nt)Dn##anr@y{!F;}seSq? zz5;bl^M$i(f63+eh?TMcno6!g2=O{b$KWHFKz<7v2#_7H4z96pc0s@fe}CI6r}ev- z)}l33tDhi#1w@7~XTu`N&_QR=(EYaQ{W3UK&1vGlQi8O%{0(S&-M4fs@lDPJ;RygY z`ZtXndXB-Gvlz5JDu^xh&l}b_rf=5;9he&|u|$(8ICPVA+9hdMc97^vzuBiO-kKNG z#g;^z(DTIA5$$=~NjzlaFf0>)LZ(rx<_0Jcl)|4n_txLhU9GGN;$cy$lt0J6u9Z7c?KA39qI1s?>@pN$r2Yv(+aUepX zsCkNZ$8?1Al%2MuBX2O;z&Lw-olz@YIcR^xna%lX0^%3#05rR&<|=oGMeOTfXT?408jSoAp9?rK573TWwK_LMUEg28T zdz{<^Pe*igfDHjl=JsXY`z}ZLt}laGwPx?-GJ6=i^~mMb9h#!#dPaaY^mB&AuY&%* zZ{ofsVaQB|oak$%+#^p|_>dl(Xdgh*kDXTdk3^@Pov1Ez3D|HnZ)Jpl+vbzMLREsb8&?ikC1B=irLO!q9Uitd+D6HE!;`W4F)6 z%s3NHIIi$c?A)^7db>JZZM)dr@!QrwP8m+J!Mt}lN*Nq^bbacQ`dC@Ts}&(F=q!4j z5lBuzP7u3|ZMO|@`ixTAc-r2pDv!GH&FD(k-Fb~63&@w3dPSq4hghSb34!6s>XZDbrYy=~Ix?m1bCx zWSsUf0Z8~$v}6$2y`xa^w{isa7IsY^hkwQY!+PG*y7}73;p;W|t6uJ7RQjjlcw}CJ z0|3&zcmW`!mEd!=#3>-(Tv!&)FmkFW56FFxx} ztaVi=-oK8#-GO%=&Zd2#g~s<%1ZhSfEPZY1!OWJt*w>%N3=(Bt4BsQFU`?0G8PdqT zXn>@x28q5^SU7(Js~5OLDxmbC>VAOPUrN;X+h9)d)0g+<)gk|hm-pq>A^(Y&7rm}- zFFM>0r>~zo1WxrfN3|S?YdkI|sWFav5$!5zyj@#Qo4PAh+FYZM!~<>8{dh7@`wVaf zj#0|~c=ou6SsE5VYLD!;$>GnC&l!fWrUU(alQd|d%=mH_z=f^E8DWol<+MDwwA5=7 zGTAX(+`#G9xEhzXtqz%8Qqg(qBq9ZuR#q^)cnc|dnJZs=I|Bowu@!bVh8BXO4<;vdlxceA*}}0J5dr(?qToV9YZi8GH8!?fdKCN zKFtQAmy&gqUY^FfN`Z=otvUzDGJ|Kh&E`$7MBL^$Y&Q(<; zBl?@|fR)w;5i5_g+Ha=Sf=(nlt)X+hs>ek^{pT67NeOmpmHl`a4q2r<9gJJUwwYk| zEZsNqy@g!t@isOnv(fH=tQZVon{+UpisOD@*yN0e=t-^j+o+HEB8rA|TN=O?YC_pJ zE@UPH9oKNS&m#sB2K2F_xTs8M0Wi8N1ugcLdpnEvlz=TBxS#|ArW6y@Y_wZ5HDEGg z{s|76J09}Adek9w=n`FENUkNPCRt>ZP%->__ANkEThejCW~4k`ND_JUm&JO{8X`o;3=PZgd$ztU8T_)CTVN-PrH z%x2`jMXSB0Hj~Xlo&Az7G^-GFGLOim;`%S`>xW2yel0Z|$SMNYl=*&m2S8HsK`Y|k z|rhfez61;!$V`5qWOc%hpA4;!}5Tx&K%V`x%Nt5tJ?LWh0^DRti24Fap`p=gL za86n9fVN71Fh4R?e!YaRuOR>SHK_gHeGLg-SHyqnw^gSy0>&dXumXt5m?Eosny*!Pw>x9| zi<4zE0nt2mD#R*t;v@u^K{sV4<5hcYHrt{uZ2;DRF>(p}$}_Mh$UeoLbvdQ;ncriB zvQEu(DJq~kT-y^GC<}8li7PH&+;D?*g5higJ3S4=qAa^`MKU0?bV%xS$;dB4Lp58U$Oj!Y&~A1hqhv)&T`EQNPBbUzp>w%BkuGFKG`rl7But2f8h z>!1Bc3fnI_*w2nD1SATRwOKU^$a%X0Ggj5fzrm1YV-AO3mp)oCw2^Y;ZnaR)ktD8; z=~R1Y~&E;NK&m z!QZsFkQ#T9>1Gt7y+*MSor6^b38KiBCCHY-!=sR$&$rfx=8#aOoeBJY?HP^6xeCQ3 zPWQQD+#XCe_2_fyTw|*j=PkZFXb?%X&y*W>KrZ6Q8t%~+iOq?8N?l3uO%;c|Iv;kY zgQk~NSJ84TjWQ7Me#RadH4dBbGHs+3cDtWd-y9z>ll&yi`wD4&33&z{Hi$qqGo=& z`A_AO=Uy{Uboo*~MQ1hb_v5fw+`Dx9)4=<6R+^koO1hU4*94c=3s(I>CREjv^OBu` zW!5t@u*?Fd@Vm2m{_-C{W^@}?U=syq*0plqJPGfyaV0Mvj{xGzF90B>a1eEUnI62d z;$Yzf0$JOAgAKvn>=ion^){6Q3n1`%-|O@XkqFLM2RcXN9rVef2A1}7tctq zyz||xuzbDG)v zT|kbEc0Dg(1#^;z4<-EAw-Kscy1o?;qKanc-60+CG@fjNa!=p+i>T@AB;p2>`ffX(ivs_W|wYBOFJtmj+`3Bgq)lQz~9J9-Jb>kR;g~2S=2ul}sulSueLc z8@b$JR(AquEMk$Q!9^CAyfwtr$nCU(h7e@G1muAcIk1nKhgFL5qmw$w_$)O{BIrl6 z@&pJ`D#!{Zo)911r|R1KN7sY0Dmx2_yWJBfOriw?gJI2@3(6dwfXou zJOBV&Ciq5vqJrslzau4507+tB)x1)`q!!@wk5kNAOe9anX+SX;LvS+l(o}sfLRR6k!5#4VL^`ts4WfyXq9QpjjV6Db3wcZ_TVD%AVa4{c*(rVRfZ{FN)DML# zk*2o}hiksR*OYMCTU$JTsH=jysa$WiBgL`(g`w8BE^7y{)sE5bF5#)5k>qtc&PNZx zo<(>g_IGS^Ho=K5#F1myM!mVY_=DU?)j_|g&8}f=$bwX_AqSY?bc-I(qLf39-kT7(Xu@jOR}G;<1Y| z$bVeU-Vvg{K}d42xDwu$lg2ZR0jLaJ24IW~afj$Fgo8Via?fYC%ZDkv`}yFs$~n7= zq#_>{ak1tro^*nUkE}&O^k{n{6l$Q8$6mPFq-D^#0{ej7@o{iNKU&>YXTUv(86fmy zy_Izd|1CdJe8zPf{~uvfed=5O)QE6z(F^2P@~lBA%*KNs`MgK71qGJlTvy58RvRyQ zKaewiAR=-gS9d-N))DscisiPPUBVKPQ=zHfem=9o3ItFu9x+`lA`kHOHvwRNd_p9Z z`ck>P2iHD96$m%N8e;)*2%w#E@J~?r9xdkl4L zPp}U-T$|Kq2GJRoy9BLi?x?hoPFZmrpLPwO3fJ=2hEo{60DidrhTRF(sKtOuMQeoptYm*eyWo&{tCDxDeKK$mqHS zC5!lgCM*;6=Plya^c{jF$$;71V$NX#8G*I26gQEl&z^#P=K6OrE%dyUUubZo}9o4QQ+VvGP36H$#`5eI@kQBIhO_NrI!+v z>LZ&sW5e>D(T9O{QC^5)NDpRLV=S5n9;|_Euht{vumHvFpa52Kg#f+mz9sMFw2s>2 zzy?s9uD2Bix}cE323+8o6jsxw8m&z6mUaAz=p6O{X%&%38{A!sgO&1xHcRN71suCJm0__3FkFkc!g$Y8Z4^KrZzyfU+DAC(Kf!(&P(O1 z0qp11*Qd@iosB0AaX>Fff1#cV4!`n%B?gA7c*M#pUqTqGv_M*E87wsSdcX@DCaxZg zlWYTi0I>4GlLM`khBVK&lST$^IndUbrGIYT5N-d~QHH#cRp@niHFnM6=I$Sopc4u| z6CLJ8{KWlu0{NJ5LSII2+jR0Q2>HFS9koom65(n*8JsNP#e;T%a`!aLu7hYr8 z!&PLjo?_7pN*@qdPQ+OCES^S}OB7YjOK#gqj=kvvG6_;2DUM550H$g0h#q z6$6(BKC$zs>OSAA)NT(qfiLpD}k<3OZLsoZQ zGGDFd)JWO@3>(uDp1OkSZJ8VA)Id!YTezOjZEP&p0tkxBgJqzQ;#3p!P zkQ6H?g*|)S^Rj-Z>pg_EH2amdy(NQYT2H@LvXw#48S&iRV8y2#`pvFVE-i}cOS~%* z<<0;GXpA#MClKFGl)BFDbYOGp`6lg!d!5BR5re92%m_$^*d-xFT&LU_3Z|JMU!IE# zzEHALpix8>udD7VC5G8+1X7>DE3@075Htkq2;Y472IO)Mc0XS918N8PQ zC--1zbbk!FXN$Wqv9vX2wzuWT=S6D^43zD3;4epGacK-awUEcRHL=xxbj0*=Ne-w{ zL$<+Dck_HcQFe1}O_I0k#5l(uE#7zmg&Q*M2JuYS2B;>t1z$aYw-%cTW)+WyTGhUK z0NEYXRV&5RX)D3T0GRnVORL-Zuwz2jx2t?i%A1gTL8#*GadXhIE8=yh>%y14v0P}) z(^B^WLzVh`8H(=e>gcd(*ozV!x0nmeOClrn>KUMn55E)t=Wn-#Oa?KwU_Sh-N&aG* zs*1ZXeqR{=kH27F_~%KVItvlaJBV*CUSkJP>priZlU_FN0y=~tf|NMO=w3hr`xGJONE84!4(O_jhn0}Y z{x&3e)xcL|;nzEYq}O^`XkWYq+EgV2Ff-cIc&d52QO$nhzo;cLI?d zJ^uMT2rh%nNU0BSV-J*9G`m2!If<{Ke1_s$>ytobKuzr0H(TLxwsVXjC>3u8o+3v zhLCRb%BDy`HMt{q>#>L4U@O368GzRS9L#yp_ z?B4BN@@lOdXdL>pu#CwdyRnvzUQlvOBpd8cGg}%fNcIlKRs2LKOyMdY`hD@Bg5z)3B)17Zp9o=iCdnYy< zP(TcUoJRinaJAT7j8DA>%0dXpUSW7UU-R|>?6$bul1*XkE?Dg_%Q@!PD-tY~@iP)z zV2z(aWj>D6Vj%0OOz`+5YvD|I#rmodGUx{RM~9k(1dj`AqoDvO4Tgn>~!%aO(3(Puivwh}Bq@>$mwnMj6F2Z}`2=74}r9wMF}NqGg# ziip9+Gt9VE!@31(US-q_k2M6V!&aRE^nd&!&jDm-@R}NX5H#^Vw)wEggYY?h&4xsp zWxtnIuPm!)_cFE;YHC$6BElc+t8K_J2XWqM{AN#pump?7VHN}s0O!YV_;&v)OMy3> z1`v((QC&Vp&u6O0b2nZx**{y}>2ZE_`17Q{PY$aGhU1GA5J!MTlvSsZhcG_O*;i|#X@Or9>S z)*kN1{*bVPi;Fu7`iw=@H#cW@?q|IXc}(%AVqK;Jvbig|e=$>al|mBdsTE0vBQC_g zZMP&)a2FNz?n!vfg(+X7DThMdWT$Y3v>C)MxPo%dIe5E#Ud2V=Ag0&yfC|Ra(y#Bp z3G`|8Nw(|hz#r<9Wx$k*B@3p&4y~SO@|h>me9S5GX4T8EMqOtm;&@g10@sK;MBTyo zI?Xp}*WHx?4t<@4nUHaCBI{5oTOMb}BoM!Q5$w1%h*F zegI)=;sYa{H5H}e5Hh;ueRwNH7~US?BhGf4ME2`ZEQ;7Zuv zIEH_;)PaE@LaI^EFHmqli|c17BTrfcs!=uRhKBmixYih&?4R~MFCK8X-IM*I)et3n z9CYe28KtQ!;7dpVC{D&~bh1mOui-NuG4p-ok-B5SbpRww3}TeB+XXU~_GDeHcY5f@ z6P#m7M!~yL7|mP9@P|XnKPY`Ju0!R?1l0GlrsCL&q@0_XLf(CNRO1CC<_f>*O84*t z;j!C=4F_$XtxTj$w<4Z6RubniGJ(uXE#f3%ZX0r7uk;sL}pK zZ~4d0(gGVq)i89_2#>n)tSNh#Y3&{%EV2*tv7PWiG#zv}w?P4iyOtKWHwe&!C#}dF z(aY0Su=VdEzlQ{aZ4~J=f~GW#WFlwB#D->Ks=C9^s1y#BXrYP)CJu>#vC^=g`?!rS`yN*j0Ubie-zlN8uIr5eK(3iA_@Kcc z?w$=;cWU71C*jfLZH%=-n#4)pLU1(RfsHkaeegACQ9+2RpC&PKn(+&0X!{8?S~l_!@zBeoXKY0kj<$* zl2V3DSN;`gx6aqRYOSAvZ^B+$vO-bRuHgzhG>I~!(Pm>s8tcJD?{a8JJ$5j+tXsBk z(kHA!i>1(C$%^13;`21+?k^QSa5#SD;8S0cSUB`XAM^}@3GX3f)7t^tcHMwE2Y0Z+ z(dP_ju%B+>Fk*o^ZE zSP$3gQ`~CLPvOZKgeN<%^!E4m?Bl^eAz2&;)e0w=D!nWcP&wzt!>Xc^fiYIkP^dkvu0C?7eEJk{w z=pL*>m2ynZsoh>F1+*`?nj&P*aSU6WNP%7%0`2P%nPA+@wQi*&Rbg1zIuab)bD)Li zE+O0J{7mObA(IQh$g?Njou5$)td~azc@|0IRI9srf51(u1~P+Zyj&sMl}?fx-1e7} zJyW>dw9^$|91t&A_tJ0sg59q+YhsrTx6V9gr=ieEc0leR;P7xAXfjfF2R3b3!@^*- zCI}9OF}u5fIphf>l<~gw5HL5Fj(ee3k7*xNivu6(3d8DllSqI=<3$n2WsToU0bF5#k~WN z{U&+OW;kxscswu`q1A>UIkRYZznE>r^`` zMvKzF?I@eSSRdvbAEt({d2;bzAO_1o4fa_6h}`}p=lU`n1>vWt{l&l~PUJveWFP;> z>whn3Dm%Xf^Y^dVf>(so-%&ykqLBVz&wrs`zNOWtP)OI@EB658Pp|?1s(%e_rqptX zEnbH>IGcHA=5r8&ylqTitmZ_c`8KzEog{%*!O(A^0$ic@LiN77;a-th zZ<&uT1a4#EhxFrp_-gEhQ}x>SUNBy64U>qM6s-Y7P8$iu2N@Cm;XVm-f1ib*kn<&l zQ*K$T$$6A`S>L3`^0KpJ_P7f`tD(}TtvwTsE7ePKN5}?FLw9Bk>7$qVDbl^+tmyTA zAoW&PumWQ10A=r9yKO@WD6|Fen`2^`ZN7YRp|Q*^oSv&0;qrjRhawV%R*r9erANmbpeko>XQiVaDxcQ6+A`bA5VMVObN^t$1nm*sX z>?Pq=r|B|B7M_VBpO=^0;S$Ps~X6{7X_46i9B9|j~1sS&8trLpKQCosMGm>s!mHbXx={Rw7_X=pgI8}1=2y8 zSMH|P1d!VV)eum4;E4H16@W80^|qvk)1@uD-+j{`>ox@7K(F}<5umrQ9-@eUR<{p7 zt=oXid#k};3-=%Ew&hIX`xQq%&a(|Xo7dW?1fWZbKq_kP^90ac$Qs^*=a=ggTtOz# zHNJP!OZnY5{S37Es^iF?3VAk$8i*u+?pRYcFhkQ1WP={4jvEn=AVvI&5!<`Gp>WRQ zp@Vxo5?QyM;pXU@#;bx&)A*>VrnO>9(Nk>Q5hwN{tP;j{*)UO7uJ*Xs*zm8JRm9DD zh8Z`yk^bn;2Ty8E8UhNhE~#XgaJr%dXAKq#*aZk4=acjtx^*YqQXztCfTa-qL$?aB z%EJO?Q(4n#=Fc?>;q97wxucpeJ@Vmsk5=eRP*Rpf>Y+*tItNa`;%9~S$^d5Tv#Ozk zatpaDJUp*F&# zs@V#1gJ^UXu%^M~rU`c)jrH}f>o{e4NmC;Bq>ZIi?5k|WWOnUDspp)BV^iSR?QGOc zBj-*pINJkoe(TsuZq21~;!D2{FOQ&gQmYl|y>gSUn|?;NRXF65!%8;-g5a8&Om`To z44WyE~Qus z?g;)X2~bX3-A6Jo%T0J3(|ihsBLecQUc*k3E$p#&9{_GpAJ)EpkX_!@BP$SQ5-=A~ z26=|qK{V~!B*CxI(q&*MI-*E?)ecFVnVFRE*HiOsANSx4M6KP9HBMWVB!%6c>v9X+ z6^9+Yc3fUQM9PZ;)xb(k{DdpZ_`c4(?!YXW_I>Wy%#G{d)hKi#%5*fOBsG5n5zblux>5TK|NBqWg2Otk{jY>9I;zLKUrPY4AD zKOUR@^h~~8`F}i-P4+)|CNP=T{sxhfjd}W)vm8w1ZF3hl$uvEh1Zo+hSz>=~(4U9}Row z)LEwpeYzbJGG+x?@Vb_mS>5iAU6x|<&gr6!utQQhhkOXF-+By4Daj{;h$cGPNKzwa zF`bXi^@N1TwwAXSi<4(HJUg6WUbIfXx1jX|zY-we##_W}_}pgi1gx4LxD9S3$vPYv zelm0m1*9pmZFYy_`7|Gvn(^^PeF9Np5BGQWg!a@ufEdUkh+m%Z4ApXipXNP2)726j zK%RDaYvYxzsk~k^R$WG2XuD`KLDz-r=L16SttbAXwiU_IwE10t^M#MIQC&)s3pacu zjuKMCWodWlW2PJ$e3dUDAZiVKobNWs`}ZJ=kGtLn-K;^}9uDzZ(3!OR^ zH=6jc+}R6p1^x5GA9#!m4!Z{w3*`_x%BH+h2m~zM^AHJI0LJd0EkrY)!v4&d7&Gef z+t307SGnCDpYj^!xMFX;g6|WndSxqhJPIkaBDVVwubcLODf$6>G&%j+!p}&z;QYYD zloLUECcY9_uTAJt0>?PCa@W_8?0kC(!Og4m(be7Wj<4AU%k~O3q!(zcT8L@b$yz6qU~ZVGbsqt5!u3`$zZwT$eR)NAa&w8kNN3^`&KB+90W^2^hSEZZsn#A zYt;)cAew71=~P>gZIBF*2luF-c9O+m2Ps7RjEi^K4(2W=MkaimH^K_) zQSth)Q{oHK1%Bzo~DaWc}2V zeyMLCkNxqP{7>uK&(Gw4r@jqZV4Cx3OlhxA3E6M#v0BM{7}%if)gML_ds1>_9}xJU zQ20dp`SEq9d}*Uz$>peHQ_wF`dC~fHW-e%zb|!hkJ)r&7Wy4xy$%sQ0d5qkq$5( z$+|;7|2Qj)N)oKT=dBl+S&qEqFVmDe0+au#zVQGiqv;iF1_su>mjK z|AYGWP0R+ja;-k}Wx}*zEyjm?c3%VG0TDT}L_D4r+P_*rZAngc3vzo9wQ&>5VQPgm z;euXuOIu`6MiYpS#L2@4(j0M}b>Xm*{cwFs*u&9a)GQ~_^Ll%(qkF_=0y$w!eJgQS zW;QtCQ;GPnmJ9LPhP~+TFU8$eI8uo#tEKBWES=)^(vn<0sFx@)iU!TB%408~J}5JI z&bvYm3H2NkQHc15Q3avQ6x$hC`H17NNhsIjR>wwo&&bkNpGeo!Du zP?z1M;MXUM`SUsBZ%oof>;_HbR{<+OL3c7)%IBuaRWG5SkH5T<{WT!byuGO8?D`?St7`?6~)?Q227}Es!F@h;Ua-mhzo%d{%zhu*w=_QGl z`hF&)(sWOw9^TIz1y~BZWtb}?W^DJYp8=w5JohdSE+d0HZ?~9@T2Q13r(U1Sx?nOW z(k1GCpa6Q!-CmH%DzV*Xr7KrgxCz;MAUiv6q#FzvcnY= zs6fwQB_fl{(+-d0wD=P!r^{l0Be8kWY74cg&Q2-8AJAj{^T!1p(j^NzP z*MW_f$X8$EFq@p7Z)H7vm|L`Qv9p2|*F?T^+xuhd(<4T0m~rCOYSyCL4Zi;q$?1$H z_WVl4?43F2swn~)%k9M_c?z7e%C$D3akd;N!fNU5~QWS2tLDtgFVSueKQKq*2Wa+WB|ulgpcjaI3C>_IovW|hvJF5Cz&r;|8i1nx7GUs; zCQ}4hz#HuW?u|9VF!y+)7<#|9Bfq>LpUKgBtWLvZCLpqyZfh7su?IYSrKD#UnrG~e zrKK5#Ny^}(=OWm0mY8}PeE;GAwr_EU3DVE0x`DBiHPAbexCPgJgj>%-d-holA?{Ud zrKB@3%ns=>*ScPdJffThjh^|MG=q2HIqJ~mG{3VUOzHKMipml;jDR0mJT;FM!nhq+ zw&WQx?&19)ThQ+4RNlhL>=V|=6E@i7Co~paa}U>N&bzj!V6e_KQzMvd%M8Rg)7JD% z@28A8v(k(sb5Ux2L_Zi@()7#Yt^+f7pC~7+s#ihIFLyV^z{>;d1XTb?JEcmE0F!Wc z3f27vp(b2A5Q6ROpOG#lRsxpTxMAiD)X3CbxgpnVad*sky_^x`O{MO%-5qGX1Lt8I5q!|)O zJ{@hBg&fNlRQ8scE@-;~M&+39lzob~GJ|ajV(!vV3?A${Pek2~7kLo!U~X%k5FStV zQeMg^NrZbK0k4zuDj3G)V1|aJ?OC=uRYqL7ZdOS*!fdn7t_oTms-bDV>?R3Q#&{80 z2$(0(?UYtzRLNz4Ar;D8_7?y*ado43)6xAGW8K#_jTYhae1xe=|h+iW?P<7~9Tx&my(GG!{06HLVRGHqoi#?f_VDZ|qm8|y05qP#-tiWY&1xq!m%$*1blTM z1ALth&p}xV^#Md;>*7(*0S1Kk>J4-HDzpUuv7dG@*#N`{SfvvWveL!$MRRz%jUAD5 z-an=9Ao70+4c=c+h8z z7RvyiB2-y}YQ6$ppt$XzJwI&dZK>>XqNE;xVUzTv#GEiZBMdg(mbhoMygsoHnH5qr zRE_K~6;FqbV+34p&m(>8FQD#2hNEg_)85=%{w4iO0PWUz|If#u@pAspw^khRg@1aB z{1?;4@x3T&?eBM1k%xSg?*r!V^W_gGfclG%vVPM7wF#&*A6)E>G}jS?LNbuM1u$8! zJ`&{!j-D4J5!IkY{Z%CjVd3-#K>HkHpc4IiT*_B*(hS$g;`2Ug21@vm0`-19e~v7b z=%&3RR3O(8VjbTE+iw63NQn_Ou!q7%-2yZIH|XJqi2ui96_7-~pgRDd;R2fR3O($H z0TY5IlPIR2kYR;^U@Y8xmnnQxDSWq70hr+%7GO3nA773df$-p;^QtyqzJZ{ct$%z! zKHs`t4Z4UQGXnU___sRwPd93qoc$bwK%oO9rRvMYd=(&)SPMl`HDtA~B`2JEgn!MZc zj9~@`C)+W!Gvg56(q7_g-mNWBg&LJ4`fjIh^z8zP>3dNX`GrJ;D|0r=vl=EZzr9?5 zGt2WY2za~?5ul%_vfZ+v(&(xZTvansCOg_Y?{ANn2EVIPo7{E>nBeyZq>mq4X~441 zyoEYwSq}P5EMEjFng~T*YW#-aF>ljF*lfd^maviWt^S1^jKDs1h{_h&=Jbj&7ycRU zwXC`%pj7PdqRpEpff{LHj??2!0eFNTJ+GKdR49I&oJ! z_oTt2yBpgdtZg>BFB=kmumoq<-%Flf9*RVb))5evSHn!=7z?;V;kn! zc!%=1WYTuKvWUclZ{r4~LlOg2Bh@+h=I-NisdcqmJPv10d$H8xcn5neWFxCp)>vLS zlk4o?fW~q&PyT7Ix}xRmR>e7cPayFqCfiJBxiE0DR9Lb)&S3YioOpwY8R<6X`TLYMi9h6iT%uX`JteF zQxkt^X#ZS;zYh@pZGIqL-nAHn+uumxe~M9nu+x4LK6l3uDXNFj`j;CYP(%7Al4L!? zWZMX#X8ff%HNX=4T{6X+{)Zj&LuVukau{J2|J`Z-wI0JZ`)@JY-|OoKmk1p93;5gD zkMg>L^c0L!5Ws`oOf~ThL>&VNfwE`STL^R*|qc!n03$~#|kbJ z7g^#?drXDF?8XIqMj4pbbEoaI39a=A!n2Mk@}i4TQcl3^kmRz6A%yMW=4#>g8h;uP zRo#;ZBi9koq$+1nQ&mrP7BdIjJc83Ico(U*?yC zmrqHw_FaZ}Dy!ErT9PxOU#cstMpSumHk5j-?=@WByd)9K5j-CFEiX7hI0d|dASkc0K3mj1bvdaW(@_X;A)@ZkS$q_tr5m{32-m?u)_|1`(!k*b5i9 z$94J*K#MeUf^b@jlEuC^ffrEE40yZ6JS}65I7y@dr7PJqqtkQx%hgdJv5Ie zi$N?KUOEV+X+de#Zaib+#ipJs37`idTyse8Lvh&>niIqp9!u<6tjl4}Z^`j`=@Z{L zVC$;TM`j6k;2!Lz34QdoNsHUc=d-3scJ*C6DYJR585dTs$ z|FvMA-vzTq{wkQi6|nN>Ukc_sccwNWcoLjk65Q}kn-Gh{0K`T*Y9a zAWYnofXngIE$>_2%!hdX1IclKlyAuU`(7|VDlJ?_zi3axcizl9(sc!Y0O(OQ(4+qJ z*mU@%58e1RH~*rof-Vl`ZQm2kWs82Zoifl0x!7)mdv)kRTH#+_&puFVtA3kL!LjAh z-^eOB+|{e^A+#U5()a!He*?_D;Vdq)qx~FqHZwXNRmFWKr<#FSD+k1rCYNaIX@9(g9H>|?YZrgA7=^9qT7le3iCi;lLx?8 zhZZ*ushe=_bk1V8c?d7a9h9Dt8G)kE8a=rn`!G3YCy-xz8}dP!EhrG>jAEiTNi#A5 zIDE~Fr>aYz&vQ6%gK+P0xwyIDUu@wns{rR&rB(V6tcbx77wWnPDQpeRKfIHqJiAY& zcT+BqPk8h?rp%-LEd^Z15Zg`l$ZN>PQkMd~qBD26+oGeHt@5n96}Z-E_1a}~c2H4# zzme<#d|417Eu72{RR-Mmr*yL1$;Wo`0Q)HcV264C=fNV^%I)D?^GUhexSC3d^r^QU zX}=X$=XbGN5H9%pqWa5Vaq~q(WZJDIbw(}sr%RCcg&_bX7DQC1ZC&?*(dq}H+xyS> za%pyu?(mdPjXsN2=dTL#81_=N8PVhIvFi_Lv)tF-@QK}Gp{ly$mIt57*a~M1kp!cj z0o3E-98S%x$dv%GZ^&y#%6weIZN>9CWceI!8#3<^{;*!vXsbN8%OM~NhJ|gCaI=XH zKc9~GayOtvb!~iD677-wbXP5hZ@CPhDA5rPCXQSetKWr8yBQvyBHb5nTLsZpZ#^3G z5n$OYcz6@o!l+pu?Q-iLN9hCIY8`zKbJU6&p)CUAT94|piMwt z^-ic?Jv=_V8PDyJgsJnn+JmvLk(zveW_6_Jx&|XZ>)G$Y+>-FWxF55we=B@)1aDMc z{IO|rw;+sQShplgzd5#(v|~x>_aR&Yzx$%*|K=_==f9MgO<>;S2hfi||0BK4o)}n= zN^oHP7FG>3DfJd`B%L(ZANG!xSQ@ZWlDuU@ntKP+`vE z`o3Gdk$fh&(r%Gxg!1>X9)u{smkpO-_;oVB0Rv#+_W4*9(pFFdFaT(SKVRG5pw8{v zNcR00T3q;2v%B>?0!8rqzYZV&hF=a5f)f3vfqO>qi7T+ykwEw^V@xu=S0(0g5%{M%) z64bO0zxCVErg)(_xS<7Dvws+a{5l2c7eft@{lR_4nnwXAyu=PW@HOi^?d|C6elyXq zYs3VXKM3m4ym2iEW)kmqmAzbmP~y9%Hx@5abjC!Gar>+W>M1?Wb#*&JYN=2-;|+pI zn=L5|7l_HYFrL_2x!9>)(&|ev!^P_!G0reJJnF*@P)zOHKAE>V+RtgEnmKJ5uvtIH z$)V-7M%_MSBBq*0Ol{PcQ@amX#^?6lSG;pk`T+s27j$d6rwdZ^&mDqcz2+ST0k|^YuI&u_^zk z8gKBB3d*{Q$M^!SQ_O)mjMs2#lW{>Z@30V?#N0O?o!(c9(8DckhZIM(>I_OjENCJV99*O=Is$Z z;Le)zJWwx!7bug<=S1F~e5_h4@Y@dc48%z5jO;~N{Xy8>vu&!{IstL~ZlxPQ|AW`D zV9FRhJ)m2CBr9KTOXlcS&+{~(r~SZ?=lj7>+)D{8uCtz&6WRD%Zc4MpG#$HKyeGQ# zt?06;k#uhI>bAv>bq$+vg1AwyaXyK4rY4bEhuT$pVMervB?6MQ7o&6btZMsQF(QGI zb(+J_%QjAOBn93V3KtEGOt!V9@YPERS|W%W8OemFkrt6$Ia>J?6}Kej`k@o`Q4g8$ zf#O)&YuU>gWAv^$2ZqWBO5D)WKc~Q{m%6?{hw>i|?qDDSIsFH482|Au{Uz;A<$ttC zKU@b`-#3p>M+eM<2n_#iVCMIU&p-Y8Z)dy&Q9c4h9{xjf^A6P%`1+TY!NaxL6M3*# zzt5`%ki%FBma`d-mKrkQ;2jx5yAZ?2@54>teIP&9a4=Ts+bIn^AgIp}p8-Q3;0=C^ zRs;y-JVaOutl5ti!VI~|`8?Z22|Qm(Joj*6cpf3p9jwIwqlJ(;z)xg%*ggS@B8g>y z6&8gb)6iAb7`WA*V5(TZ4R9d;@`LOMc%4tRFk>LG4k4PVwyqWTD z`r!KnjUO<2hxf;DcHLk!1;HIrdAeH-I92KqdNk-1;hPBABX9(NZw}w+A^6hZC;i2C z{X6T%cjv~21IZ?5o8$A9+Q?lz8bEYLgsF|Btu-FKvX)C=8xRI9IqUg&x57&xTI_y* zm2g_^PCgl0l;5;IJ)E@-?jkq)T1!?|sO-HsM<)!z;smgI{2tte zCOczB^8y~qur*Lo6NZMpxNUlymwK=2>n*g?Co5a!(Wl#k@5qt}JU5H!-Pr2~{+@An zYtR+n@eHkIdis3kK$B4~7DSbOYrAaz3asby51kXlFhT~z8v!%9(tA?->lyWY$^zpq1aeZ(&;hEzThk$w|jRC5W=+kPgFK9f^T7U5T#FU?kxIyn{7J~4q1nuDFy^k+k zYV~nGR270eupJQ6P2E5(vt)PeLPtC9wDi`aMVp7kuoM8PlD33=69<5S)@HjHr{pn8 zuP_9j3AS6hnzS?&SOPoL47SzPwmIJi%lS4w-=DSBL%54y2w7qgxgEYdoxM>1)Yn1C zND8Whk3GTVF?=0>{o~^HEvWbH%V1`6quQ-_6jweKW_C0Fc`24*DJwhvgA4d2a-Of_@Cg zz~nStpdh{*N67s7#7w+#y6@@OxB2{EYl!b9U%Uac_f1#>X~cJb2|NaRi60M6e|q|# zN8P_adpL0Z{ptU=&mPM9$42%aHI82;jsTaDp8ox2*pL@u##6YrUSTi-wy@JT)2*O{ zUOP2B*y^%a9YYQ!0bQm}R}qJK!*wrG6`nd-0<6d<7vs@R%e2RVN*4$)+Rv0`dDnXl z@QEbdWDCl9DpQH0@2ycalFmz2Z?NU^FElK+^XFAMv#=X9uoX@QvbeU`b%36)YLc*` zEpdOQXzy{YAQX?;tU1q0c+MTpdojgIbKZ}6AwM&_19Hfsp@jg-kclC$#eC-1leQ46 ze}SkHPdv!0IgQl-uG$Ploduq6j*)bYyjSIc4iW6Q`0?3g-5h8!qncNUR5yie*XVs- zq>xcE1zoe=eg26g*cJ_#8|yy!2U0iSG*k}6rkM-)t{q((j?Wyo{HH^Cf_gO}=?D;8E@7K^E=|c`QSys1mB;RiuNODR=aV8H+6d?rR?5F) z?D*lD==ebCfV%}|9CV(%bv zWw+tEeGJh(_2}&VHUEKre8Exo7W-;>7p^`!R{EOxQI3 zByp6D;|7@i+Jh|bTiEZ2-!5Y3{a{d-b`19%ZZE6X6NY1#Bd$4&4cRz%PuGl`Fug)n0@(b5aMXQ8w#W!MpC?dMK)Nq%sl0mH z&zn7|3+a9-ULl=Q+ZYA-?CyaJ?8XOTp`P ziA^Bl((Xi1=NO%5eG6)9VM8s;;ef%7ED0F&x>SUEOh<`WYzfJb64#FE?M}TMwml0e zqY93H=wdp<_TtbFJBmOV7~q0INv#iRvmsh*H=hjZtZR}&!9GgR1J_7<^fnVKI)-M? z;SchNW@GGTt#H@dJ!KeMZ%-ZACsAo*A$2|MtJJJqApj%?N#&{Dal8q7QDi7aLv)FkYMu7T&jKQA_F-4*CP&wtiLHVJxrV55zX(j|EiGf|DM#FM zPhi>;QfMYjSkADbxw{)Lv=Gj2fhpIBuK*+Ouznwqcpo)s@K8!4<$Uu-n}Sn`zO^>j zr^U7gfxVvF8gy^P-HUrK|t1l`s)}6vUd}bboDPMRtrdtO8385|yjmjGMoqXXN(eLjHa z@F!E%FA3c~gFK}AavToOVU=Yu%*+;)U=28_R_^?9v;FQ+9beB`KirK7lWOgFG)|Xs zNdP{s#ed4i15=htA5j#;;lgp+C=zn%g>hFkQ;050LEO&Sgdn_;?qufSX)$UdL6NK3 zDPC{8-PMW~!Xd-QVY?S7RKE|q^uS4J??C9awjl0S?9bCuN^YO+21P`kcNfIpn_YCRp9N-#gnD@<^f`~? z(&6(*D@XR05!LM}k#ao18$@tmYZqzF?m09{Ma3}sg}A^3Sq4qtna8^tf%=by88XQe zHTK*$A=)X25!yHzh?qHvFga_WqneLLT1DJFp4fNbTocO@&i4Dgb+4s|zZ>rwhg#T7p;&$RFV1HoYM|HMC)z<0CsZ)H%9-|unk z2m6`_&F>Gr$fsQ7o#e*bUrQcTJUdvWv&nz-{O=&QaN|BA`lQHj!YUsw7!8TAvsFPi zRGnrG(qccm5ik^DI(|9u5WV!Cqsz(AbLZAu$76!as zT0cED1=Y`9vEa6w)qwj*pnvPrWl6_3KmN7I9PFTgx_Vpz<4S&s^?r-hg)?wGj_Qn6&rEL>wpF{4>C1ZIg#ELaBVuSN5taox`(i)-&ERI7o zhkqKQ4tJHzDip~6$u=k=-65xh(~Xut9qz6t^9}F;^3ecJf?xB;)Z&)~2=FI$Ho@^3 zJW#BBI(=!`_DdSk11H;{h(F0%8*YR)Xu)xBgcq3k%oQWPqz(h04RBOScI(2t{d_$< z@}dEw=P=Zecoi}o77TtLgZHa>R*s2^C$rO;US$%rUg|=Bexxr6$7xtv&HRx%` zonu6r7bbC`n@0l@tZ#$t^r^R8^WJwLsdeh|{T0byqcJ7{oS9lgAVxB|D{CReGK!yCCdIV zW4&9Q4NU6nkLWY=&YcnddTR^_zgpMvbv7rKp&ACG{~)^fTV?sgRDjVj#CIi|wUb?I zw4v{(j1%?kjw-ejz=WR=F`;@WtXV+P5EIB8W)3snCsjX7hCFY(*`XMs-*5yrp1^!D z+xF0GF;hw}7`Pdi%gnhdb3VA4!F4aUkI#uJY1OJlkSSz7ixQ@+?Mx1KJx;YnOB5z) z5KOA}>N$g4g)H_U=0MnWcgQW@o=KWlaYmdWb;?fC$)Qu;joAX)j|R#6>*-uWC#5T=hxEeBs@#FaNx}3d8|k-p32v#9!c5@iz|9(#^0xM(X7UE~6IU1n@OfT% zAy@TzIa-@eFXE`>{x{BP^8Y92^tU$UW5)NlbGj;U`-2hj<(&R}?Emd^`j?jFzdon; zhg5*2d1%|wu7;G&N>a>6o9#Rv?0G^uL>f%(JayCSyJKQ3Df{Z8%lQfh)9Grk6y=<= zqP1@)!x|{*a<@}iicWpLnlLVCWXw3&kn*%N6MG^-Abn~A@9J&n4&rNG_-q_Fgofeg zb#x#B=mn9WS0J;TqrPea)PSh(^#V>?y9t)=bu@Z*SRbNT$SMd-{D2)qq`s7!ZGJz$ zN*T;-eE{y;v)r_kHAAvZctEfE5o8jtJx>m=l5h@~sJD2H16NmBv+8k$xiMHIxrb~~ z)MQV|CTWg7qvMNQTDK6A6RdechQFo3PB*DuccG%2lBwU?2#zOe^jJsL8@O z**b$2+hZzSw?%2vu-APOLZi;5k;T@_xA72~&nb)&$bnZB`|edcU98>Z00!10_#n2F z*br=9y+MEu%rDn{AyX>@P; zU3TvFfH;>;J2E(!Sx%MI3%!w&KP$%nn{!&aP-~9X8M3ANae4&+#n3bG_9`!crhP1K zVQcph9shlE_vdpOg4HWG2qj?pa>u*)qN>QmSEQ#Z-yQez>-=hBx%D(_S)Vu4SRAL= zKiDfzXG2C@!yXEMv^n|i!h(Ww*Uc^Te0^W!!huG!6?SWFN4FUw>ykOmI3;YMt2{OZ zeT$Ix%AWQRBzW*OrRz~a95Y8^J8cSizf}jml;_>)o6@ux5N%uUDq2TrfsT28HtF)Nph{B zpm5Tb%tJN;iZKa{yv#buN2=THparU$yv%zNg3;c2vnQFO%1=3=kf4&)8WzzF-i;^Uzkw&U>uoi;R%7$mrqO^XE;rVTKj zEdY}~TR0IcVYa=u?T|8oI`ZATzGmyX5uT_zCZ~Tqr-T0$KpZy+^8fRhG4*L?`e?4n ze-z^XTW2j!?*U^w{-sp;+c5FnS%V|gKx)WC z2EaQ7^h3)C?D`1$*7I3LP(OPu)pM14d5z7;Y>uCHnB*0CjwtlJ<<4%v12!C1ekxEcsv? zJrLKWr-{~FBkRGO%8t~)w0_m)E+?{jB3iB&0NOw$^5*JbNlwP9IwP|(Jy%^G>Ey!o zky#cPaXdH>As&eqq&aKM^TBS<)g6+bQ^MXe_@Y~Yd}ipuj*5rR#vAM+c87W}r3Ytb zl3TR1dSv+r5L9R1LULy4wb{nHP}c9m48fU)2xa(fxf4A!E@o!^ddz_y!?>MW5D7X`_~Jkod-`xe9CSZ|BEq7lK%6y^I!At ziVy+RA>Fqi>3?$ce`*T;=is(1fyuZxeR5B_xm z`d<3TK*#vY5noJ+8$loP-Lnr8`UJQq@8!>l7(n$l;Go=YOyP4$1agYpgM05=N%*!# z0r3Ba3gn|UpYGw8;p69aN$^|cn}SK_yrX`Cq)1n_I*>(ygjYL?a6JZJKh$5*e2+>$ zevQk*0Xg0JLDUG|di-zNd-Z3fAa{#^uJjkWK;&zF1AD2rN8uwL1#(wv`1lDTNytWm zd?YejzxWwogv!buX!O5-{yR?Pt?Pqk5hA#Nk_CL`??(yvZ(-M69QD7pN;h6E>M12^ z?nLw>WN}Zw*5R*Zc=O$%`dNoXO*6%wG9#m%&QzUS()QIGqQhd zL%pl6^R(BNEwNuxn=iwiMASLj*G5BZy61Fyu9`z17Hqz=7<%ZB%zi#>-D3d?^eL8K z?3z~hCZDI5#`q@|V*1Fb>?)FH!>gJJ7xuA4V7rT_4ND#fC-rVAn>h52<~kQW!xVdW zE%o4T-gRW=&cPepU$U|2%Tt*ji(*EJvD`@&@+7Y!7&DP4;NJ&p;}B-%nU;hL5wUa^ z3!mwIau2M78J`M76$I^?ZY8GL1r)Tjb+4wf@x2Zg$7U*yun_tbD+B;mP*I)>r~jJW z%o|HwZQH0^(A7=*T`tuN+z4@y+Creq2PXnzon6EfzlzaKxIz5TmWz-9@FOV~Y$abr z>!$Sm^EN!-wE%JRhs2tG21sGtN!^zB&vRAQLkV(E=WNx~`HAykV}>xDYRyfp zJz+q9K+mnKNVEc?X^itkHo_=vrwmc{k|7cA3f(LPk<vUt3~$*QMiWiIKzrpW^LcC)f$d5c0DC6~lH*Y-NcKAm>axcXVg2r)#D6 z_@!VAExvL`V`vjY6RL1SR)o<7CpjcRv)C^?cRtUD*F#?U_IbQ)$#b!;rM&<1nW zQT2A{ONj*iXs%CC2RKvTpLchbxS6@RIiHkxPOyKxcZ>fk@IpWMHW&N6Z-ev?Hd+LR zvHy7U{`akxWWMYC{G-l`!qHiO<+r?5GNy$6j1{_hvgw7B^nR5CR%ip4S;+vz-<$CF z{eJzIi4?BP?VBvA@ggV%VGG8+_eeY>(0}Hr2Ha%>V514RgHV=3U<8=B z6_jt-(VHOs(_{bktlm#*^MB`A!M6!&{vWnl(r1k64?4cR_+Y5>5Ej2L4r5=@IFw=_ z1$QtxeIW!$bX%JfHeSl?z zBKrhs1L~a|l{>QD;=^XYWw4ig)bklw*M{=i>=SOkLpRV#Y#x5>Qng6g&% zEDW|Gw_KBch8){kZll_Iy7!lgKKXzEmJSDf0IY_>xWGSTGdV)U4?(JbHxGDVFo=ZdFLcoXnBF0};? zLe1dZi~n@v{Yh-*kSQG5xoJ`5%cBK5U%KOB=^M&Xq~A|=1RndW-#Mgj8emY;AhVZi zR0a^F36m*ac@n`)NO({1u2tu^wc+B^H{oxWoAa`Q_!O_xn?3 ztB?05z5^MFRUTG7e@7{8Y=~4p-G8eO{-kWH$bnczjf4X`_hqI~MO)_eCv2{xqvZ0P zVLVNcgoUPl-w-NenFip(!8wzj^7Ul(g?!9hF&B`IlW~|#f+m|l2ZGx^9A~dy^h;`8 zt4EU8t^$&mtBPQTPsxyo4k8jXB)7tmYv)%=^u6wT3lP3XJ?d(@G9J4DRv`cvx+%Ac zn1Y1H=OYTF?|Qp6QGwd8LfBevKpk}{+MENNM;9|!PG!$fdcg02n=9y6bzGRW9@Z`R z2}6bS9`a$Eo=jq*mYt);_jGFRB9K4siZJH=@m5pMa&w>+J9%n)xn|b&9Z2*_JX{t- zxb(9H#QpBo0epVsQHLy09D*;`09;JH;AWtZIlvGDhsj}s{Imb)&*y{`{WnL&f1!bp z-1lj*{B&AizdbEv?tlNZ*jHThUHhPg|9)C{(edNN*Z>sa&Byz4VuX_=Ccm5*pO2aU zA3w9tW8)Wk@9$+{`eQCdZyK)%Y3>F4b(o}eOJ865gDsx9zqu68M_m;}8>+Agp zRm~?KJTO~M%bu*B1@{ssw%f}+!X~sHg6PqlZ$wid`>b$;vk82A2R{yK3H$uVb6b{9 zAYVtS+{U8ZNAXZFqv;+Kep{K_J>)x`ZP7rJTIqwD*!ghfOIjDCRTluGirFBjBgtk+ zu-V4Pxu90%6!>aT=50rfAXlAZH>IwST~F>xGQoVzGKkuHZt458X_$RSs!FGpvLhgN z36g4joYNPuCVQ}hH?RI;lj7?}mu#ilF|Yt$_|LWAR?C^S42Bx&n~Ue`K0x5fx9P#d-ls+cr-l!whT3R~1pnj92iqr#3_)SL*CtWw1z>O( zm~J45^Ly?6W{buw+xhDqP@C0fKlnZbzA1s=F%DvX;J-nO1||n?`8Xk>n>hT<(QY{qFcpWs4$ijmedPSEa(wd|VcWxy zI(t97+PhRb0Q_+4AREVNE}@@wC3<|4qd4Q{@dk4be#OIV5J9L~b_ic9cR2-j*H$-P z3)cW8*n3t9Y1!qC#P_pwIE-qjGj&F*$V9lv_8zsTC1KJWV0%(f1Q z+no%%hUP7K(WGKF`&3eble#M`S@1{Yd|2}XQ6B(iGK|DNS+v{W!p!Ly4G5_~`ml=& zA+*pUhDbtJAwot`@!4y5f9LD`+t>)&KPY(k50RYC>3b*v<8UTeGQ`UYy zC~6CIQ|7e!l4UCZAplIJZ>d@GHRpCOO-~6Pe_-5H2&!Cgk-vX` z1E26YPmw_<{An11x5#ORgM$X#z-oRj<9~f@3V&HRgcxMl{S+j8(}4*AcR+ZlpMMG& zZUW)RNbiP6@}7_s2cPf_Lk58wP@A+rp~i&IL5{YELCzcC zd~J3vC!ACH;NzgY%0LG~+867j&(VS5VOynyw#dWmfTG)hj>R=a$>{_u#sh?SASotE zOsKH0!tCL#`bsRnJEWHC#D2!a*K{-YQ|4@2q;GnRyE4bfd+aI&X+d6Zt~X57Qs;sqR4PN<gr(@%#eD`U9o^KZQUM|^bTl=*4&)d>P#WeCf z1vK$;1c6<1z8_ayQPuUPAK>Jk-O!5U{Vb;v=%8F zFbozM50GF9W6Yr993S4RgYp6esml#7mvdiF+&_yELL>5y^L5LEle9;HAW z1sbbP(09|>a;hGiV7J73El`i+mMO$h7jM<@3PuKdhnFPuHdva)dxsy8-l%hGoinSr z_2DBI@6gSkD(c{RfPu0l0fMJLZn`6oNkPR)v8PgTD&+o*s5fE&v1-=$_7m-#%Sj)& zp-I|TTFxK=g_Ab{Jn=f&b0?HivR!He#4-kE&oq|MWTTLiAU;nj9Z`1WX?NvxxGW8+ z#t883wEI->j~9LNS)W?jTSZ&2J8qnRur2^fpm$iaXLD|yYukWL>ow{IcXQ*tfF*J3 z3e&n}pe~sUf^Di&oV5o!!7bqx@t$BVfbi@lx{(rwdhOr!y6Yr~pK$sjq)Z#b&e=P! ziOnHwoP1A<$z3mq_N0a=X<)W5LDn}(aWBA$`{2bO>Evd3g$qcDxm~(9T5U7EYQ*;BN64r< z*EE=ob5Q=8Q3W~kYd=U;aDW5R!uL_T96*TK&=~vS0*1NYM~r-kA`94c9N;c~F~GniM)~k_yjEd% zj(1;wL4#Tj+OigE__~EI=Wl&MjB^y%>if!%?@buxjL@DVLMyUuM?Oz z|Kl;8mscLB+TTxy=MOyziyZ3;WF%O=zZK*Bd0C_#_)@=KCWu1xuU`9)?wA!5~ zwx|E?miujZ@?+0U9x<@Vu8&~ckRTccG&pfl`@&5)@O0El=l0t?+p3v&6Om7zn+m}? z7`E`5=|bPZBqNlj$~@RH&GW?}FxmQfWaHp$;TUQ9+tNZr7th}A&XT8-0YM8i^ga~C zcpSL9=GdWs7Z26l%xZLJi`(gb(=>)6b+go&5+DtZ&ed`Osxdd~MSx$OEwuJ}ns?JC zDA@`q1q0Jc=J+m-feGN?%YdqPFXeV|yaow~*RzZN$s!A1{AQ@C*x$*FWE!GHoySbY z1SgN>C^Ph;3XYc}-25(vu1^I<^NDH%gZ5%wqaE7q8G6_g8Ks}C*JM3xbb_8={h$+; zJ8F9rn3g9`S8n!uZ`&6hAblBfIm;3=(!vF`F1pfJ$CWJ4u6xFsb4zW z2X&%0I2r_1qj_NXWlzsVazHTk4qZSV&+FqPPu3khR#kqM6!XDZPfIHKdnF;Z80!xWrNwy*Jofc z$T~rsAKvV5gm!seRw#aDTisa)GLm&>xLG40dcaa*G3*^uKekWz=6PH4pJziB&);13 zrSm_{lHSaZL*MSVQy-9D@bH5(dqL9?fGq;}%-dN*KX8_EfaBv^`?)FU+ppVp+Csl2 zL*J-DGCJ?z)Q9RU{o6F^&9G%@e1Wrlz7cdV-+%`w!-H{vS?&8f6p8~t3d_1qNx9Gx zzx53U_HA!|u%LT}!!facxqrbagm3nNZNNDhY=@BSWI(I!V&;%i<$8JKlXrkyJ~w?Q z)yV0?1-pIi`W7twcl^Ow`XQ_kLd6!gCpirBd5-@{*aU6e`3$ZefW)$keEvKxuq3pl z8D?i$EXGI{lPv7Uhz%3LyJSR*kr{{z5U-!F+~@19V9_ss4@Wmfgw%EmU?A{(dI3k( znPIL4Tke<(%0Liv@p*yB`g~pBiSQN3RMrA7B!uvS%>DD{XklAMB$#mdydeMcH@tqn z;ST;{W<;7v81}`8H-@s{%HTU?UtUm$Zva;eR~}nz_qh^yK3usDe+Gmdyf9q3@VNkZ zUcLJ~@m75O3-}-4(s@Q>K3AqgpM@2_yakIv87_oO0A^)4o-r)u!0=Z{*k-=GMR*4m z1Gb4Byz-Yzhp+d~3#SabFx;Kd08GY6Hzr+JoB`;^*Uw02(gI%K^BDp)@B2U#=9%yV z2%B9j+31)4{8!lDkKZQ^Ku-`T@P@`V;0a-ze9&ij?xYJ}r>fFoU*JwyoDWNlk3}2! zb-Ppoi{Ezo(mDU~n_K}~ZaaYxVQqnh1k4`dWqtTZVtYt%{RIlM?YmGR65+1El`;;7 zFBh@Xy;B2fvTyy5KkFfVJtayu&+{BqSYS!|+zl37@weOW$40&T2KxEN{(=%$xvk6q zK}=VpcVNRio;!)UF863Un?^bqVUnsXdcY1xNKJQ$%i8xAJ){Fiq5RH1b;yCb4u|0t zjgKzEQk~Wn6NC44--D&nHeFR1JX$}kRYev6;?KjggC-PRT$SxSL_CPGrwDs=tFa5= zchR{T*YE^dUv21)*At($uPDCrqh)~E)Z*a`^OmV=W#=5yfufRE1070G$WC?1lqx$_ zJoyV15~p2%9s#dfoT6E$xdiuG;aw4r8zwFm14h@l&IaxUo=7R~*(X$iM1wNtX;c*K z)$>R8b~LZ=d+Sa3g;92{gRGzd?^BCWR#CxKtIYehdrmz$^_aEDg~Q zZpqgXOxSF>WP@}txRmIvteOs`kMEvmGgZmjJygC#K?IJ?0}z{wkWv|db$`1+{L}>l zJP>xbEvs7!zC<5bLeg}9W(SEaUWJc!M}oJtW4V*b_b@n)&WGo#4`llJ+B-}7wg&`V zd1uSSIGG7AD3{A% zPlYG2(4gXmF%~3}R~VSSf2urJUHU%NhR1)H#y@Z_Pe?zlsBa?C<;V1UXMLSpfC!X1 zfcX=UWiog;jh{Gbk;nZWyX*j`@zQZLcd zoz;bxn#p;ijFw-`$lHyF@UkJS6oShPWE|MFzTb;|NdfXRoJGyIH{X80n22u<6JQNp z6Zdiz2?;*$?dCvVRC@=Z5ac$6mV&lAb;4OX^p=hu9}kGI*oy}-!6A+m z^#@pWq_HzUFh0Kt$%>!&go1-fepVAk zajWD;kl%q27%-^Xhhs;Fu*v#Q}#=scd zOovT{%Di!pH>FIrJW6!#lpAo}!;ekK64?~>Zr_=7a9iB;a zuG(0;yibqwak&A!A{pkh_)r6~yJ+M^z23{HZ}KO7!Oni3VUs1lXpnA3Psl11WhXcsp!5BfbRw=qYW^ zAz}GTOcuMFAoeG8m5f`VVOs$$SSfnc21}M!lAOUyBCcmKAoJLR_@N1@Q0VLBN(x|D z4!RcgJopXYSr2D;Jo}wi4n~n6JSH*Oc+HXh^^xibx6LO01+WNF;PDFUwvv2_0^RRAmD05}b*!e-^ua<|d?7m9s*e&bQP=wl~q>5sSB4 zL3p{-TNgS#Qb=F5*@+hHklkm8V&1gC$1SvSkNfDj-T_oAw8gG9A-q$j!QvoImcl^b zu3al2=(|~di5;!D;vll6x2o~scL$IcxZ=5HW@rlYn7ka65mGCdQ=!su zCbUQN7|y;=zHMl1z{Z@56NT(=1tijt*O?$Y2;IszW=aCt&b#Ll?V~vxs!FDK`Z{y` z1MhYfxPbeoHWq7@$Kx|mMCN_d-%qIY-e5F1<{1JHRza1%^K=ikyT#(mj{qBN zJ_hwzFEMs186DtE5^#3$zFtjpW0h+$R+lq0mqV`=N|s&4gX}4TmOW=+vf#V-BP1tFf4g{MgSg?!c(|6Ct^z|`lfM&z<{0<(l8yUE zw}ErmV)eh5o`K<`ik&;SX5;VM@4sF@wVS?biNF2(A2;F4=h(EJzxSSOM)48h!Qony ztl7Ie-(C;x9l>q)Cw(a3!(DY26gU84*+Dl)`?xP+Uw}|VD8^y(@|%?dzzHn?O2xLk z9!_vvRm$(%+GneSW9T(hVw>dKJ6Jow>Ai*5cTjaE!5dv7BX;d{bD7#g-=M(BEh}UyP9>M>!FgslIFzP%Q z0WC@=hiz3tyxVV5k&o9!7}z~2+$866i(nKgL==yUOZeDN)Nzy!986u{WuW0j9ku$7 zH9Hmq6pY_M%slEsx7s8o2*!T6rZ}dVQJavD5T8UwI;DhdF?@j40)CgN71`H8NqH>w zaLPp=K3?N8ox?M|m-r8!t4cYA}N$un&T6{*OL&H&*vAUYPZkp#Ojp-8&<6g>N zj+fxV2+z`i8r&c_>V~_kw7Y9hteVv@mS^OcoHlCG$RR@=DℑZMqcF=be=Sww19L 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 z

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ù&417^D9Tr)htG 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\lDVN8okjE*Pzp0SFDVMjYkhdw9uc?r) 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 W-`x10d-Z?&SAX?apFP@q^#1@`HYs}m literal 0 HcmV?d00001 diff --git a/config/config.sample.php b/config/config.sample.php index a4b9fb06ccea..80186e339a07 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -445,7 +445,7 @@ /** * Define the SMTP security style - * Depends on `mail_smtpmode`. Specify when you are using `ssl` or `tls`. + * Depends on `mail_smtpmode`. Specify when you are using `ssl` or not. * Leave empty for no encryption. */ 'mail_smtpsecure' => '', @@ -456,13 +456,6 @@ */ 'mail_smtpauth' => false, -/** - * Define the SMTP authentication type - * Depends on `mail_smtpmode`. If SMTP authentication is required, - * choose the authentication type as `LOGIN` (default) or `PLAIN`. - */ -'mail_smtpauthtype' => 'LOGIN', - /** * Define the SMTP authentication username * Depends on `mail_smtpauth`. Specify the username for authenticating to the SMTP server. diff --git a/lib/private/Mail/Logger.php b/lib/private/Mail/Logger.php new file mode 100644 index 000000000000..17b51e36a39b --- /dev/null +++ b/lib/private/Mail/Logger.php @@ -0,0 +1,20 @@ +log[] = [$level, $message, $context]; + } + + /** + * @throws \JsonException + */ + public function toJSON(): string { + return json_encode($this->log, JSON_THROW_ON_ERROR); + } +} diff --git a/lib/private/Mail/Mailer.php b/lib/private/Mail/Mailer.php index 62b33d89f22d..8a5f3eed175c 100644 --- a/lib/private/Mail/Mailer.php +++ b/lib/private/Mail/Mailer.php @@ -23,9 +23,17 @@ use Egulias\EmailValidator\EmailValidator; use Egulias\EmailValidator\Validation\RFCValidation; +use OC_Defaults; use OCP\IConfig; use OCP\Mail\IMailer; use OCP\ILogger; +use Psr\Log\LoggerInterface; +use Symfony\Component\Mailer\Exception\TransportExceptionInterface; +use Symfony\Component\Mailer\Transport\SendmailTransport; +use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport; +use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream; +use Symfony\Component\Mailer\Transport\TransportInterface; +use Symfony\Component\Mime\Email; /** * Class Mailer provides some basic functions to create a mail message that can be used in combination with @@ -46,24 +54,15 @@ * @package OC\Mail */ class Mailer implements IMailer { - /** @var \Swift_SmtpTransport|\Swift_SendmailTransport Cached transport */ - private $instance = null; - /** @var IConfig */ - private $config; - /** @var ILogger */ - private $logger; - /** @var \OC_Defaults */ - private $defaults; + private ?TransportInterface $instance = null; + private IConfig $config; + private ILogger $logger; + private OC_Defaults $defaults; - /** - * @param IConfig $config - * @param ILogger $logger - * @param \OC_Defaults $defaults - */ public function __construct( IConfig $config, ILogger $logger, - \OC_Defaults $defaults + OC_Defaults $defaults ) { $this->config = $config; $this->logger = $logger; @@ -72,11 +71,9 @@ public function __construct( /** * Creates a new message object that can be passed to send() - * - * @return Message */ - public function createMessage() { - return new Message(new \Swift_Message()); + public function createMessage(): Message { + return new Message(new Email()); } /** @@ -89,18 +86,34 @@ public function createMessage() { * @throws \Exception In case it was not possible to send the message. (for example if an invalid mail address * has been supplied.) */ - public function send(Message $message) { - $debugMode = $this->config->getSystemValue('mail_smtpdebug', false); - + public function send(Message $message): array { if (!\is_array($message->getFrom()) || \count($message->getFrom()) === 0) { $message->setFrom([\OCP\Util::getDefaultEmailAddress($this->defaults->getName())]); } - $failedRecipients = []; - - $mailer = $this->getInstance(); - - $mailer->send($message->getSwiftMessage(), $failedRecipients); + $debugMode = $this->config->getSystemValue('mail_smtpdebug', false); + $logger = $debugMode ? new Logger() : null; + + try { + $this->getInstance($logger ?? null)->send($message->getMessage()); + } catch (TransportExceptionInterface $e) { + # in case of exception it is expected that none of the mails has been sent + $failedRecipients = []; + + $recipients = array_merge($message->getTo(), $message->getCc(), $message->getBcc()); + array_walk($recipients, static function ($value, $key) use (&$failedRecipients) { + if (is_numeric($key)) { + $failedRecipients[] = $value; + } else { + $failedRecipients[] = $key; + } + }); + + $this->logger->logException($e, ['failed-recipients' => $recipients]); + + # list of failed recipients is not added by intention to not accidentally disclose private data + throw new \RuntimeException("Failed to deliver email", 0, $e); + } $allRecipients = []; if (!empty($message->getTo())) { @@ -119,10 +132,11 @@ public function send(Message $message) { 'app' => 'core', 'from' => \json_encode($message->getFrom()), 'recipients' => \json_encode($allRecipients), - 'subject' => $message->getSubject() + 'subject' => $message->getSubject(), + 'mail_log' => ($logger !== null) ? $logger->toJSON() : null, ]); - return $failedRecipients; + return []; } /** @@ -131,9 +145,9 @@ public function send(Message $message) { * @param string $email Email address to be validated * @return bool True if the mail address is valid, false otherwise */ - public function validateMailAddress($email) { - $validator = new EmailValidator(); - return $validator->isValid($this->convertEmail($email), new RFCValidation()); + public function validateMailAddress(string $email): bool { + return (new EmailValidator()) + ->isValid($this->convertEmail($email), new RFCValidation()); } /** @@ -144,12 +158,12 @@ public function validateMailAddress($email) { * @param string $email * @return string Converted mail address if `idn_to_ascii` exists */ - protected function convertEmail($email) { + protected function convertEmail(string $email): string { if (!\function_exists('idn_to_ascii') || \strpos($email, '@') === false) { return $email; } - list($name, $domain) = \explode('@', $email, 2); + [$name, $domain] = \explode('@', $email, 2); if (\defined('INTL_IDNA_VARIANT_UTS46')) { $domain = \idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46); } else { @@ -158,80 +172,50 @@ protected function convertEmail($email) { return $name.'@'.$domain; } - /** - * Returns whatever transport is configured within the config - * - * @return \Swift_SmtpTransport|\Swift_SendmailTransport - */ - protected function getInstance() { + protected function getInstance(?LoggerInterface $logger = null): TransportInterface { if ($this->instance !== null) { return $this->instance; } $mailMode = $this->config->getSystemValue('mail_smtpmode', 'php'); if ($mailMode === 'smtp') { - $instance = $this->getSmtpInstance(); + $transport = $this->getSmtpInstance($logger ?? null); } else { - // FIXME: Move into the return statement but requires proper testing - // for SMTP and mail as well. Thus not really doable for a - // minor release. - $instance = new \Swift_Mailer($this->getSendMailInstance()); - } - - // Register plugins - - // Enable logger if debug mode is enabled - if ($this->config->getSystemValue('mail_smtpdebug', false)) { - $mailLogger = new \Swift_Plugins_Loggers_ArrayLogger(); - $instance->registerPlugin(new \Swift_Plugins_LoggerPlugin($mailLogger)); + $transport = $this->getSendMailInstance($logger ?? null); } - // Enable antiflood on smtp connection (defaults to 100 mails before reconnect) - $instance->registerPlugin(new \Swift_Plugins_AntiFloodPlugin()); - - $this->instance = $instance; + $this->instance = $transport; return $this->instance; } - /** - * Returns the SMTP transport - * - * @return \Swift_SmtpTransport - */ - protected function getSmtpInstance() { - $transport = new \Swift_SmtpTransport(); - $transport->setTimeout($this->config->getSystemValue('mail_smtptimeout', 10)); - $transport->setHost($this->config->getSystemValue('mail_smtphost', '127.0.0.1')); - $transport->setPort($this->config->getSystemValue('mail_smtpport', 25)); + protected function getSmtpInstance(?LoggerInterface $logger): EsmtpTransport { + $timeout = $this->config->getSystemValue('mail_smtptimeout', 10); + $host = $this->config->getSystemValue('mail_smtphost', '127.0.0.1'); + $port = $this->config->getSystemValue('mail_smtpport', 25); + $smtpSecurity = $this->config->getSystemValue('mail_smtpsecure', ''); + $tls = $smtpSecurity === 'ssl' ? true : null; + $transport = new EsmtpTransport($host, $port, $tls, null, $logger); if ($this->config->getSystemValue('mail_smtpauth', false)) { $transport->setUsername($this->config->getSystemValue('mail_smtpname', '')); $transport->setPassword($this->config->getSystemValue('mail_smtppassword', '')); - $transport->setAuthMode($this->config->getSystemValue('mail_smtpauthtype', 'LOGIN')); } - $smtpSecurity = $this->config->getSystemValue('mail_smtpsecure', ''); - if (!empty($smtpSecurity)) { - $transport->setEncryption($smtpSecurity); + $stream = $transport->getStream(); + if ($stream instanceof SocketStream) { + $stream->setTimeout($timeout); } - $transport->start(); + return $transport; } - /** - * Returns the sendmail transport - * - * @return \Swift_SendmailTransport - */ - protected function getSendMailInstance() { - switch ($this->config->getSystemValue('mail_smtpmode', 'sendmail')) { - case 'qmail': - $binaryPath = '/var/qmail/bin/sendmail'; - break; - default: - $binaryPath = '/usr/sbin/sendmail'; - break; + protected function getSendMailInstance(?LoggerInterface $logger = null): SendmailTransport { + $i = $this->config->getSystemValue('mail_smtpmode', 'sendmail'); + if ($i === 'qmail') { + $binaryPath = '/var/qmail/bin/sendmail'; + } else { + $binaryPath = '/usr/sbin/sendmail'; } - return new \Swift_SendmailTransport($binaryPath . ' -bs'); + return new SendmailTransport($binaryPath . ' -bs', null, $logger); } } diff --git a/lib/private/Mail/Message.php b/lib/private/Mail/Message.php index 12633ff76ad6..88ce719abf07 100644 --- a/lib/private/Mail/Message.php +++ b/lib/private/Mail/Message.php @@ -22,55 +22,38 @@ namespace OC\Mail; -use Swift_Message; +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Email; /** - * Class Message provides a wrapper around SwiftMail + * Class Message provides a wrapper around Symfony\Component\Mime\Email * * @package OC\Mail */ class Message { - /** @var Swift_Message */ - private $swiftMessage; + private Email $message; + private array $from = []; + private array $replyTo = []; + private array $to = []; + private array $cc = []; + private array $bcc = []; - /** - * @param Swift_Message $swiftMessage - */ - public function __construct(Swift_Message $swiftMessage) { - $this->swiftMessage = $swiftMessage; + public function __construct(Email $swiftMessage) { + $this->message = $swiftMessage; } /** - * SwiftMailer does currently not work with IDN domains, this function therefore converts the domains - * FIXME: Remove this once SwiftMailer supports IDN - * * @param array $addresses Array of mail addresses, key will get converted - * @return array Converted addresses if `idn_to_ascii` exists + * @return Address[] Converted addresses if `idn_to_ascii` exists */ - protected function convertAddresses($addresses) { - if (!\function_exists('idn_to_ascii')) { - return $addresses; - } - + protected function convertAddresses(array $addresses): array { $convertedAddresses = []; foreach ($addresses as $email => $readableName) { - if (!\is_numeric($email)) { - list($name, $domain) = \explode('@', $email, 2); - if (\defined('INTL_IDNA_VARIANT_UTS46')) { - $domain = \idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46); - } else { - $domain = \idn_to_ascii($domain); - } - $convertedAddresses[$name.'@'.$domain] = $readableName; + if (\is_numeric($email)) { + $convertedAddresses[] = new Address($readableName); } else { - list($name, $domain) = \explode('@', $readableName, 2); - if (\defined('INTL_IDNA_VARIANT_UTS46')) { - $domain = \idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46); - } else { - $domain = \idn_to_ascii($domain); - } - $convertedAddresses[$email] = $name.'@'.$domain; + $convertedAddresses[] = new Address($email, $readableName ?? ''); } } @@ -83,12 +66,11 @@ protected function convertAddresses($addresses) { * If no "From" address is used \OC\Mail\Mailer will use mail_from_address and mail_domain from config.php * * @param array $addresses Example: array('sender@domain.org', 'other@domain.org' => 'A name') - * @return $this */ - public function setFrom(array $addresses) { - $addresses = $this->convertAddresses($addresses); + public function setFrom(array $addresses): Message { + $this->message->from(...$this->convertAddresses($addresses)); - $this->swiftMessage->setFrom($addresses); + $this->from = $addresses; return $this; } @@ -97,42 +79,36 @@ public function setFrom(array $addresses) { * * @return array */ - public function getFrom() { - return $this->swiftMessage->getFrom(); + public function getFrom(): array { + return $this->from; } /** * Set the Reply-To address of this message - * - * @param array $addresses - * @return $this */ - public function setReplyTo(array $addresses) { - $addresses = $this->convertAddresses($addresses); + public function setReplyTo(array $addresses): Message { + $this->message->replyTo(...$this->convertAddresses($addresses)); - $this->swiftMessage->setReplyTo($addresses); + $this->replyTo = $addresses; return $this; } /** * Returns the Reply-To address of this message - * - * @return array */ - public function getReplyTo() { - return $this->swiftMessage->getReplyTo(); + public function getReplyTo(): array { + return $this->replyTo; } /** - * Set the to addresses of this message. + * Set the to-addresses of this message. * * @param array $recipients Example: array('recipient@domain.org', 'other@domain.org' => 'A name') - * @return $this */ - public function setTo(array $recipients) { - $recipients = $this->convertAddresses($recipients); + public function setTo(array $recipients): Message { + $this->message->to(...$this->convertAddresses($recipients)); - $this->swiftMessage->setTo($recipients); + $this->to = $recipients; return $this; } @@ -141,82 +117,68 @@ public function setTo(array $recipients) { * * @return array */ - public function getTo() { - return $this->swiftMessage->getTo(); + public function getTo(): array { + return $this->to; } /** * Set the CC recipients of this message. * * @param array $recipients Example: array('recipient@domain.org', 'other@domain.org' => 'A name') - * @return $this */ - public function setCc(array $recipients) { - $recipients = $this->convertAddresses($recipients); + public function setCc(array $recipients): Message { + $this->message->cc(...$this->convertAddresses($recipients)); - $this->swiftMessage->setCc($recipients); + $this->cc = $recipients; return $this; } /** * Get the cc address of this message. - * - * @return array */ - public function getCc() { - return $this->swiftMessage->getCc(); + public function getCc(): array { + return $this->cc; } /** * Set the BCC recipients of this message. * * @param array $recipients Example: array('recipient@domain.org', 'other@domain.org' => 'A name') - * @return $this */ - public function setBcc(array $recipients) { - $recipients = $this->convertAddresses($recipients); + public function setBcc(array $recipients): Message { + $this->message->bcc(...$this->convertAddresses($recipients)); - $this->swiftMessage->setBcc($recipients); + $this->bcc = $recipients; return $this; } /** * Get the Bcc address of this message. - * - * @return array */ - public function getBcc() { - return $this->swiftMessage->getBcc(); + public function getBcc(): array { + return $this->bcc; } /** * Set the subject of this message. - * - * @param $subject - * @return $this */ - public function setSubject($subject) { - $this->swiftMessage->setSubject($subject); + public function setSubject(string $subject): Message { + $this->message->subject($subject); return $this; } /** - * Get the from subject of this message. - * - * @return string + * Get the subject of this message. */ - public function getSubject() { - return $this->swiftMessage->getSubject(); + public function getSubject(): string { + return $this->message->getSubject(); } /** * Set the plain-text body of this message. - * - * @param string $body - * @return $this */ - public function setPlainBody($body) { - $this->swiftMessage->setBody($body); + public function setPlainBody(string $body): Message { + $this->message->text($body); return $this; } @@ -225,8 +187,8 @@ public function setPlainBody($body) { * * @return string */ - public function getPlainBody() { - return $this->swiftMessage->getBody(); + public function getPlainBody(): string { + return $this->message->getTextBody() ?? ''; } /** @@ -235,26 +197,27 @@ public function getPlainBody() { * @param string $body * @return $this */ - public function setHtmlBody($body) { - $this->swiftMessage->addPart($body, 'text/html'); + public function setHtmlBody(string $body): Message { + $this->message->html($body); return $this; } - /** - * Get's the underlying SwiftMessage - * @return Swift_Message - */ - public function getSwiftMessage() { - return $this->swiftMessage; + public function getMessage(): Email { + return $this->message; } - /** - * @param string $body - * @param string $contentType - * @return $this - */ - public function setBody($body, $contentType) { - $this->swiftMessage->setBody($body, $contentType); + public function setBody(string $body, string $contentType): Message { + if ($contentType === 'text/html') { + $this->message->html($body); + } else { + $this->message->text($body); + } + + return $this; + } + + public function attach($body, string $name = null, string $contentType = null): self { + $this->message->attach($body, $name, $contentType); return $this; } } diff --git a/lib/public/Mail/IMailer.php b/lib/public/Mail/IMailer.php index 333e961d071c..070dc42737ea 100644 --- a/lib/public/Mail/IMailer.php +++ b/lib/public/Mail/IMailer.php @@ -51,7 +51,7 @@ interface IMailer { * @return Message * @since 8.1.0 */ - public function createMessage(); + public function createMessage(): Message; /** * Send the specified message. Also sets the from address to the value defined in config.php @@ -64,7 +64,7 @@ public function createMessage(); * has been supplied.) * @since 8.1.0 */ - public function send(Message $message); + public function send(Message $message): array; /** * Checks if an e-mail address is valid @@ -73,5 +73,5 @@ public function send(Message $message); * @return bool True if the mail address is valid, false otherwise * @since 8.1.0 */ - public function validateMailAddress($email); + public function validateMailAddress(string $email): bool; } diff --git a/settings/Controller/MailSettingsController.php b/settings/Controller/MailSettingsController.php index 9e6e3287e1d4..9f89b3e311d2 100644 --- a/settings/Controller/MailSettingsController.php +++ b/settings/Controller/MailSettingsController.php @@ -85,7 +85,6 @@ public function __construct( * @param string $mail_smtpmode * @param string $mail_smtpsecure * @param string $mail_smtphost - * @param string $mail_smtpauthtype * @param int $mail_smtpauth * @param string $mail_smtpport * @return array @@ -96,7 +95,6 @@ public function setMailSettings( $mail_smtpmode, $mail_smtpsecure, $mail_smtphost, - $mail_smtpauthtype, $mail_smtpauth, $mail_smtpport ) { @@ -162,36 +160,35 @@ public function sendTestMail() { $email = $this->userSession->getUser()->getEMailAddress(); } - if (!empty($email)) { - try { - $message = $this->mailer->createMessage(); - $message->setTo([$email => $this->userSession->getUser()->getDisplayName()]); - $message->setFrom([$this->defaultMailAddress]); - $message->setSubject($this->l10n->t('test email settings')); - $message->setPlainBody('If you received this email, the settings seem to be correct.'); - $this->mailer->send($message); - } catch (\Exception $e) { - return [ - 'data' => [ - 'message' => (string) $this->l10n->t('A problem occurred while sending the email. Please revise your settings. (Error: %s)', [$e->getMessage()]), - ], - 'status' => 'error', - ]; - } + if (empty($email)) { + return ['data' => + ['message' => + (string) $this->l10n->t('You need to set your user email before being able to send test emails.'), + ], + 'status' => 'error' + ]; + } + try { + $message = $this->mailer->createMessage(); + $message->setTo([$email => $this->userSession->getUser()->getDisplayName()]); + $message->setFrom([$this->defaultMailAddress]); + $message->setSubject($this->l10n->t('test email settings')); + $message->setPlainBody('If you received this email, the settings seem to be correct.'); + $this->mailer->send($message); return ['data' => ['message' => (string) $this->l10n->t('Email sent') ], 'status' => 'success' ]; + } catch (\Exception $e) { + return [ + 'data' => [ + 'message' => (string) $this->l10n->t('A problem occurred while sending the email. Please revise your settings. (Error: %s)', [$e->getMessage()]), + ], + 'status' => 'error', + ]; } - - return ['data' => - ['message' => - (string) $this->l10n->t('You need to set your user email before being able to send test emails.'), - ], - 'status' => 'error' - ]; } } diff --git a/settings/Panels/Admin/Mail.php b/settings/Panels/Admin/Mail.php index 4a86cb8b34fc..0c75b63a9f61 100644 --- a/settings/Panels/Admin/Mail.php +++ b/settings/Panels/Admin/Mail.php @@ -61,7 +61,6 @@ public function getPanel() { $template->assign('mail_smtpsecure', $this->config->getSystemValue("mail_smtpsecure", '')); $template->assign('mail_smtphost', $this->config->getSystemValue("mail_smtphost", '')); $template->assign('mail_smtpport', $this->config->getSystemValue("mail_smtpport", '')); - $template->assign('mail_smtpauthtype', $this->config->getSystemValue("mail_smtpauthtype", '')); $template->assign('mail_smtpauth', $this->config->getSystemValue("mail_smtpauth", false)); $template->assign('mail_smtpname', $this->config->getSystemValue("mail_smtpname", '')); $template->assign('mail_user_email', $this->userSession->getUser()->getEMailAddress()); diff --git a/settings/templates/panels/admin/mail.php b/settings/templates/panels/admin/mail.php index 8604fb4d9a25..45394de5ffc9 100644 --- a/settings/templates/panels/admin/mail.php +++ b/settings/templates/panels/admin/mail.php @@ -1,15 +1,8 @@ $l->t('None'), - 'LOGIN' => $l->t('Login'), - 'PLAIN' => $l->t('Plain'), - 'NTLM' => $l->t('NT LAN Manager'), -]; $mail_smtpsecure = [ '' => $l->t('None'), 'ssl' => $l->t('SSL/TLS'), - 'tls' => $l->t('STARTTLS'), ]; $mail_smtpmode = [ 'php', @@ -80,17 +73,6 @@

'.htmlspecialchars($td, ENT_NOQUOTES, 'UTF-8').'
'; - } - - return "\n$th:\n".$td."\n"; - } - - /** - * Create a HTML h1 tag - * - * @param string $title Text to be in the h1 - */ - protected function addTitle(string $title, Level $level): string - { - $title = htmlspecialchars($title, ENT_NOQUOTES, 'UTF-8'); - - return '

'.$title.'

'; - } - - /** - * Formats a log record. - * - * @return string The formatted record - */ - public function format(LogRecord $record): string - { - $output = $this->addTitle($record->level->getName(), $record->level); - $output .= ''; - - $output .= $this->addRow('Message', $record->message); - $output .= $this->addRow('Time', $this->formatDate($record->datetime)); - $output .= $this->addRow('Channel', $record->channel); - if (\count($record->context) > 0) { - $embeddedTable = '
'; - foreach ($record->context as $key => $value) { - $embeddedTable .= $this->addRow((string) $key, $this->convertToString($value)); - } - $embeddedTable .= '
'; - $output .= $this->addRow('Context', $embeddedTable, false); - } - if (\count($record->extra) > 0) { - $embeddedTable = ''; - foreach ($record->extra as $key => $value) { - $embeddedTable .= $this->addRow((string) $key, $this->convertToString($value)); - } - $embeddedTable .= '
'; - $output .= $this->addRow('Extra', $embeddedTable, false); - } - - return $output.''; - } - - /** - * Formats a set of log records. - * - * @return string The formatted set of records - */ - public function formatBatch(array $records): string - { - $message = ''; - foreach ($records as $record) { - $message .= $this->format($record); - } - - return $message; - } - - /** - * @param mixed $data - */ - protected function convertToString($data): string - { - if (null === $data || is_scalar($data)) { - return (string) $data; - } - - $data = $this->normalize($data); - - return Utils::jsonEncode($data, JSON_PRETTY_PRINT | Utils::DEFAULT_JSON_FLAGS, true); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php deleted file mode 100644 index 039c38dded8c..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php +++ /dev/null @@ -1,213 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Formatter; - -use Stringable; -use Throwable; -use Monolog\LogRecord; - -/** - * Encodes whatever record data is passed to it as json - * - * This can be useful to log to databases or remote APIs - * - * @author Jordi Boggiano - */ -class JsonFormatter extends NormalizerFormatter -{ - public const BATCH_MODE_JSON = 1; - public const BATCH_MODE_NEWLINES = 2; - - /** @var self::BATCH_MODE_* */ - protected int $batchMode; - - protected bool $appendNewline; - - protected bool $ignoreEmptyContextAndExtra; - - protected bool $includeStacktraces = false; - - /** - * @param self::BATCH_MODE_* $batchMode - * - * @throws \RuntimeException If the function json_encode does not exist - */ - public function __construct(int $batchMode = self::BATCH_MODE_JSON, bool $appendNewline = true, bool $ignoreEmptyContextAndExtra = false, bool $includeStacktraces = false) - { - $this->batchMode = $batchMode; - $this->appendNewline = $appendNewline; - $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra; - $this->includeStacktraces = $includeStacktraces; - - parent::__construct(); - } - - /** - * The batch mode option configures the formatting style for - * multiple records. By default, multiple records will be - * formatted as a JSON-encoded array. However, for - * compatibility with some API endpoints, alternative styles - * are available. - */ - public function getBatchMode(): int - { - return $this->batchMode; - } - - /** - * True if newlines are appended to every formatted record - */ - public function isAppendingNewlines(): bool - { - return $this->appendNewline; - } - - /** - * @inheritDoc - */ - public function format(LogRecord $record): string - { - $normalized = parent::format($record); - - if (isset($normalized['context']) && $normalized['context'] === []) { - if ($this->ignoreEmptyContextAndExtra) { - unset($normalized['context']); - } else { - $normalized['context'] = new \stdClass; - } - } - if (isset($normalized['extra']) && $normalized['extra'] === []) { - if ($this->ignoreEmptyContextAndExtra) { - unset($normalized['extra']); - } else { - $normalized['extra'] = new \stdClass; - } - } - - return $this->toJson($normalized, true) . ($this->appendNewline ? "\n" : ''); - } - - /** - * @inheritDoc - */ - public function formatBatch(array $records): string - { - return match ($this->batchMode) { - static::BATCH_MODE_NEWLINES => $this->formatBatchNewlines($records), - default => $this->formatBatchJson($records), - }; - } - - public function includeStacktraces(bool $include = true): self - { - $this->includeStacktraces = $include; - - return $this; - } - - /** - * Return a JSON-encoded array of records. - * - * @phpstan-param LogRecord[] $records - */ - protected function formatBatchJson(array $records): string - { - return $this->toJson($this->normalize($records), true); - } - - /** - * Use new lines to separate records instead of a - * JSON-encoded array. - * - * @phpstan-param LogRecord[] $records - */ - protected function formatBatchNewlines(array $records): string - { - $oldNewline = $this->appendNewline; - $this->appendNewline = false; - $formatted = array_map(fn (LogRecord $record) => $this->format($record), $records); - $this->appendNewline = $oldNewline; - - return implode("\n", $formatted); - } - - /** - * Normalizes given $data. - * - * @return null|scalar|array|object - */ - protected function normalize(mixed $data, int $depth = 0): mixed - { - if ($depth > $this->maxNormalizeDepth) { - return 'Over '.$this->maxNormalizeDepth.' levels deep, aborting normalization'; - } - - if (is_array($data)) { - $normalized = []; - - $count = 1; - foreach ($data as $key => $value) { - if ($count++ > $this->maxNormalizeItemCount) { - $normalized['...'] = 'Over '.$this->maxNormalizeItemCount.' items ('.count($data).' total), aborting normalization'; - break; - } - - $normalized[$key] = $this->normalize($value, $depth + 1); - } - - return $normalized; - } - - if (is_object($data)) { - if ($data instanceof \DateTimeInterface) { - return $this->formatDate($data); - } - - if ($data instanceof Throwable) { - return $this->normalizeException($data, $depth); - } - - // if the object has specific json serializability we want to make sure we skip the __toString treatment below - if ($data instanceof \JsonSerializable) { - return $data; - } - - if ($data instanceof Stringable) { - return $data->__toString(); - } - - return $data; - } - - if (is_resource($data)) { - return parent::normalize($data); - } - - return $data; - } - - /** - * Normalizes given exception with or without its own stack trace based on - * `includeStacktraces` property. - * - * @inheritDoc - */ - protected function normalizeException(Throwable $e, int $depth = 0): array - { - $data = parent::normalizeException($e, $depth); - if (!$this->includeStacktraces) { - unset($data['trace']); - } - - return $data; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LineFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LineFormatter.php deleted file mode 100644 index 76ac7700b5c9..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LineFormatter.php +++ /dev/null @@ -1,244 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Formatter; - -use Closure; -use Monolog\Utils; -use Monolog\LogRecord; - -/** - * Formats incoming records into a one-line string - * - * This is especially useful for logging to files - * - * @author Jordi Boggiano - * @author Christophe Coevoet - */ -class LineFormatter extends NormalizerFormatter -{ - public const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"; - - protected string $format; - protected bool $allowInlineLineBreaks; - protected bool $ignoreEmptyContextAndExtra; - protected bool $includeStacktraces; - protected Closure|null $stacktracesParser = null; - - /** - * @param string|null $format The format of the message - * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format - * @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries - * - * @throws \RuntimeException If the function json_encode does not exist - */ - public function __construct(?string $format = null, ?string $dateFormat = null, bool $allowInlineLineBreaks = false, bool $ignoreEmptyContextAndExtra = false, bool $includeStacktraces = false) - { - $this->format = $format === null ? static::SIMPLE_FORMAT : $format; - $this->allowInlineLineBreaks = $allowInlineLineBreaks; - $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra; - $this->includeStacktraces($includeStacktraces); - parent::__construct($dateFormat); - } - - public function includeStacktraces(bool $include = true, ?Closure $parser = null): self - { - $this->includeStacktraces = $include; - if ($this->includeStacktraces) { - $this->allowInlineLineBreaks = true; - $this->stacktracesParser = $parser; - } - - return $this; - } - - public function allowInlineLineBreaks(bool $allow = true): self - { - $this->allowInlineLineBreaks = $allow; - - return $this; - } - - public function ignoreEmptyContextAndExtra(bool $ignore = true): self - { - $this->ignoreEmptyContextAndExtra = $ignore; - - return $this; - } - - /** - * @inheritDoc - */ - public function format(LogRecord $record): string - { - $vars = parent::format($record); - - $output = $this->format; - foreach ($vars['extra'] as $var => $val) { - if (false !== strpos($output, '%extra.'.$var.'%')) { - $output = str_replace('%extra.'.$var.'%', $this->stringify($val), $output); - unset($vars['extra'][$var]); - } - } - - foreach ($vars['context'] as $var => $val) { - if (false !== strpos($output, '%context.'.$var.'%')) { - $output = str_replace('%context.'.$var.'%', $this->stringify($val), $output); - unset($vars['context'][$var]); - } - } - - if ($this->ignoreEmptyContextAndExtra) { - if (\count($vars['context']) === 0) { - unset($vars['context']); - $output = str_replace('%context%', '', $output); - } - - if (\count($vars['extra']) === 0) { - unset($vars['extra']); - $output = str_replace('%extra%', '', $output); - } - } - - foreach ($vars as $var => $val) { - if (false !== strpos($output, '%'.$var.'%')) { - $output = str_replace('%'.$var.'%', $this->stringify($val), $output); - } - } - - // remove leftover %extra.xxx% and %context.xxx% if any - if (false !== strpos($output, '%')) { - $output = preg_replace('/%(?:extra|context)\..+?%/', '', $output); - if (null === $output) { - $pcreErrorCode = preg_last_error(); - - throw new \RuntimeException('Failed to run preg_replace: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode)); - } - } - - return $output; - } - - public function formatBatch(array $records): string - { - $message = ''; - foreach ($records as $record) { - $message .= $this->format($record); - } - - return $message; - } - - /** - * @param mixed $value - */ - public function stringify($value): string - { - return $this->replaceNewlines($this->convertToString($value)); - } - - protected function normalizeException(\Throwable $e, int $depth = 0): string - { - $str = $this->formatException($e); - - if (($previous = $e->getPrevious()) instanceof \Throwable) { - do { - $depth++; - if ($depth > $this->maxNormalizeDepth) { - $str .= '\n[previous exception] Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization'; - break; - } - - $str .= "\n[previous exception] " . $this->formatException($previous); - } while ($previous = $previous->getPrevious()); - } - - return $str; - } - - /** - * @param mixed $data - */ - protected function convertToString($data): string - { - if (null === $data || is_bool($data)) { - return var_export($data, true); - } - - if (is_scalar($data)) { - return (string) $data; - } - - return $this->toJson($data, true); - } - - protected function replaceNewlines(string $str): string - { - if ($this->allowInlineLineBreaks) { - if (0 === strpos($str, '{')) { - $str = preg_replace('/(?getCode(); - if ($e instanceof \SoapFault) { - if (isset($e->faultcode)) { - $str .= ' faultcode: ' . $e->faultcode; - } - - if (isset($e->faultactor)) { - $str .= ' faultactor: ' . $e->faultactor; - } - - if (isset($e->detail)) { - if (is_string($e->detail)) { - $str .= ' detail: ' . $e->detail; - } elseif (is_object($e->detail) || is_array($e->detail)) { - $str .= ' detail: ' . $this->toJson($e->detail, true); - } - } - } - $str .= '): ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine() . ')'; - - if ($this->includeStacktraces) { - $str .= $this->stacktracesParser($e); - } - - return $str; - } - - private function stacktracesParser(\Throwable $e): string - { - $trace = $e->getTraceAsString(); - - if ($this->stacktracesParser !== null) { - $trace = $this->stacktracesParserCustom($trace); - } - - return "\n[stacktrace]\n" . $trace . "\n"; - } - - private function stacktracesParserCustom(string $trace): string - { - return implode("\n", array_filter(array_map($this->stacktracesParser, explode("\n", $trace)))); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php deleted file mode 100644 index 5f0b6a453fc2..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php +++ /dev/null @@ -1,47 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Formatter; - -use Monolog\LogRecord; - -/** - * Encodes message information into JSON in a format compatible with Loggly. - * - * @author Adam Pancutt - */ -class LogglyFormatter extends JsonFormatter -{ - /** - * Overrides the default batch mode to new lines for compatibility with the - * Loggly bulk API. - */ - public function __construct(int $batchMode = self::BATCH_MODE_NEWLINES, bool $appendNewline = false) - { - parent::__construct($batchMode, $appendNewline); - } - - /** - * Appends the 'timestamp' parameter for indexing by Loggly. - * - * @see https://www.loggly.com/docs/automated-parsing/#json - * @see \Monolog\Formatter\JsonFormatter::format() - */ - protected function normalizeRecord(LogRecord $record): array - { - $recordData = parent::normalizeRecord($record); - - $recordData["timestamp"] = $record->datetime->format("Y-m-d\TH:i:s.uO"); - unset($recordData["datetime"]); - - return $recordData; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php deleted file mode 100644 index 10ad0d9c0a68..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php +++ /dev/null @@ -1,64 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Formatter; - -use Monolog\LogRecord; - -/** - * Encodes message information into JSON in a format compatible with Logmatic. - * - * @author Julien Breux - */ -class LogmaticFormatter extends JsonFormatter -{ - protected const MARKERS = ["sourcecode", "php"]; - - protected string $hostname = ''; - - protected string $appName = ''; - - public function setHostname(string $hostname): self - { - $this->hostname = $hostname; - - return $this; - } - - public function setAppName(string $appName): self - { - $this->appName = $appName; - - return $this; - } - - /** - * Appends the 'hostname' and 'appname' parameter for indexing by Logmatic. - * - * @see http://doc.logmatic.io/docs/basics-to-send-data - * @see \Monolog\Formatter\JsonFormatter::format() - */ - public function normalizeRecord(LogRecord $record): array - { - $record = parent::normalizeRecord($record); - - if ($this->hostname !== '') { - $record["hostname"] = $this->hostname; - } - if ($this->appName !== '') { - $record["appname"] = $this->appName; - } - - $record["@marker"] = static::MARKERS; - - return $record; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php deleted file mode 100644 index abee3cd13770..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php +++ /dev/null @@ -1,102 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Formatter; - -use Monolog\LogRecord; - -/** - * Serializes a log message to Logstash Event Format - * - * @see https://www.elastic.co/products/logstash - * @see https://github.com/elastic/logstash/blob/master/logstash-core/src/main/java/org/logstash/Event.java - * - * @author Tim Mower - */ -class LogstashFormatter extends NormalizerFormatter -{ - /** - * @var string the name of the system for the Logstash log message, used to fill the @source field - */ - protected string $systemName; - - /** - * @var string an application name for the Logstash log message, used to fill the @type field - */ - protected string $applicationName; - - /** - * @var string the key for 'extra' fields from the Monolog record - */ - protected string $extraKey; - - /** - * @var string the key for 'context' fields from the Monolog record - */ - protected string $contextKey; - - /** - * @param string $applicationName The application that sends the data, used as the "type" field of logstash - * @param string|null $systemName The system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine - * @param string $extraKey The key for extra keys inside logstash "fields", defaults to extra - * @param string $contextKey The key for context keys inside logstash "fields", defaults to context - * - * @throws \RuntimeException If the function json_encode does not exist - */ - public function __construct(string $applicationName, ?string $systemName = null, string $extraKey = 'extra', string $contextKey = 'context') - { - // logstash requires a ISO 8601 format date with optional millisecond precision. - parent::__construct('Y-m-d\TH:i:s.uP'); - - $this->systemName = $systemName === null ? (string) gethostname() : $systemName; - $this->applicationName = $applicationName; - $this->extraKey = $extraKey; - $this->contextKey = $contextKey; - } - - /** - * @inheritDoc - */ - public function format(LogRecord $record): string - { - $recordData = parent::format($record); - - $message = [ - '@timestamp' => $recordData['datetime'], - '@version' => 1, - 'host' => $this->systemName, - ]; - if (isset($recordData['message'])) { - $message['message'] = $recordData['message']; - } - if (isset($recordData['channel'])) { - $message['type'] = $recordData['channel']; - $message['channel'] = $recordData['channel']; - } - if (isset($recordData['level_name'])) { - $message['level'] = $recordData['level_name']; - } - if (isset($recordData['level'])) { - $message['monolog_level'] = $recordData['level']; - } - if ('' !== $this->applicationName) { - $message['type'] = $this->applicationName; - } - if (\count($recordData['extra']) > 0) { - $message[$this->extraKey] = $recordData['extra']; - } - if (\count($recordData['context']) > 0) { - $message[$this->contextKey] = $recordData['context']; - } - - return $this->toJson($message) . "\n"; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php deleted file mode 100644 index a3bdd4f87234..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php +++ /dev/null @@ -1,160 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Formatter; - -use MongoDB\BSON\Type; -use MongoDB\BSON\UTCDateTime; -use Monolog\Utils; -use Monolog\LogRecord; - -/** - * Formats a record for use with the MongoDBHandler. - * - * @author Florian Plattner - */ -class MongoDBFormatter implements FormatterInterface -{ - private bool $exceptionTraceAsString; - private int $maxNestingLevel; - private bool $isLegacyMongoExt; - - /** - * @param int $maxNestingLevel 0 means infinite nesting, the $record itself is level 1, $record->context is 2 - * @param bool $exceptionTraceAsString set to false to log exception traces as a sub documents instead of strings - */ - public function __construct(int $maxNestingLevel = 3, bool $exceptionTraceAsString = true) - { - $this->maxNestingLevel = max($maxNestingLevel, 0); - $this->exceptionTraceAsString = $exceptionTraceAsString; - - $this->isLegacyMongoExt = extension_loaded('mongodb') && version_compare((string) phpversion('mongodb'), '1.1.9', '<='); - } - - /** - * @inheritDoc - * - * @return mixed[] - */ - public function format(LogRecord $record): array - { - /** @var mixed[] $res */ - $res = $this->formatArray($record->toArray()); - - return $res; - } - - /** - * @inheritDoc - * - * @return array - */ - public function formatBatch(array $records): array - { - $formatted = []; - foreach ($records as $key => $record) { - $formatted[$key] = $this->format($record); - } - - return $formatted; - } - - /** - * @param mixed[] $array - * @return mixed[]|string Array except when max nesting level is reached then a string "[...]" - */ - protected function formatArray(array $array, int $nestingLevel = 0) - { - if ($this->maxNestingLevel > 0 && $nestingLevel > $this->maxNestingLevel) { - return '[...]'; - } - - foreach ($array as $name => $value) { - if ($value instanceof \DateTimeInterface) { - $array[$name] = $this->formatDate($value, $nestingLevel + 1); - } elseif ($value instanceof \Throwable) { - $array[$name] = $this->formatException($value, $nestingLevel + 1); - } elseif (is_array($value)) { - $array[$name] = $this->formatArray($value, $nestingLevel + 1); - } elseif (is_object($value) && !$value instanceof Type) { - $array[$name] = $this->formatObject($value, $nestingLevel + 1); - } - } - - return $array; - } - - /** - * @param mixed $value - * @return mixed[]|string - */ - protected function formatObject($value, int $nestingLevel) - { - $objectVars = get_object_vars($value); - $objectVars['class'] = Utils::getClass($value); - - return $this->formatArray($objectVars, $nestingLevel); - } - - /** - * @return mixed[]|string - */ - protected function formatException(\Throwable $exception, int $nestingLevel) - { - $formattedException = [ - 'class' => Utils::getClass($exception), - 'message' => $exception->getMessage(), - 'code' => (int) $exception->getCode(), - 'file' => $exception->getFile() . ':' . $exception->getLine(), - ]; - - if ($this->exceptionTraceAsString === true) { - $formattedException['trace'] = $exception->getTraceAsString(); - } else { - $formattedException['trace'] = $exception->getTrace(); - } - - return $this->formatArray($formattedException, $nestingLevel); - } - - protected function formatDate(\DateTimeInterface $value, int $nestingLevel): UTCDateTime - { - if ($this->isLegacyMongoExt) { - return $this->legacyGetMongoDbDateTime($value); - } - - return $this->getMongoDbDateTime($value); - } - - private function getMongoDbDateTime(\DateTimeInterface $value): UTCDateTime - { - return new UTCDateTime((int) floor(((float) $value->format('U.u')) * 1000)); - } - - /** - * This is needed to support MongoDB Driver v1.19 and below - * - * See https://github.com/mongodb/mongo-php-driver/issues/426 - * - * It can probably be removed in 2.1 or later once MongoDB's 1.2 is released and widely adopted - */ - private function legacyGetMongoDbDateTime(\DateTimeInterface $value): UTCDateTime - { - $milliseconds = floor(((float) $value->format('U.u')) * 1000); - - $milliseconds = (PHP_INT_SIZE == 8) //64-bit OS? - ? (int) $milliseconds - : (string) $milliseconds; - - // @phpstan-ignore-next-line - return new UTCDateTime($milliseconds); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php deleted file mode 100644 index b1214f073e0f..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php +++ /dev/null @@ -1,305 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Formatter; - -use Monolog\DateTimeImmutable; -use Monolog\Utils; -use Throwable; -use Monolog\LogRecord; - -/** - * Normalizes incoming records to remove objects/resources so it's easier to dump to various targets - * - * @author Jordi Boggiano - */ -class NormalizerFormatter implements FormatterInterface -{ - public const SIMPLE_DATE = "Y-m-d\TH:i:sP"; - - protected string $dateFormat; - protected int $maxNormalizeDepth = 9; - protected int $maxNormalizeItemCount = 1000; - - private int $jsonEncodeOptions = Utils::DEFAULT_JSON_FLAGS; - - /** - * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format - * @throws \RuntimeException If the function json_encode does not exist - */ - public function __construct(?string $dateFormat = null) - { - $this->dateFormat = null === $dateFormat ? static::SIMPLE_DATE : $dateFormat; - if (!function_exists('json_encode')) { - throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s NormalizerFormatter'); - } - } - - /** - * @inheritDoc - */ - public function format(LogRecord $record) - { - return $this->normalizeRecord($record); - } - - /** - * Normalize an arbitrary value to a scalar|array|null - * - * @return null|scalar|array - */ - public function normalizeValue(mixed $data): mixed - { - return $this->normalize($data); - } - - /** - * @inheritDoc - */ - public function formatBatch(array $records) - { - foreach ($records as $key => $record) { - $records[$key] = $this->format($record); - } - - return $records; - } - - public function getDateFormat(): string - { - return $this->dateFormat; - } - - public function setDateFormat(string $dateFormat): self - { - $this->dateFormat = $dateFormat; - - return $this; - } - - /** - * The maximum number of normalization levels to go through - */ - public function getMaxNormalizeDepth(): int - { - return $this->maxNormalizeDepth; - } - - public function setMaxNormalizeDepth(int $maxNormalizeDepth): self - { - $this->maxNormalizeDepth = $maxNormalizeDepth; - - return $this; - } - - /** - * The maximum number of items to normalize per level - */ - public function getMaxNormalizeItemCount(): int - { - return $this->maxNormalizeItemCount; - } - - public function setMaxNormalizeItemCount(int $maxNormalizeItemCount): self - { - $this->maxNormalizeItemCount = $maxNormalizeItemCount; - - return $this; - } - - /** - * Enables `json_encode` pretty print. - */ - public function setJsonPrettyPrint(bool $enable): self - { - if ($enable) { - $this->jsonEncodeOptions |= JSON_PRETTY_PRINT; - } else { - $this->jsonEncodeOptions &= ~JSON_PRETTY_PRINT; - } - - return $this; - } - - /** - * Provided as extension point - * - * Because normalize is called with sub-values of context data etc, normalizeRecord can be - * extended when data needs to be appended on the record array but not to other normalized data. - * - * @return array - */ - protected function normalizeRecord(LogRecord $record): array - { - /** @var array $normalized */ - $normalized = $this->normalize($record->toArray()); - - return $normalized; - } - - /** - * @return null|scalar|array - */ - protected function normalize(mixed $data, int $depth = 0): mixed - { - if ($depth > $this->maxNormalizeDepth) { - return 'Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization'; - } - - if (null === $data || is_scalar($data)) { - if (is_float($data)) { - if (is_infinite($data)) { - return ($data > 0 ? '' : '-') . 'INF'; - } - if (is_nan($data)) { - return 'NaN'; - } - } - - return $data; - } - - if (is_array($data)) { - $normalized = []; - - $count = 1; - foreach ($data as $key => $value) { - if ($count++ > $this->maxNormalizeItemCount) { - $normalized['...'] = 'Over ' . $this->maxNormalizeItemCount . ' items ('.count($data).' total), aborting normalization'; - break; - } - - $normalized[$key] = $this->normalize($value, $depth + 1); - } - - return $normalized; - } - - if ($data instanceof \DateTimeInterface) { - return $this->formatDate($data); - } - - if (is_object($data)) { - if ($data instanceof Throwable) { - return $this->normalizeException($data, $depth); - } - - if ($data instanceof \JsonSerializable) { - /** @var null|scalar|array $value */ - $value = $data->jsonSerialize(); - } elseif (method_exists($data, '__toString')) { - /** @var string $value */ - $value = $data->__toString(); - } else { - // the rest is normalized by json encoding and decoding it - /** @var null|scalar|array $value */ - $value = json_decode($this->toJson($data, true), true); - } - - return [Utils::getClass($data) => $value]; - } - - if (is_resource($data)) { - return sprintf('[resource(%s)]', get_resource_type($data)); - } - - return '[unknown('.gettype($data).')]'; - } - - /** - * @return mixed[] - */ - protected function normalizeException(Throwable $e, int $depth = 0) - { - if ($depth > $this->maxNormalizeDepth) { - return ['Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization']; - } - - if ($e instanceof \JsonSerializable) { - return (array) $e->jsonSerialize(); - } - - $data = [ - 'class' => Utils::getClass($e), - 'message' => $e->getMessage(), - 'code' => (int) $e->getCode(), - 'file' => $e->getFile().':'.$e->getLine(), - ]; - - if ($e instanceof \SoapFault) { - if (isset($e->faultcode)) { - $data['faultcode'] = $e->faultcode; - } - - if (isset($e->faultactor)) { - $data['faultactor'] = $e->faultactor; - } - - if (isset($e->detail)) { - if (is_string($e->detail)) { - $data['detail'] = $e->detail; - } elseif (is_object($e->detail) || is_array($e->detail)) { - $data['detail'] = $this->toJson($e->detail, true); - } - } - } - - $trace = $e->getTrace(); - foreach ($trace as $frame) { - if (isset($frame['file'], $frame['line'])) { - $data['trace'][] = $frame['file'].':'.$frame['line']; - } - } - - if (($previous = $e->getPrevious()) instanceof \Throwable) { - $data['previous'] = $this->normalizeException($previous, $depth + 1); - } - - return $data; - } - - /** - * Return the JSON representation of a value - * - * @param mixed $data - * @throws \RuntimeException if encoding fails and errors are not ignored - * @return string if encoding fails and ignoreErrors is true 'null' is returned - */ - protected function toJson($data, bool $ignoreErrors = false): string - { - return Utils::jsonEncode($data, $this->jsonEncodeOptions, $ignoreErrors); - } - - protected function formatDate(\DateTimeInterface $date): string - { - // in case the date format isn't custom then we defer to the custom DateTimeImmutable - // formatting logic, which will pick the right format based on whether useMicroseconds is on - if ($this->dateFormat === self::SIMPLE_DATE && $date instanceof DateTimeImmutable) { - return (string) $date; - } - - return $date->format($this->dateFormat); - } - - public function addJsonEncodeOption(int $option): self - { - $this->jsonEncodeOptions |= $option; - - return $this; - } - - public function removeJsonEncodeOption(int $option): self - { - $this->jsonEncodeOptions &= ~$option; - - return $this; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php deleted file mode 100644 index 4bc20a08cde7..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php +++ /dev/null @@ -1,49 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Formatter; - -use Monolog\LogRecord; - -/** - * Formats data into an associative array of scalar (+ null) values. - * Objects and arrays will be JSON encoded. - * - * @author Andrew Lawson - */ -class ScalarFormatter extends NormalizerFormatter -{ - /** - * @inheritDoc - * - * @phpstan-return array $record - */ - public function format(LogRecord $record): array - { - $result = []; - foreach ($record->toArray() as $key => $value) { - $result[$key] = $this->toScalar($value); - } - - return $result; - } - - protected function toScalar(mixed $value): string|int|float|bool|null - { - $normalized = $this->normalize($value); - - if (is_array($normalized)) { - return $this->toJson($normalized, true); - } - - return $normalized; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/SyslogFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/SyslogFormatter.php deleted file mode 100644 index 6ed7e92efa59..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/SyslogFormatter.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Formatter; - -use Monolog\Level; -use Monolog\LogRecord; - -/** - * Serializes a log message according to RFC 5424 - * - * @author Dalibor Karlović - * @author Renat Gabdullin - */ -class SyslogFormatter extends LineFormatter -{ - private const SYSLOG_FACILITY_USER = 1; - private const FORMAT = "<%extra.priority%>1 %datetime% %extra.hostname% %extra.app-name% %extra.procid% %channel% %extra.structured-data% %level_name%: %message% %context% %extra%\n"; - private const NILVALUE = '-'; - - private string $hostname; - private int $procid; - - public function __construct(private string $applicationName = self::NILVALUE) - { - parent::__construct(self::FORMAT, 'Y-m-d\TH:i:s.uP', true, true); - $this->hostname = (string) gethostname(); - $this->procid = (int) getmypid(); - } - - public function format(LogRecord $record): string - { - $record->extra = $this->formatExtra($record); - - return parent::format($record); - } - - /** - * @param LogRecord $record - * @return array - */ - private function formatExtra(LogRecord $record): array - { - $extra = $record->extra; - $extra['app-name'] = $this->applicationName; - $extra['hostname'] = $this->hostname; - $extra['procid'] = $this->procid; - $extra['priority'] = self::calculatePriority($record->level); - $extra['structured-data'] = self::NILVALUE; - - return $extra; - } - - private static function calculatePriority(Level $level): int - { - return (self::SYSLOG_FACILITY_USER * 8) + $level->toRFC5424Level(); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php deleted file mode 100644 index 2e28b3ab4890..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php +++ /dev/null @@ -1,139 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Formatter; - -use Monolog\Level; -use Monolog\LogRecord; - -/** - * Serializes a log message according to Wildfire's header requirements - * - * @author Eric Clemmons (@ericclemmons) - * @author Christophe Coevoet - * @author Kirill chEbba Chebunin - */ -class WildfireFormatter extends NormalizerFormatter -{ - /** - * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format - * - * @throws \RuntimeException If the function json_encode does not exist - */ - public function __construct(?string $dateFormat = null) - { - parent::__construct($dateFormat); - - // http headers do not like non-ISO-8559-1 characters - $this->removeJsonEncodeOption(JSON_UNESCAPED_UNICODE); - } - - /** - * Translates Monolog log levels to Wildfire levels. - * - * @return 'LOG'|'INFO'|'WARN'|'ERROR' - */ - private function toWildfireLevel(Level $level): string - { - return match ($level) { - Level::Debug => 'LOG', - Level::Info => 'INFO', - Level::Notice => 'INFO', - Level::Warning => 'WARN', - Level::Error => 'ERROR', - Level::Critical => 'ERROR', - Level::Alert => 'ERROR', - Level::Emergency => 'ERROR', - }; - } - - /** - * @inheritDoc - */ - public function format(LogRecord $record): string - { - // Retrieve the line and file if set and remove them from the formatted extra - $file = $line = ''; - if (isset($record->extra['file'])) { - $file = $record->extra['file']; - unset($record->extra['file']); - } - if (isset($record->extra['line'])) { - $line = $record->extra['line']; - unset($record->extra['line']); - } - - $message = ['message' => $record->message]; - $handleError = false; - if (count($record->context) > 0) { - $message['context'] = $this->normalize($record->context); - $handleError = true; - } - if (count($record->extra) > 0) { - $message['extra'] = $this->normalize($record->extra); - $handleError = true; - } - if (count($message) === 1) { - $message = reset($message); - } - - if (is_array($message) && isset($message['context']['table'])) { - $type = 'TABLE'; - $label = $record->channel .': '. $record->message; - $message = $message['context']['table']; - } else { - $type = $this->toWildfireLevel($record->level); - $label = $record->channel; - } - - // Create JSON object describing the appearance of the message in the console - $json = $this->toJson([ - [ - 'Type' => $type, - 'File' => $file, - 'Line' => $line, - 'Label' => $label, - ], - $message, - ], $handleError); - - // The message itself is a serialization of the above JSON object + it's length - return sprintf( - '%d|%s|', - strlen($json), - $json - ); - } - - /** - * @inheritDoc - * - * @phpstan-return never - */ - public function formatBatch(array $records) - { - throw new \BadMethodCallException('Batch formatting does not make sense for the WildfireFormatter'); - } - - /** - * @inheritDoc - * - * @return null|scalar|array|object - */ - protected function normalize(mixed $data, int $depth = 0): mixed - { - if (is_object($data) && !$data instanceof \DateTimeInterface) { - return $data; - } - - return parent::normalize($data, $depth); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AbstractHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AbstractHandler.php deleted file mode 100644 index 3399a54e237f..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AbstractHandler.php +++ /dev/null @@ -1,102 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Monolog\Logger; -use Monolog\ResettableInterface; -use Psr\Log\LogLevel; -use Monolog\LogRecord; - -/** - * Base Handler class providing basic level/bubble support - * - * @author Jordi Boggiano - */ -abstract class AbstractHandler extends Handler implements ResettableInterface -{ - protected Level $level = Level::Debug; - protected bool $bubble = true; - - /** - * @param int|string|Level|LogLevel::* $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - * - * @phpstan-param value-of|value-of|Level|LogLevel::* $level - */ - public function __construct(int|string|Level $level = Level::Debug, bool $bubble = true) - { - $this->setLevel($level); - $this->bubble = $bubble; - } - - /** - * @inheritDoc - */ - public function isHandling(LogRecord $record): bool - { - return $record->level->value >= $this->level->value; - } - - /** - * Sets minimum logging level at which this handler will be triggered. - * - * @param Level|LogLevel::* $level Level or level name - * - * @phpstan-param value-of|value-of|Level|LogLevel::* $level - */ - public function setLevel(int|string|Level $level): self - { - $this->level = Logger::toMonologLevel($level); - - return $this; - } - - /** - * Gets minimum logging level at which this handler will be triggered. - */ - public function getLevel(): Level - { - return $this->level; - } - - /** - * Sets the bubbling behavior. - * - * @param bool $bubble true means that this handler allows bubbling. - * false means that bubbling is not permitted. - */ - public function setBubble(bool $bubble): self - { - $this->bubble = $bubble; - - return $this; - } - - /** - * Gets the bubbling behavior. - * - * @return bool true means that this handler allows bubbling. - * false means that bubbling is not permitted. - */ - public function getBubble(): bool - { - return $this->bubble; - } - - /** - * @inheritDoc - */ - public function reset(): void - { - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php deleted file mode 100644 index de13a76bec87..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php +++ /dev/null @@ -1,60 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\LogRecord; - -/** - * Base Handler class providing the Handler structure, including processors and formatters - * - * Classes extending it should (in most cases) only implement write($record) - * - * @author Jordi Boggiano - * @author Christophe Coevoet - */ -abstract class AbstractProcessingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface -{ - use ProcessableHandlerTrait; - use FormattableHandlerTrait; - - /** - * @inheritDoc - */ - public function handle(LogRecord $record): bool - { - if (!$this->isHandling($record)) { - return false; - } - - if (\count($this->processors) > 0) { - $record = $this->processRecord($record); - } - - $record->formatted = $this->getFormatter()->format($record); - - $this->write($record); - - return false === $this->bubble; - } - - /** - * Writes the (already formatted) record down to the log of the implementing handler - */ - abstract protected function write(LogRecord $record): void; - - public function reset(): void - { - parent::reset(); - - $this->resetProcessors(); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php deleted file mode 100644 index 695a1c07f4f2..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php +++ /dev/null @@ -1,95 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Monolog\Formatter\FormatterInterface; -use Monolog\Formatter\LineFormatter; - -/** - * Common syslog functionality - */ -abstract class AbstractSyslogHandler extends AbstractProcessingHandler -{ - protected int $facility; - - /** - * List of valid log facility names. - * @var array - */ - protected array $facilities = [ - 'auth' => \LOG_AUTH, - 'authpriv' => \LOG_AUTHPRIV, - 'cron' => \LOG_CRON, - 'daemon' => \LOG_DAEMON, - 'kern' => \LOG_KERN, - 'lpr' => \LOG_LPR, - 'mail' => \LOG_MAIL, - 'news' => \LOG_NEWS, - 'syslog' => \LOG_SYSLOG, - 'user' => \LOG_USER, - 'uucp' => \LOG_UUCP, - ]; - - /** - * Translates Monolog log levels to syslog log priorities. - */ - protected function toSyslogPriority(Level $level): int - { - return $level->toRFC5424Level(); - } - - /** - * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant - */ - public function __construct(string|int $facility = \LOG_USER, int|string|Level $level = Level::Debug, bool $bubble = true) - { - parent::__construct($level, $bubble); - - if (!defined('PHP_WINDOWS_VERSION_BUILD')) { - $this->facilities['local0'] = \LOG_LOCAL0; - $this->facilities['local1'] = \LOG_LOCAL1; - $this->facilities['local2'] = \LOG_LOCAL2; - $this->facilities['local3'] = \LOG_LOCAL3; - $this->facilities['local4'] = \LOG_LOCAL4; - $this->facilities['local5'] = \LOG_LOCAL5; - $this->facilities['local6'] = \LOG_LOCAL6; - $this->facilities['local7'] = \LOG_LOCAL7; - } else { - $this->facilities['local0'] = 128; // LOG_LOCAL0 - $this->facilities['local1'] = 136; // LOG_LOCAL1 - $this->facilities['local2'] = 144; // LOG_LOCAL2 - $this->facilities['local3'] = 152; // LOG_LOCAL3 - $this->facilities['local4'] = 160; // LOG_LOCAL4 - $this->facilities['local5'] = 168; // LOG_LOCAL5 - $this->facilities['local6'] = 176; // LOG_LOCAL6 - $this->facilities['local7'] = 184; // LOG_LOCAL7 - } - - // convert textual description of facility to syslog constant - if (is_string($facility) && array_key_exists(strtolower($facility), $this->facilities)) { - $facility = $this->facilities[strtolower($facility)]; - } elseif (!in_array($facility, array_values($this->facilities), true)) { - throw new \UnexpectedValueException('Unknown facility value "'.$facility.'" given'); - } - - $this->facility = $facility; - } - - /** - * @inheritDoc - */ - protected function getDefaultFormatter(): FormatterInterface - { - return new LineFormatter('%channel%.%level_name%: %message% %context% %extra%'); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AmqpHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AmqpHandler.php deleted file mode 100644 index 72265d4baca0..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/AmqpHandler.php +++ /dev/null @@ -1,159 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Monolog\Formatter\FormatterInterface; -use Monolog\Formatter\JsonFormatter; -use PhpAmqpLib\Message\AMQPMessage; -use PhpAmqpLib\Channel\AMQPChannel; -use AMQPExchange; -use Monolog\LogRecord; - -class AmqpHandler extends AbstractProcessingHandler -{ - protected AMQPExchange|AMQPChannel $exchange; - - /** @var array */ - private array $extraAttributes = []; - - protected string $exchangeName; - - /** - * @param AMQPExchange|AMQPChannel $exchange AMQPExchange (php AMQP ext) or PHP AMQP lib channel, ready for use - * @param string|null $exchangeName Optional exchange name, for AMQPChannel (PhpAmqpLib) only - */ - public function __construct(AMQPExchange|AMQPChannel $exchange, ?string $exchangeName = null, int|string|Level $level = Level::Debug, bool $bubble = true) - { - if ($exchange instanceof AMQPChannel) { - $this->exchangeName = (string) $exchangeName; - } elseif ($exchangeName !== null) { - @trigger_error('The $exchangeName parameter can only be passed when using PhpAmqpLib, if using an AMQPExchange instance configure it beforehand', E_USER_DEPRECATED); - } - $this->exchange = $exchange; - - parent::__construct($level, $bubble); - } - - /** - * @return array - */ - public function getExtraAttributes(): array - { - return $this->extraAttributes; - } - - /** - * Configure extra attributes to pass to the AMQPExchange (if you are using the amqp extension) - * - * @param array $extraAttributes One of content_type, content_encoding, - * message_id, user_id, app_id, delivery_mode, - * priority, timestamp, expiration, type - * or reply_to, headers. - * @return $this - */ - public function setExtraAttributes(array $extraAttributes): self - { - $this->extraAttributes = $extraAttributes; - return $this; - } - - /** - * @inheritDoc - */ - protected function write(LogRecord $record): void - { - $data = $record->formatted; - $routingKey = $this->getRoutingKey($record); - - if ($this->exchange instanceof AMQPExchange) { - $attributes = [ - 'delivery_mode' => 2, - 'content_type' => 'application/json', - ]; - if (\count($this->extraAttributes) > 0) { - $attributes = array_merge($attributes, $this->extraAttributes); - } - $this->exchange->publish( - $data, - $routingKey, - 0, - $attributes - ); - } else { - $this->exchange->basic_publish( - $this->createAmqpMessage($data), - $this->exchangeName, - $routingKey - ); - } - } - - /** - * @inheritDoc - */ - public function handleBatch(array $records): void - { - if ($this->exchange instanceof AMQPExchange) { - parent::handleBatch($records); - - return; - } - - foreach ($records as $record) { - if (!$this->isHandling($record)) { - continue; - } - - $record = $this->processRecord($record); - $data = $this->getFormatter()->format($record); - - $this->exchange->batch_basic_publish( - $this->createAmqpMessage($data), - $this->exchangeName, - $this->getRoutingKey($record) - ); - } - - $this->exchange->publish_batch(); - } - - /** - * Gets the routing key for the AMQP exchange - */ - protected function getRoutingKey(LogRecord $record): string - { - $routingKey = sprintf('%s.%s', $record->level->name, $record->channel); - - return strtolower($routingKey); - } - - private function createAmqpMessage(string $data): AMQPMessage - { - $attributes = [ - 'delivery_mode' => 2, - 'content_type' => 'application/json', - ]; - if (\count($this->extraAttributes) > 0) { - $attributes = array_merge($attributes, $this->extraAttributes); - } - return new AMQPMessage($data, $attributes); - } - - /** - * @inheritDoc - */ - protected function getDefaultFormatter(): FormatterInterface - { - return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php deleted file mode 100644 index 5930ca488e69..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php +++ /dev/null @@ -1,301 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Formatter\FormatterInterface; -use Monolog\Formatter\LineFormatter; -use Monolog\Utils; -use Monolog\LogRecord; -use Monolog\Level; - -use function count; -use function headers_list; -use function stripos; - -/** - * Handler sending logs to browser's javascript console with no browser extension required - * - * @author Olivier Poitrey - */ -class BrowserConsoleHandler extends AbstractProcessingHandler -{ - protected static bool $initialized = false; - - /** @var LogRecord[] */ - protected static array $records = []; - - protected const FORMAT_HTML = 'html'; - protected const FORMAT_JS = 'js'; - protected const FORMAT_UNKNOWN = 'unknown'; - - /** - * @inheritDoc - * - * Formatted output may contain some formatting markers to be transferred to `console.log` using the %c format. - * - * Example of formatted string: - * - * You can do [[blue text]]{color: blue} or [[green background]]{background-color: green; color: white} - */ - protected function getDefaultFormatter(): FormatterInterface - { - return new LineFormatter('[[%channel%]]{macro: autolabel} [[%level_name%]]{font-weight: bold} %message%'); - } - - /** - * @inheritDoc - */ - protected function write(LogRecord $record): void - { - // Accumulate records - static::$records[] = $record; - - // Register shutdown handler if not already done - if (!static::$initialized) { - static::$initialized = true; - $this->registerShutdownFunction(); - } - } - - /** - * Convert records to javascript console commands and send it to the browser. - * This method is automatically called on PHP shutdown if output is HTML or Javascript. - */ - public static function send(): void - { - $format = static::getResponseFormat(); - if ($format === self::FORMAT_UNKNOWN) { - return; - } - - if (count(static::$records) > 0) { - if ($format === self::FORMAT_HTML) { - static::writeOutput(''); - } else { // js format - static::writeOutput(self::generateScript()); - } - static::resetStatic(); - } - } - - public function close(): void - { - self::resetStatic(); - } - - public function reset(): void - { - parent::reset(); - - self::resetStatic(); - } - - /** - * Forget all logged records - */ - public static function resetStatic(): void - { - static::$records = []; - } - - /** - * Wrapper for register_shutdown_function to allow overriding - */ - protected function registerShutdownFunction(): void - { - if (PHP_SAPI !== 'cli') { - register_shutdown_function(['Monolog\Handler\BrowserConsoleHandler', 'send']); - } - } - - /** - * Wrapper for echo to allow overriding - */ - protected static function writeOutput(string $str): void - { - echo $str; - } - - /** - * Checks the format of the response - * - * If Content-Type is set to application/javascript or text/javascript -> js - * If Content-Type is set to text/html, or is unset -> html - * If Content-Type is anything else -> unknown - * - * @return string One of 'js', 'html' or 'unknown' - * @phpstan-return self::FORMAT_* - */ - protected static function getResponseFormat(): string - { - // Check content type - foreach (headers_list() as $header) { - if (stripos($header, 'content-type:') === 0) { - return static::getResponseFormatFromContentType($header); - } - } - - return self::FORMAT_HTML; - } - - /** - * @return string One of 'js', 'html' or 'unknown' - * @phpstan-return self::FORMAT_* - */ - protected static function getResponseFormatFromContentType(string $contentType): string - { - // This handler only works with HTML and javascript outputs - // text/javascript is obsolete in favour of application/javascript, but still used - if (stripos($contentType, 'application/javascript') !== false || stripos($contentType, 'text/javascript') !== false) { - return self::FORMAT_JS; - } - - if (stripos($contentType, 'text/html') !== false) { - return self::FORMAT_HTML; - } - - return self::FORMAT_UNKNOWN; - } - - private static function generateScript(): string - { - $script = []; - foreach (static::$records as $record) { - $context = self::dump('Context', $record->context); - $extra = self::dump('Extra', $record->extra); - - if (\count($context) === 0 && \count($extra) === 0) { - $script[] = self::call_array(self::getConsoleMethodForLevel($record->level), self::handleStyles($record->formatted)); - } else { - $script = array_merge( - $script, - [self::call_array('groupCollapsed', self::handleStyles($record->formatted))], - $context, - $extra, - [self::call('groupEnd')] - ); - } - } - - return "(function (c) {if (c && c.groupCollapsed) {\n" . implode("\n", $script) . "\n}})(console);"; - } - - private static function getConsoleMethodForLevel(Level $level): string - { - return match ($level) { - Level::Debug => 'debug', - Level::Info, Level::Notice => 'info', - Level::Warning => 'warn', - Level::Error, Level::Critical, Level::Alert, Level::Emergency => 'error', - }; - } - - /** - * @return string[] - */ - private static function handleStyles(string $formatted): array - { - $args = []; - $format = '%c' . $formatted; - preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); - - foreach (array_reverse($matches) as $match) { - $args[] = '"font-weight: normal"'; - $args[] = self::quote(self::handleCustomStyles($match[2][0], $match[1][0])); - - $pos = $match[0][1]; - $format = Utils::substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . Utils::substr($format, $pos + strlen($match[0][0])); - } - - $args[] = self::quote('font-weight: normal'); - $args[] = self::quote($format); - - return array_reverse($args); - } - - private static function handleCustomStyles(string $style, string $string): string - { - static $colors = ['blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey']; - static $labels = []; - - $style = preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function (array $m) use ($string, &$colors, &$labels) { - if (trim($m[1]) === 'autolabel') { - // Format the string as a label with consistent auto assigned background color - if (!isset($labels[$string])) { - $labels[$string] = $colors[count($labels) % count($colors)]; - } - $color = $labels[$string]; - - return "background-color: $color; color: white; border-radius: 3px; padding: 0 2px 0 2px"; - } - - return $m[1]; - }, $style); - - if (null === $style) { - $pcreErrorCode = preg_last_error(); - - throw new \RuntimeException('Failed to run preg_replace_callback: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode)); - } - - return $style; - } - - /** - * @param mixed[] $dict - * @return mixed[] - */ - private static function dump(string $title, array $dict): array - { - $script = []; - $dict = array_filter($dict); - if (\count($dict) === 0) { - return $script; - } - $script[] = self::call('log', self::quote('%c%s'), self::quote('font-weight: bold'), self::quote($title)); - foreach ($dict as $key => $value) { - $value = json_encode($value); - if (false === $value) { - $value = self::quote(''); - } - $script[] = self::call('log', self::quote('%s: %o'), self::quote((string) $key), $value); - } - - return $script; - } - - private static function quote(string $arg): string - { - return '"' . addcslashes($arg, "\"\n\\") . '"'; - } - - /** - * @param mixed $args - */ - private static function call(...$args): string - { - $method = array_shift($args); - if (!is_string($method)) { - throw new \UnexpectedValueException('Expected the first arg to be a string, got: '.var_export($method, true)); - } - - return self::call_array($method, $args); - } - - /** - * @param mixed[] $args - */ - private static function call_array(string $method, array $args): string - { - return 'c.' . $method . '(' . implode(', ', $args) . ');'; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/BufferHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/BufferHandler.php deleted file mode 100644 index ff89faa8a31f..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/BufferHandler.php +++ /dev/null @@ -1,165 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Monolog\ResettableInterface; -use Monolog\Formatter\FormatterInterface; -use Monolog\LogRecord; - -/** - * Buffers all records until closing the handler and then pass them as batch. - * - * This is useful for a MailHandler to send only one mail per request instead of - * sending one per log message. - * - * @author Christophe Coevoet - */ -class BufferHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface -{ - use ProcessableHandlerTrait; - - protected HandlerInterface $handler; - - protected int $bufferSize = 0; - - protected int $bufferLimit; - - protected bool $flushOnOverflow; - - /** @var LogRecord[] */ - protected array $buffer = []; - - protected bool $initialized = false; - - /** - * @param HandlerInterface $handler Handler. - * @param int $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. - * @param bool $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded - */ - public function __construct(HandlerInterface $handler, int $bufferLimit = 0, int|string|Level $level = Level::Debug, bool $bubble = true, bool $flushOnOverflow = false) - { - parent::__construct($level, $bubble); - $this->handler = $handler; - $this->bufferLimit = $bufferLimit; - $this->flushOnOverflow = $flushOnOverflow; - } - - /** - * @inheritDoc - */ - public function handle(LogRecord $record): bool - { - if ($record->level->isLowerThan($this->level)) { - return false; - } - - if (!$this->initialized) { - // __destructor() doesn't get called on Fatal errors - register_shutdown_function([$this, 'close']); - $this->initialized = true; - } - - if ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) { - if ($this->flushOnOverflow) { - $this->flush(); - } else { - array_shift($this->buffer); - $this->bufferSize--; - } - } - - if (\count($this->processors) > 0) { - $record = $this->processRecord($record); - } - - $this->buffer[] = $record; - $this->bufferSize++; - - return false === $this->bubble; - } - - public function flush(): void - { - if ($this->bufferSize === 0) { - return; - } - - $this->handler->handleBatch($this->buffer); - $this->clear(); - } - - public function __destruct() - { - // suppress the parent behavior since we already have register_shutdown_function() - // to call close(), and the reference contained there will prevent this from being - // GC'd until the end of the request - } - - /** - * @inheritDoc - */ - public function close(): void - { - $this->flush(); - - $this->handler->close(); - } - - /** - * Clears the buffer without flushing any messages down to the wrapped handler. - */ - public function clear(): void - { - $this->bufferSize = 0; - $this->buffer = []; - } - - public function reset(): void - { - $this->flush(); - - parent::reset(); - - $this->resetProcessors(); - - if ($this->handler instanceof ResettableInterface) { - $this->handler->reset(); - } - } - - /** - * @inheritDoc - */ - public function setFormatter(FormatterInterface $formatter): HandlerInterface - { - if ($this->handler instanceof FormattableHandlerInterface) { - $this->handler->setFormatter($formatter); - - return $this; - } - - throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); - } - - /** - * @inheritDoc - */ - public function getFormatter(): FormatterInterface - { - if ($this->handler instanceof FormattableHandlerInterface) { - return $this->handler->getFormatter(); - } - - throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php deleted file mode 100644 index 2f7f21d5f427..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php +++ /dev/null @@ -1,192 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Formatter\ChromePHPFormatter; -use Monolog\Formatter\FormatterInterface; -use Monolog\Level; -use Monolog\Utils; -use Monolog\LogRecord; -use Monolog\DateTimeImmutable; - -/** - * Handler sending logs to the ChromePHP extension (http://www.chromephp.com/) - * - * This also works out of the box with Firefox 43+ - * - * @author Christophe Coevoet - */ -class ChromePHPHandler extends AbstractProcessingHandler -{ - use WebRequestRecognizerTrait; - - /** - * Version of the extension - */ - protected const VERSION = '4.0'; - - /** - * Header name - */ - protected const HEADER_NAME = 'X-ChromeLogger-Data'; - - /** - * Regular expression to detect supported browsers (matches any Chrome, or Firefox 43+) - */ - protected const USER_AGENT_REGEX = '{\b(?:Chrome/\d+(?:\.\d+)*|HeadlessChrome|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}'; - - protected static bool $initialized = false; - - /** - * Tracks whether we sent too much data - * - * Chrome limits the headers to 4KB, so when we sent 3KB we stop sending - */ - protected static bool $overflowed = false; - - /** @var mixed[] */ - protected static array $json = [ - 'version' => self::VERSION, - 'columns' => ['label', 'log', 'backtrace', 'type'], - 'rows' => [], - ]; - - protected static bool $sendHeaders = true; - - /** - * @throws \RuntimeException If the function json_encode does not exist - */ - public function __construct(int|string|Level $level = Level::Debug, bool $bubble = true) - { - parent::__construct($level, $bubble); - if (!function_exists('json_encode')) { - throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s ChromePHPHandler'); - } - } - - /** - * @inheritDoc - */ - public function handleBatch(array $records): void - { - if (!$this->isWebRequest()) { - return; - } - - $messages = []; - - foreach ($records as $record) { - if ($record->level < $this->level) { - continue; - } - - $message = $this->processRecord($record); - $messages[] = $message; - } - - if (\count($messages) > 0) { - $messages = $this->getFormatter()->formatBatch($messages); - self::$json['rows'] = array_merge(self::$json['rows'], $messages); - $this->send(); - } - } - - /** - * @inheritDoc - */ - protected function getDefaultFormatter(): FormatterInterface - { - return new ChromePHPFormatter(); - } - - /** - * Creates & sends header for a record - * - * @see sendHeader() - * @see send() - */ - protected function write(LogRecord $record): void - { - if (!$this->isWebRequest()) { - return; - } - - self::$json['rows'][] = $record->formatted; - - $this->send(); - } - - /** - * Sends the log header - * - * @see sendHeader() - */ - protected function send(): void - { - if (self::$overflowed || !self::$sendHeaders) { - return; - } - - if (!self::$initialized) { - self::$initialized = true; - - self::$sendHeaders = $this->headersAccepted(); - if (!self::$sendHeaders) { - return; - } - - self::$json['request_uri'] = $_SERVER['REQUEST_URI'] ?? ''; - } - - $json = Utils::jsonEncode(self::$json, Utils::DEFAULT_JSON_FLAGS & ~JSON_UNESCAPED_UNICODE, true); - $data = base64_encode($json); - if (strlen($data) > 3 * 1024) { - self::$overflowed = true; - - $record = new LogRecord( - message: 'Incomplete logs, chrome header size limit reached', - level: Level::Warning, - channel: 'monolog', - datetime: new DateTimeImmutable(true), - ); - self::$json['rows'][count(self::$json['rows']) - 1] = $this->getFormatter()->format($record); - $json = Utils::jsonEncode(self::$json, Utils::DEFAULT_JSON_FLAGS & ~JSON_UNESCAPED_UNICODE, true); - $data = base64_encode($json); - } - - if (trim($data) !== '') { - $this->sendHeader(static::HEADER_NAME, $data); - } - } - - /** - * Send header string to the client - */ - protected function sendHeader(string $header, string $content): void - { - if (!headers_sent() && self::$sendHeaders) { - header(sprintf('%s: %s', $header, $content)); - } - } - - /** - * Verifies if the headers are accepted by the current user agent - */ - protected function headersAccepted(): bool - { - if (!isset($_SERVER['HTTP_USER_AGENT'])) { - return false; - } - - return preg_match(static::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']) === 1; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php deleted file mode 100644 index 8d9c10e76132..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php +++ /dev/null @@ -1,97 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Formatter\FormatterInterface; -use Monolog\Formatter\JsonFormatter; -use Monolog\Level; -use Monolog\LogRecord; - -/** - * CouchDB handler - * - * @author Markus Bachmann - * @phpstan-type Options array{ - * host: string, - * port: int, - * dbname: string, - * username: string|null, - * password: string|null - * } - * @phpstan-type InputOptions array{ - * host?: string, - * port?: int, - * dbname?: string, - * username?: string|null, - * password?: string|null - * } - */ -class CouchDBHandler extends AbstractProcessingHandler -{ - /** - * @var mixed[] - * @phpstan-var Options - */ - private array $options; - - /** - * @param mixed[] $options - * - * @phpstan-param InputOptions $options - */ - public function __construct(array $options = [], int|string|Level $level = Level::Debug, bool $bubble = true) - { - $this->options = array_merge([ - 'host' => 'localhost', - 'port' => 5984, - 'dbname' => 'logger', - 'username' => null, - 'password' => null, - ], $options); - - parent::__construct($level, $bubble); - } - - /** - * @inheritDoc - */ - protected function write(LogRecord $record): void - { - $basicAuth = null; - if (null !== $this->options['username'] && null !== $this->options['password']) { - $basicAuth = sprintf('%s:%s@', $this->options['username'], $this->options['password']); - } - - $url = 'http://'.$basicAuth.$this->options['host'].':'.$this->options['port'].'/'.$this->options['dbname']; - $context = stream_context_create([ - 'http' => [ - 'method' => 'POST', - 'content' => $record->formatted, - 'ignore_errors' => true, - 'max_redirects' => 0, - 'header' => 'Content-type: application/json', - ], - ]); - - if (false === @file_get_contents($url, false, $context)) { - throw new \RuntimeException(sprintf('Could not connect to %s', $url)); - } - } - - /** - * @inheritDoc - */ - protected function getDefaultFormatter(): FormatterInterface - { - return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/CubeHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/CubeHandler.php deleted file mode 100644 index 8388f5ade74d..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/CubeHandler.php +++ /dev/null @@ -1,167 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Monolog\Utils; -use Monolog\LogRecord; - -/** - * Logs to Cube. - * - * @link https://github.com/square/cube/wiki - * @author Wan Chen - * @deprecated Since 2.8.0 and 3.2.0, Cube appears abandoned and thus we will drop this handler in Monolog 4 - */ -class CubeHandler extends AbstractProcessingHandler -{ - private ?\Socket $udpConnection = null; - private ?\CurlHandle $httpConnection = null; - private string $scheme; - private string $host; - private int $port; - /** @var string[] */ - private array $acceptedSchemes = ['http', 'udp']; - - /** - * Create a Cube handler - * - * @throws \UnexpectedValueException when given url is not a valid url. - * A valid url must consist of three parts : protocol://host:port - * Only valid protocols used by Cube are http and udp - */ - public function __construct(string $url, int|string|Level $level = Level::Debug, bool $bubble = true) - { - $urlInfo = parse_url($url); - - if ($urlInfo === false || !isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) { - throw new \UnexpectedValueException('URL "'.$url.'" is not valid'); - } - - if (!in_array($urlInfo['scheme'], $this->acceptedSchemes, true)) { - throw new \UnexpectedValueException( - 'Invalid protocol (' . $urlInfo['scheme'] . ').' - . ' Valid options are ' . implode(', ', $this->acceptedSchemes) - ); - } - - $this->scheme = $urlInfo['scheme']; - $this->host = $urlInfo['host']; - $this->port = $urlInfo['port']; - - parent::__construct($level, $bubble); - } - - /** - * Establish a connection to an UDP socket - * - * @throws \LogicException when unable to connect to the socket - * @throws MissingExtensionException when there is no socket extension - */ - protected function connectUdp(): void - { - if (!extension_loaded('sockets')) { - throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler'); - } - - $udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0); - if (false === $udpConnection) { - throw new \LogicException('Unable to create a socket'); - } - - $this->udpConnection = $udpConnection; - if (!socket_connect($this->udpConnection, $this->host, $this->port)) { - throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port); - } - } - - /** - * Establish a connection to an http server - * - * @throws \LogicException when unable to connect to the socket - * @throws MissingExtensionException when no curl extension - */ - protected function connectHttp(): void - { - if (!extension_loaded('curl')) { - throw new MissingExtensionException('The curl extension is required to use http URLs with the CubeHandler'); - } - - $httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put'); - if (false === $httpConnection) { - throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port); - } - - $this->httpConnection = $httpConnection; - curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST"); - curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true); - } - - /** - * @inheritDoc - */ - protected function write(LogRecord $record): void - { - $date = $record->datetime; - - $data = ['time' => $date->format('Y-m-d\TH:i:s.uO')]; - $context = $record->context; - - if (isset($context['type'])) { - $data['type'] = $context['type']; - unset($context['type']); - } else { - $data['type'] = $record->channel; - } - - $data['data'] = $context; - $data['data']['level'] = $record->level; - - if ($this->scheme === 'http') { - $this->writeHttp(Utils::jsonEncode($data)); - } else { - $this->writeUdp(Utils::jsonEncode($data)); - } - } - - private function writeUdp(string $data): void - { - if (null === $this->udpConnection) { - $this->connectUdp(); - } - - if (null === $this->udpConnection) { - throw new \LogicException('No UDP socket could be opened'); - } - - socket_send($this->udpConnection, $data, strlen($data), 0); - } - - private function writeHttp(string $data): void - { - if (null === $this->httpConnection) { - $this->connectHttp(); - } - - if (null === $this->httpConnection) { - throw new \LogicException('No connection could be established'); - } - - curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']'); - curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, [ - 'Content-Type: application/json', - 'Content-Length: ' . strlen('['.$data.']'), - ]); - - Curl\Util::execute($this->httpConnection, 5, false); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/Curl/Util.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/Curl/Util.php deleted file mode 100644 index 4decf0e62ed9..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/Curl/Util.php +++ /dev/null @@ -1,69 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler\Curl; - -use CurlHandle; - -/** - * This class is marked as internal and it is not under the BC promise of the package. - * - * @internal - */ -final class Util -{ - /** @var array */ - private static array $retriableErrorCodes = [ - CURLE_COULDNT_RESOLVE_HOST, - CURLE_COULDNT_CONNECT, - CURLE_HTTP_NOT_FOUND, - CURLE_READ_ERROR, - CURLE_OPERATION_TIMEOUTED, - CURLE_HTTP_POST_ERROR, - CURLE_SSL_CONNECT_ERROR, - ]; - - /** - * Executes a CURL request with optional retries and exception on failure - * - * @param CurlHandle $ch curl handler - * @return bool|string @see curl_exec - */ - public static function execute(CurlHandle $ch, int $retries = 5, bool $closeAfterDone = true) - { - while ($retries--) { - $curlResponse = curl_exec($ch); - if ($curlResponse === false) { - $curlErrno = curl_errno($ch); - - if (false === in_array($curlErrno, self::$retriableErrorCodes, true) || $retries === 0) { - $curlError = curl_error($ch); - - if ($closeAfterDone) { - curl_close($ch); - } - - throw new \RuntimeException(sprintf('Curl error (code %d): %s', $curlErrno, $curlError)); - } - - continue; - } - - if ($closeAfterDone) { - curl_close($ch); - } - - return $curlResponse; - } - - return false; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php deleted file mode 100644 index b8ec90099bb4..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php +++ /dev/null @@ -1,166 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Monolog\Logger; -use Psr\Log\LogLevel; -use Monolog\LogRecord; - -/** - * Simple handler wrapper that deduplicates log records across multiple requests - * - * It also includes the BufferHandler functionality and will buffer - * all messages until the end of the request or flush() is called. - * - * This works by storing all log records' messages above $deduplicationLevel - * to the file specified by $deduplicationStore. When further logs come in at the end of the - * request (or when flush() is called), all those above $deduplicationLevel are checked - * against the existing stored logs. If they match and the timestamps in the stored log is - * not older than $time seconds, the new log record is discarded. If no log record is new, the - * whole data set is discarded. - * - * This is mainly useful in combination with Mail handlers or things like Slack or HipChat handlers - * that send messages to people, to avoid spamming with the same message over and over in case of - * a major component failure like a database server being down which makes all requests fail in the - * same way. - * - * @author Jordi Boggiano - */ -class DeduplicationHandler extends BufferHandler -{ - protected string $deduplicationStore; - - protected Level $deduplicationLevel; - - protected int $time; - - private bool $gc = false; - - /** - * @param HandlerInterface $handler Handler. - * @param string|null $deduplicationStore The file/path where the deduplication log should be kept - * @param int|string|Level|LogLevel::* $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes - * @param int $time The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - * - * @phpstan-param value-of|value-of|Level|LogLevel::* $deduplicationLevel - */ - public function __construct(HandlerInterface $handler, ?string $deduplicationStore = null, int|string|Level $deduplicationLevel = Level::Error, int $time = 60, bool $bubble = true) - { - parent::__construct($handler, 0, Level::Debug, $bubble, false); - - $this->deduplicationStore = $deduplicationStore === null ? sys_get_temp_dir() . '/monolog-dedup-' . substr(md5(__FILE__), 0, 20) .'.log' : $deduplicationStore; - $this->deduplicationLevel = Logger::toMonologLevel($deduplicationLevel); - $this->time = $time; - } - - public function flush(): void - { - if ($this->bufferSize === 0) { - return; - } - - $passthru = null; - - foreach ($this->buffer as $record) { - if ($record->level->value >= $this->deduplicationLevel->value) { - $passthru = $passthru === true || !$this->isDuplicate($record); - if ($passthru) { - $this->appendRecord($record); - } - } - } - - // default of null is valid as well as if no record matches duplicationLevel we just pass through - if ($passthru === true || $passthru === null) { - $this->handler->handleBatch($this->buffer); - } - - $this->clear(); - - if ($this->gc) { - $this->collectLogs(); - } - } - - private function isDuplicate(LogRecord $record): bool - { - if (!file_exists($this->deduplicationStore)) { - return false; - } - - $store = file($this->deduplicationStore, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); - if (!is_array($store)) { - return false; - } - - $yesterday = time() - 86400; - $timestampValidity = $record->datetime->getTimestamp() - $this->time; - $expectedMessage = preg_replace('{[\r\n].*}', '', $record->message); - - for ($i = count($store) - 1; $i >= 0; $i--) { - list($timestamp, $level, $message) = explode(':', $store[$i], 3); - - if ($level === $record->level->getName() && $message === $expectedMessage && $timestamp > $timestampValidity) { - return true; - } - - if ($timestamp < $yesterday) { - $this->gc = true; - } - } - - return false; - } - - private function collectLogs(): void - { - if (!file_exists($this->deduplicationStore)) { - return; - } - - $handle = fopen($this->deduplicationStore, 'rw+'); - - if (false === $handle) { - throw new \RuntimeException('Failed to open file for reading and writing: ' . $this->deduplicationStore); - } - - flock($handle, LOCK_EX); - $validLogs = []; - - $timestampValidity = time() - $this->time; - - while (!feof($handle)) { - $log = fgets($handle); - if (is_string($log) && '' !== $log && substr($log, 0, 10) >= $timestampValidity) { - $validLogs[] = $log; - } - } - - ftruncate($handle, 0); - rewind($handle); - foreach ($validLogs as $log) { - fwrite($handle, $log); - } - - flock($handle, LOCK_UN); - fclose($handle); - - $this->gc = false; - } - - private function appendRecord(LogRecord $record): void - { - file_put_contents($this->deduplicationStore, $record->datetime->getTimestamp() . ':' . $record->level->getName() . ':' . preg_replace('{[\r\n].*}', '', $record->message) . "\n", FILE_APPEND); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php deleted file mode 100644 index eab9f1089d83..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php +++ /dev/null @@ -1,47 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Monolog\Formatter\NormalizerFormatter; -use Monolog\Formatter\FormatterInterface; -use Doctrine\CouchDB\CouchDBClient; -use Monolog\LogRecord; - -/** - * CouchDB handler for Doctrine CouchDB ODM - * - * @author Markus Bachmann - */ -class DoctrineCouchDBHandler extends AbstractProcessingHandler -{ - private CouchDBClient $client; - - public function __construct(CouchDBClient $client, int|string|Level $level = Level::Debug, bool $bubble = true) - { - $this->client = $client; - parent::__construct($level, $bubble); - } - - /** - * @inheritDoc - */ - protected function write(LogRecord $record): void - { - $this->client->postDocument($record->formatted); - } - - protected function getDefaultFormatter(): FormatterInterface - { - return new NormalizerFormatter; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php deleted file mode 100644 index f1c5a9590dfd..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php +++ /dev/null @@ -1,80 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Aws\Sdk; -use Aws\DynamoDb\DynamoDbClient; -use Monolog\Formatter\FormatterInterface; -use Aws\DynamoDb\Marshaler; -use Monolog\Formatter\ScalarFormatter; -use Monolog\Level; -use Monolog\LogRecord; - -/** - * Amazon DynamoDB handler (http://aws.amazon.com/dynamodb/) - * - * @link https://github.com/aws/aws-sdk-php/ - * @author Andrew Lawson - */ -class DynamoDbHandler extends AbstractProcessingHandler -{ - public const DATE_FORMAT = 'Y-m-d\TH:i:s.uO'; - - protected DynamoDbClient $client; - - protected string $table; - - protected Marshaler $marshaler; - - public function __construct(DynamoDbClient $client, string $table, int|string|Level $level = Level::Debug, bool $bubble = true) - { - $this->marshaler = new Marshaler; - - $this->client = $client; - $this->table = $table; - - parent::__construct($level, $bubble); - } - - /** - * @inheritDoc - */ - protected function write(LogRecord $record): void - { - $filtered = $this->filterEmptyFields($record->formatted); - $formatted = $this->marshaler->marshalItem($filtered); - - $this->client->putItem([ - 'TableName' => $this->table, - 'Item' => $formatted, - ]); - } - - /** - * @param mixed[] $record - * @return mixed[] - */ - protected function filterEmptyFields(array $record): array - { - return array_filter($record, function ($value) { - return [] !== $value; - }); - } - - /** - * @inheritDoc - */ - protected function getDefaultFormatter(): FormatterInterface - { - return new ScalarFormatter(self::DATE_FORMAT); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php deleted file mode 100644 index d9b85b4d0ec8..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php +++ /dev/null @@ -1,142 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Elastica\Document; -use Monolog\Formatter\FormatterInterface; -use Monolog\Formatter\ElasticaFormatter; -use Monolog\Level; -use Elastica\Client; -use Elastica\Exception\ExceptionInterface; -use Monolog\LogRecord; - -/** - * Elastic Search handler - * - * Usage example: - * - * $client = new \Elastica\Client(); - * $options = array( - * 'index' => 'elastic_index_name', - * 'type' => 'elastic_doc_type', Types have been removed in Elastica 7 - * ); - * $handler = new ElasticaHandler($client, $options); - * $log = new Logger('application'); - * $log->pushHandler($handler); - * - * @author Jelle Vink - * @phpstan-type Options array{ - * index: string, - * type: string, - * ignore_error: bool - * } - * @phpstan-type InputOptions array{ - * index?: string, - * type?: string, - * ignore_error?: bool - * } - */ -class ElasticaHandler extends AbstractProcessingHandler -{ - protected Client $client; - - /** - * @var mixed[] Handler config options - * @phpstan-var Options - */ - protected array $options; - - /** - * @param Client $client Elastica Client object - * @param mixed[] $options Handler configuration - * - * @phpstan-param InputOptions $options - */ - public function __construct(Client $client, array $options = [], int|string|Level $level = Level::Debug, bool $bubble = true) - { - parent::__construct($level, $bubble); - $this->client = $client; - $this->options = array_merge( - [ - 'index' => 'monolog', // Elastic index name - 'type' => 'record', // Elastic document type - 'ignore_error' => false, // Suppress Elastica exceptions - ], - $options - ); - } - - /** - * @inheritDoc - */ - protected function write(LogRecord $record): void - { - $this->bulkSend([$record->formatted]); - } - - /** - * @inheritDoc - */ - public function setFormatter(FormatterInterface $formatter): HandlerInterface - { - if ($formatter instanceof ElasticaFormatter) { - return parent::setFormatter($formatter); - } - - throw new \InvalidArgumentException('ElasticaHandler is only compatible with ElasticaFormatter'); - } - - /** - * @return mixed[] - * - * @phpstan-return Options - */ - public function getOptions(): array - { - return $this->options; - } - - /** - * @inheritDoc - */ - protected function getDefaultFormatter(): FormatterInterface - { - return new ElasticaFormatter($this->options['index'], $this->options['type']); - } - - /** - * @inheritDoc - */ - public function handleBatch(array $records): void - { - $documents = $this->getFormatter()->formatBatch($records); - $this->bulkSend($documents); - } - - /** - * Use Elasticsearch bulk API to send list of documents - * - * @param Document[] $documents - * - * @throws \RuntimeException - */ - protected function bulkSend(array $documents): void - { - try { - $this->client->addDocuments($documents); - } catch (ExceptionInterface $e) { - if (!$this->options['ignore_error']) { - throw new \RuntimeException("Error sending messages to Elasticsearch", 0, $e); - } - } - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ElasticsearchHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ElasticsearchHandler.php deleted file mode 100644 index 74cc7b6e6572..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ElasticsearchHandler.php +++ /dev/null @@ -1,230 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Elastic\Elasticsearch\Response\Elasticsearch; -use Throwable; -use RuntimeException; -use Monolog\Level; -use Monolog\Formatter\FormatterInterface; -use Monolog\Formatter\ElasticsearchFormatter; -use InvalidArgumentException; -use Elasticsearch\Common\Exceptions\RuntimeException as ElasticsearchRuntimeException; -use Elasticsearch\Client; -use Monolog\LogRecord; -use Elastic\Elasticsearch\Exception\InvalidArgumentException as ElasticInvalidArgumentException; -use Elastic\Elasticsearch\Client as Client8; - -/** - * Elasticsearch handler - * - * @link https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/index.html - * - * Simple usage example: - * - * $client = \Elasticsearch\ClientBuilder::create() - * ->setHosts($hosts) - * ->build(); - * - * $options = array( - * 'index' => 'elastic_index_name', - * 'type' => 'elastic_doc_type', - * ); - * $handler = new ElasticsearchHandler($client, $options); - * $log = new Logger('application'); - * $log->pushHandler($handler); - * - * @author Avtandil Kikabidze - * @phpstan-type Options array{ - * index: string, - * type: string, - * ignore_error: bool, - * op_type: 'index'|'create' - * } - * @phpstan-type InputOptions array{ - * index?: string, - * type?: string, - * ignore_error?: bool, - * op_type?: 'index'|'create' - * } - */ -class ElasticsearchHandler extends AbstractProcessingHandler -{ - protected Client|Client8 $client; - - /** - * @var mixed[] Handler config options - * @phpstan-var Options - */ - protected array $options; - - /** - * @var bool - */ - private $needsType; - - /** - * @param Client|Client8 $client Elasticsearch Client object - * @param mixed[] $options Handler configuration - * - * @phpstan-param InputOptions $options - */ - public function __construct(Client|Client8 $client, array $options = [], int|string|Level $level = Level::Debug, bool $bubble = true) - { - parent::__construct($level, $bubble); - $this->client = $client; - $this->options = array_merge( - [ - 'index' => 'monolog', // Elastic index name - 'type' => '_doc', // Elastic document type - 'ignore_error' => false, // Suppress Elasticsearch exceptions - 'op_type' => 'index', // Elastic op_type (index or create) (https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#docs-index-api-op_type) - ], - $options - ); - - if ($client instanceof Client8 || $client::VERSION[0] === '7') { - $this->needsType = false; - // force the type to _doc for ES8/ES7 - $this->options['type'] = '_doc'; - } else { - $this->needsType = true; - } - } - - /** - * @inheritDoc - */ - protected function write(LogRecord $record): void - { - $this->bulkSend([$record->formatted]); - } - - /** - * @inheritDoc - */ - public function setFormatter(FormatterInterface $formatter): HandlerInterface - { - if ($formatter instanceof ElasticsearchFormatter) { - return parent::setFormatter($formatter); - } - - throw new InvalidArgumentException('ElasticsearchHandler is only compatible with ElasticsearchFormatter'); - } - - /** - * Getter options - * - * @return mixed[] - * - * @phpstan-return Options - */ - public function getOptions(): array - { - return $this->options; - } - - /** - * @inheritDoc - */ - protected function getDefaultFormatter(): FormatterInterface - { - return new ElasticsearchFormatter($this->options['index'], $this->options['type']); - } - - /** - * @inheritDoc - */ - public function handleBatch(array $records): void - { - $documents = $this->getFormatter()->formatBatch($records); - $this->bulkSend($documents); - } - - /** - * Use Elasticsearch bulk API to send list of documents - * - * @param array> $records Records + _index/_type keys - * @throws \RuntimeException - */ - protected function bulkSend(array $records): void - { - try { - $params = [ - 'body' => [], - ]; - - foreach ($records as $record) { - $params['body'][] = [ - $this->options['op_type'] => $this->needsType ? [ - '_index' => $record['_index'], - '_type' => $record['_type'], - ] : [ - '_index' => $record['_index'], - ], - ]; - unset($record['_index'], $record['_type']); - - $params['body'][] = $record; - } - - /** @var Elasticsearch */ - $responses = $this->client->bulk($params); - - if ($responses['errors'] === true) { - throw $this->createExceptionFromResponses($responses); - } - } catch (Throwable $e) { - if (! $this->options['ignore_error']) { - throw new RuntimeException('Error sending messages to Elasticsearch', 0, $e); - } - } - } - - /** - * Creates elasticsearch exception from responses array - * - * Only the first error is converted into an exception. - * - * @param mixed[]|Elasticsearch $responses returned by $this->client->bulk() - */ - protected function createExceptionFromResponses($responses): Throwable - { - foreach ($responses['items'] ?? [] as $item) { - if (isset($item['index']['error'])) { - return $this->createExceptionFromError($item['index']['error']); - } - } - - if (class_exists(ElasticInvalidArgumentException::class)) { - return new ElasticInvalidArgumentException('Elasticsearch failed to index one or more records.'); - } - - return new ElasticsearchRuntimeException('Elasticsearch failed to index one or more records.'); - } - - /** - * Creates elasticsearch exception from error array - * - * @param mixed[] $error - */ - protected function createExceptionFromError(array $error): Throwable - { - $previous = isset($error['caused_by']) ? $this->createExceptionFromError($error['caused_by']) : null; - - if (class_exists(ElasticInvalidArgumentException::class)) { - return new ElasticInvalidArgumentException($error['type'] . ': ' . $error['reason'], 0, $previous); - } - - return new ElasticsearchRuntimeException($error['type'] . ': ' . $error['reason'], 0, $previous); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php deleted file mode 100644 index 571c439e1035..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php +++ /dev/null @@ -1,93 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Formatter\LineFormatter; -use Monolog\Formatter\FormatterInterface; -use Monolog\Level; -use Monolog\Utils; -use Monolog\LogRecord; - -/** - * Stores to PHP error_log() handler. - * - * @author Elan Ruusamäe - */ -class ErrorLogHandler extends AbstractProcessingHandler -{ - public const OPERATING_SYSTEM = 0; - public const SAPI = 4; - - protected int $messageType; - protected bool $expandNewlines; - - /** - * @param int $messageType Says where the error should go. - * @param bool $expandNewlines If set to true, newlines in the message will be expanded to be take multiple log entries - * - * @throws \InvalidArgumentException If an unsupported message type is set - */ - public function __construct(int $messageType = self::OPERATING_SYSTEM, int|string|Level $level = Level::Debug, bool $bubble = true, bool $expandNewlines = false) - { - parent::__construct($level, $bubble); - - if (false === in_array($messageType, self::getAvailableTypes(), true)) { - $message = sprintf('The given message type "%s" is not supported', print_r($messageType, true)); - - throw new \InvalidArgumentException($message); - } - - $this->messageType = $messageType; - $this->expandNewlines = $expandNewlines; - } - - /** - * @return int[] With all available types - */ - public static function getAvailableTypes(): array - { - return [ - self::OPERATING_SYSTEM, - self::SAPI, - ]; - } - - /** - * @inheritDoc - */ - protected function getDefaultFormatter(): FormatterInterface - { - return new LineFormatter('[%datetime%] %channel%.%level_name%: %message% %context% %extra%'); - } - - /** - * @inheritDoc - */ - protected function write(LogRecord $record): void - { - if (!$this->expandNewlines) { - error_log((string) $record->formatted, $this->messageType); - - return; - } - - $lines = preg_split('{[\r\n]+}', (string) $record->formatted); - if ($lines === false) { - $pcreErrorCode = preg_last_error(); - - throw new \RuntimeException('Failed to preg_split formatted string: ' . $pcreErrorCode . ' / '. Utils::pcreLastErrorMessage($pcreErrorCode)); - } - foreach ($lines as $line) { - error_log($line, $this->messageType); - } - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php deleted file mode 100644 index 1776eb517fc4..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php +++ /dev/null @@ -1,68 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Throwable; -use Monolog\LogRecord; - -/** - * Forwards records to at most one handler - * - * If a handler fails, the exception is suppressed and the record is forwarded to the next handler. - * - * As soon as one handler handles a record successfully, the handling stops there. - */ -class FallbackGroupHandler extends GroupHandler -{ - /** - * @inheritDoc - */ - public function handle(LogRecord $record): bool - { - if (\count($this->processors) > 0) { - $record = $this->processRecord($record); - } - foreach ($this->handlers as $handler) { - try { - $handler->handle($record); - break; - } catch (Throwable $e) { - // What throwable? - } - } - - return false === $this->bubble; - } - - /** - * @inheritDoc - */ - public function handleBatch(array $records): void - { - if (\count($this->processors) > 0) { - $processed = []; - foreach ($records as $record) { - $processed[] = $this->processRecord($record); - } - $records = $processed; - } - - foreach ($this->handlers as $handler) { - try { - $handler->handleBatch($records); - break; - } catch (Throwable $e) { - // What throwable? - } - } - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FilterHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FilterHandler.php deleted file mode 100644 index 00381ab4d136..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FilterHandler.php +++ /dev/null @@ -1,201 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Closure; -use Monolog\Level; -use Monolog\Logger; -use Monolog\ResettableInterface; -use Monolog\Formatter\FormatterInterface; -use Psr\Log\LogLevel; -use Monolog\LogRecord; - -/** - * Simple handler wrapper that filters records based on a list of levels - * - * It can be configured with an exact list of levels to allow, or a min/max level. - * - * @author Hennadiy Verkh - * @author Jordi Boggiano - */ -class FilterHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface, FormattableHandlerInterface -{ - use ProcessableHandlerTrait; - - /** - * Handler or factory Closure($record, $this) - * - * @phpstan-var (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface - */ - protected Closure|HandlerInterface $handler; - - /** - * Minimum level for logs that are passed to handler - * - * @var bool[] Map of Level value => true - * @phpstan-var array, true> - */ - protected array $acceptedLevels; - - /** - * Whether the messages that are handled can bubble up the stack or not - */ - protected bool $bubble; - - /** - * @phpstan-param (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface $handler - * - * @param Closure|HandlerInterface $handler Handler or factory Closure($record|null, $filterHandler). - * @param int|string|Level|array $minLevelOrList A list of levels to accept or a minimum level if maxLevel is provided - * @param int|string|Level|LogLevel::* $maxLevel Maximum level to accept, only used if $minLevelOrList is not an array - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - * - * @phpstan-param value-of|value-of|Level|LogLevel::*|array|value-of|Level|LogLevel::*> $minLevelOrList - * @phpstan-param value-of|value-of|Level|LogLevel::* $maxLevel - */ - public function __construct(Closure|HandlerInterface $handler, int|string|Level|array $minLevelOrList = Level::Debug, int|string|Level $maxLevel = Level::Emergency, bool $bubble = true) - { - $this->handler = $handler; - $this->bubble = $bubble; - $this->setAcceptedLevels($minLevelOrList, $maxLevel); - } - - /** - * @phpstan-return list List of levels - */ - public function getAcceptedLevels(): array - { - return array_map(fn (int $level) => Level::from($level), array_keys($this->acceptedLevels)); - } - - /** - * @param int|string|Level|LogLevel::*|array $minLevelOrList A list of levels to accept or a minimum level or level name if maxLevel is provided - * @param int|string|Level|LogLevel::* $maxLevel Maximum level or level name to accept, only used if $minLevelOrList is not an array - * - * @phpstan-param value-of|value-of|Level|LogLevel::*|array|value-of|Level|LogLevel::*> $minLevelOrList - * @phpstan-param value-of|value-of|Level|LogLevel::* $maxLevel - */ - public function setAcceptedLevels(int|string|Level|array $minLevelOrList = Level::Debug, int|string|Level $maxLevel = Level::Emergency): self - { - if (is_array($minLevelOrList)) { - $acceptedLevels = array_map(Logger::toMonologLevel(...), $minLevelOrList); - } else { - $minLevelOrList = Logger::toMonologLevel($minLevelOrList); - $maxLevel = Logger::toMonologLevel($maxLevel); - $acceptedLevels = array_values(array_filter(Level::cases(), fn (Level $level) => $level->value >= $minLevelOrList->value && $level->value <= $maxLevel->value)); - } - $this->acceptedLevels = []; - foreach ($acceptedLevels as $level) { - $this->acceptedLevels[$level->value] = true; - } - - return $this; - } - - /** - * @inheritDoc - */ - public function isHandling(LogRecord $record): bool - { - return isset($this->acceptedLevels[$record->level->value]); - } - - /** - * @inheritDoc - */ - public function handle(LogRecord $record): bool - { - if (!$this->isHandling($record)) { - return false; - } - - if (\count($this->processors) > 0) { - $record = $this->processRecord($record); - } - - $this->getHandler($record)->handle($record); - - return false === $this->bubble; - } - - /** - * @inheritDoc - */ - public function handleBatch(array $records): void - { - $filtered = []; - foreach ($records as $record) { - if ($this->isHandling($record)) { - $filtered[] = $record; - } - } - - if (count($filtered) > 0) { - $this->getHandler($filtered[count($filtered) - 1])->handleBatch($filtered); - } - } - - /** - * Return the nested handler - * - * If the handler was provided as a factory, this will trigger the handler's instantiation. - */ - public function getHandler(LogRecord $record = null): HandlerInterface - { - if (!$this->handler instanceof HandlerInterface) { - $handler = ($this->handler)($record, $this); - if (!$handler instanceof HandlerInterface) { - throw new \RuntimeException("The factory Closure should return a HandlerInterface"); - } - $this->handler = $handler; - } - - return $this->handler; - } - - /** - * @inheritDoc - */ - public function setFormatter(FormatterInterface $formatter): HandlerInterface - { - $handler = $this->getHandler(); - if ($handler instanceof FormattableHandlerInterface) { - $handler->setFormatter($formatter); - - return $this; - } - - throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); - } - - /** - * @inheritDoc - */ - public function getFormatter(): FormatterInterface - { - $handler = $this->getHandler(); - if ($handler instanceof FormattableHandlerInterface) { - return $handler->getFormatter(); - } - - throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); - } - - public function reset(): void - { - $this->resetProcessors(); - - if ($this->getHandler() instanceof ResettableInterface) { - $this->getHandler()->reset(); - } - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php deleted file mode 100644 index e8a1b0b0dbb6..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler\FingersCrossed; - -use Monolog\LogRecord; - -/** - * Interface for activation strategies for the FingersCrossedHandler. - * - * @author Johannes M. Schmitt - */ -interface ActivationStrategyInterface -{ - /** - * Returns whether the given record activates the handler. - */ - public function isHandlerActivated(LogRecord $record): bool; -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php deleted file mode 100644 index 383e19af962d..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php +++ /dev/null @@ -1,69 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler\FingersCrossed; - -use Monolog\Level; -use Monolog\Logger; -use Psr\Log\LogLevel; -use Monolog\LogRecord; - -/** - * Channel and Error level based monolog activation strategy. Allows to trigger activation - * based on level per channel. e.g. trigger activation on level 'ERROR' by default, except - * for records of the 'sql' channel; those should trigger activation on level 'WARN'. - * - * Example: - * - * - * $activationStrategy = new ChannelLevelActivationStrategy( - * Level::Critical, - * array( - * 'request' => Level::Alert, - * 'sensitive' => Level::Error, - * ) - * ); - * $handler = new FingersCrossedHandler(new StreamHandler('php://stderr'), $activationStrategy); - * - * - * @author Mike Meessen - */ -class ChannelLevelActivationStrategy implements ActivationStrategyInterface -{ - private Level $defaultActionLevel; - - /** - * @var array - */ - private array $channelToActionLevel; - - /** - * @param int|string|Level|LogLevel::* $defaultActionLevel The default action level to be used if the record's category doesn't match any - * @param array $channelToActionLevel An array that maps channel names to action levels. - * - * @phpstan-param value-of|value-of|Level|LogLevel::* $defaultActionLevel - * @phpstan-param array|value-of|Level|LogLevel::*> $channelToActionLevel - */ - public function __construct(int|string|Level $defaultActionLevel, array $channelToActionLevel = []) - { - $this->defaultActionLevel = Logger::toMonologLevel($defaultActionLevel); - $this->channelToActionLevel = array_map(Logger::toMonologLevel(...), $channelToActionLevel); - } - - public function isHandlerActivated(LogRecord $record): bool - { - if (isset($this->channelToActionLevel[$record->channel])) { - return $record->level->value >= $this->channelToActionLevel[$record->channel]->value; - } - - return $record->level->value >= $this->defaultActionLevel->value; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php deleted file mode 100644 index c3ca2967ab6b..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler\FingersCrossed; - -use Monolog\Level; -use Monolog\LogRecord; -use Monolog\Logger; -use Psr\Log\LogLevel; - -/** - * Error level based activation strategy. - * - * @author Johannes M. Schmitt - */ -class ErrorLevelActivationStrategy implements ActivationStrategyInterface -{ - private Level $actionLevel; - - /** - * @param int|string|Level $actionLevel Level or name or value - * - * @phpstan-param value-of|value-of|Level|LogLevel::* $actionLevel - */ - public function __construct(int|string|Level $actionLevel) - { - $this->actionLevel = Logger::toMonologLevel($actionLevel); - } - - public function isHandlerActivated(LogRecord $record): bool - { - return $record->level->value >= $this->actionLevel->value; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php deleted file mode 100644 index 1c3df386d52c..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php +++ /dev/null @@ -1,242 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Closure; -use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; -use Monolog\Handler\FingersCrossed\ActivationStrategyInterface; -use Monolog\Level; -use Monolog\Logger; -use Monolog\ResettableInterface; -use Monolog\Formatter\FormatterInterface; -use Psr\Log\LogLevel; -use Monolog\LogRecord; - -/** - * Buffers all records until a certain level is reached - * - * The advantage of this approach is that you don't get any clutter in your log files. - * Only requests which actually trigger an error (or whatever your actionLevel is) will be - * in the logs, but they will contain all records, not only those above the level threshold. - * - * You can then have a passthruLevel as well which means that at the end of the request, - * even if it did not get activated, it will still send through log records of e.g. at least a - * warning level. - * - * You can find the various activation strategies in the - * Monolog\Handler\FingersCrossed\ namespace. - * - * @author Jordi Boggiano - */ -class FingersCrossedHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface, FormattableHandlerInterface -{ - use ProcessableHandlerTrait; - - /** - * Handler or factory Closure($record, $this) - * - * @phpstan-var (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface - */ - protected Closure|HandlerInterface $handler; - - protected ActivationStrategyInterface $activationStrategy; - - protected bool $buffering = true; - - protected int $bufferSize; - - /** @var LogRecord[] */ - protected array $buffer = []; - - protected bool $stopBuffering; - - protected Level|null $passthruLevel = null; - - protected bool $bubble; - - /** - * @phpstan-param (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface $handler - * - * @param Closure|HandlerInterface $handler Handler or factory Closure($record|null, $fingersCrossedHandler). - * @param int|string|Level|LogLevel::* $activationStrategy Strategy which determines when this handler takes action, or a level name/value at which the handler is activated - * @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - * @param bool $stopBuffering Whether the handler should stop buffering after being triggered (default true) - * @param int|string|Level|LogLevel::*|null $passthruLevel Minimum level to always flush to handler on close, even if strategy not triggered - * - * @phpstan-param value-of|value-of|Level|LogLevel::*|ActivationStrategyInterface $activationStrategy - * @phpstan-param value-of|value-of|Level|LogLevel::* $passthruLevel - */ - public function __construct(Closure|HandlerInterface $handler, int|string|Level|ActivationStrategyInterface $activationStrategy = null, int $bufferSize = 0, bool $bubble = true, bool $stopBuffering = true, int|string|Level|null $passthruLevel = null) - { - if (null === $activationStrategy) { - $activationStrategy = new ErrorLevelActivationStrategy(Level::Warning); - } - - // convert simple int activationStrategy to an object - if (!$activationStrategy instanceof ActivationStrategyInterface) { - $activationStrategy = new ErrorLevelActivationStrategy($activationStrategy); - } - - $this->handler = $handler; - $this->activationStrategy = $activationStrategy; - $this->bufferSize = $bufferSize; - $this->bubble = $bubble; - $this->stopBuffering = $stopBuffering; - - if ($passthruLevel !== null) { - $this->passthruLevel = Logger::toMonologLevel($passthruLevel); - } - } - - /** - * @inheritDoc - */ - public function isHandling(LogRecord $record): bool - { - return true; - } - - /** - * Manually activate this logger regardless of the activation strategy - */ - public function activate(): void - { - if ($this->stopBuffering) { - $this->buffering = false; - } - - $this->getHandler(end($this->buffer) ?: null)->handleBatch($this->buffer); - $this->buffer = []; - } - - /** - * @inheritDoc - */ - public function handle(LogRecord $record): bool - { - if (\count($this->processors) > 0) { - $record = $this->processRecord($record); - } - - if ($this->buffering) { - $this->buffer[] = $record; - if ($this->bufferSize > 0 && count($this->buffer) > $this->bufferSize) { - array_shift($this->buffer); - } - if ($this->activationStrategy->isHandlerActivated($record)) { - $this->activate(); - } - } else { - $this->getHandler($record)->handle($record); - } - - return false === $this->bubble; - } - - /** - * @inheritDoc - */ - public function close(): void - { - $this->flushBuffer(); - - $this->getHandler()->close(); - } - - public function reset(): void - { - $this->flushBuffer(); - - $this->resetProcessors(); - - if ($this->getHandler() instanceof ResettableInterface) { - $this->getHandler()->reset(); - } - } - - /** - * Clears the buffer without flushing any messages down to the wrapped handler. - * - * It also resets the handler to its initial buffering state. - */ - public function clear(): void - { - $this->buffer = []; - $this->reset(); - } - - /** - * Resets the state of the handler. Stops forwarding records to the wrapped handler. - */ - private function flushBuffer(): void - { - if (null !== $this->passthruLevel) { - $passthruLevel = $this->passthruLevel; - $this->buffer = array_filter($this->buffer, static function ($record) use ($passthruLevel) { - return $passthruLevel->includes($record->level); - }); - if (count($this->buffer) > 0) { - $this->getHandler(end($this->buffer))->handleBatch($this->buffer); - } - } - - $this->buffer = []; - $this->buffering = true; - } - - /** - * Return the nested handler - * - * If the handler was provided as a factory, this will trigger the handler's instantiation. - */ - public function getHandler(LogRecord $record = null): HandlerInterface - { - if (!$this->handler instanceof HandlerInterface) { - $handler = ($this->handler)($record, $this); - if (!$handler instanceof HandlerInterface) { - throw new \RuntimeException("The factory Closure should return a HandlerInterface"); - } - $this->handler = $handler; - } - - return $this->handler; - } - - /** - * @inheritDoc - */ - public function setFormatter(FormatterInterface $formatter): HandlerInterface - { - $handler = $this->getHandler(); - if ($handler instanceof FormattableHandlerInterface) { - $handler->setFormatter($formatter); - - return $this; - } - - throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); - } - - /** - * @inheritDoc - */ - public function getFormatter(): FormatterInterface - { - $handler = $this->getHandler(); - if ($handler instanceof FormattableHandlerInterface) { - return $handler->getFormatter(); - } - - throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php deleted file mode 100644 index 6b9e5103a9e7..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php +++ /dev/null @@ -1,174 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Formatter\WildfireFormatter; -use Monolog\Formatter\FormatterInterface; -use Monolog\LogRecord; - -/** - * Simple FirePHP Handler (http://www.firephp.org/), which uses the Wildfire protocol. - * - * @author Eric Clemmons (@ericclemmons) - */ -class FirePHPHandler extends AbstractProcessingHandler -{ - use WebRequestRecognizerTrait; - - /** - * WildFire JSON header message format - */ - protected const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'; - - /** - * FirePHP structure for parsing messages & their presentation - */ - protected const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'; - - /** - * Must reference a "known" plugin, otherwise headers won't display in FirePHP - */ - protected const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3'; - - /** - * Header prefix for Wildfire to recognize & parse headers - */ - protected const HEADER_PREFIX = 'X-Wf'; - - /** - * Whether or not Wildfire vendor-specific headers have been generated & sent yet - */ - protected static bool $initialized = false; - - /** - * Shared static message index between potentially multiple handlers - */ - protected static int $messageIndex = 1; - - protected static bool $sendHeaders = true; - - /** - * Base header creation function used by init headers & record headers - * - * @param array $meta Wildfire Plugin, Protocol & Structure Indexes - * @param string $message Log message - * - * @return array Complete header string ready for the client as key and message as value - * - * @phpstan-return non-empty-array - */ - protected function createHeader(array $meta, string $message): array - { - $header = sprintf('%s-%s', static::HEADER_PREFIX, join('-', $meta)); - - return [$header => $message]; - } - - /** - * Creates message header from record - * - * @return array - * - * @phpstan-return non-empty-array - * - * @see createHeader() - */ - protected function createRecordHeader(LogRecord $record): array - { - // Wildfire is extensible to support multiple protocols & plugins in a single request, - // but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake. - return $this->createHeader( - [1, 1, 1, self::$messageIndex++], - $record->formatted - ); - } - - /** - * @inheritDoc - */ - protected function getDefaultFormatter(): FormatterInterface - { - return new WildfireFormatter(); - } - - /** - * Wildfire initialization headers to enable message parsing - * - * @see createHeader() - * @see sendHeader() - * - * @return array - */ - protected function getInitHeaders(): array - { - // Initial payload consists of required headers for Wildfire - return array_merge( - $this->createHeader(['Protocol', 1], static::PROTOCOL_URI), - $this->createHeader([1, 'Structure', 1], static::STRUCTURE_URI), - $this->createHeader([1, 'Plugin', 1], static::PLUGIN_URI) - ); - } - - /** - * Send header string to the client - */ - protected function sendHeader(string $header, string $content): void - { - if (!headers_sent() && self::$sendHeaders) { - header(sprintf('%s: %s', $header, $content)); - } - } - - /** - * Creates & sends header for a record, ensuring init headers have been sent prior - * - * @see sendHeader() - * @see sendInitHeaders() - */ - protected function write(LogRecord $record): void - { - if (!self::$sendHeaders || !$this->isWebRequest()) { - return; - } - - // WildFire-specific headers must be sent prior to any messages - if (!self::$initialized) { - self::$initialized = true; - - self::$sendHeaders = $this->headersAccepted(); - if (!self::$sendHeaders) { - return; - } - - foreach ($this->getInitHeaders() as $header => $content) { - $this->sendHeader($header, $content); - } - } - - $header = $this->createRecordHeader($record); - if (trim(current($header)) !== '') { - $this->sendHeader(key($header), current($header)); - } - } - - /** - * Verifies if the headers are accepted by the current user agent - */ - protected function headersAccepted(): bool - { - if (isset($_SERVER['HTTP_USER_AGENT']) && 1 === preg_match('{\bFirePHP/\d+\.\d+\b}', $_SERVER['HTTP_USER_AGENT'])) { - return true; - } - - return isset($_SERVER['HTTP_X_FIREPHP_VERSION']); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php deleted file mode 100644 index 220648223f6c..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php +++ /dev/null @@ -1,132 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Formatter\FormatterInterface; -use Monolog\Formatter\LineFormatter; -use Monolog\Level; -use Monolog\LogRecord; - -/** - * Sends logs to Fleep.io using Webhook integrations - * - * You'll need a Fleep.io account to use this handler. - * - * @see https://fleep.io/integrations/webhooks/ Fleep Webhooks Documentation - * @author Ando Roots - */ -class FleepHookHandler extends SocketHandler -{ - protected const FLEEP_HOST = 'fleep.io'; - - protected const FLEEP_HOOK_URI = '/hook/'; - - /** - * @var string Webhook token (specifies the conversation where logs are sent) - */ - protected string $token; - - /** - * Construct a new Fleep.io Handler. - * - * For instructions on how to create a new web hook in your conversations - * see https://fleep.io/integrations/webhooks/ - * - * @param string $token Webhook token - * @throws MissingExtensionException if OpenSSL is missing - */ - public function __construct( - string $token, - $level = Level::Debug, - bool $bubble = true, - bool $persistent = false, - float $timeout = 0.0, - float $writingTimeout = 10.0, - ?float $connectionTimeout = null, - ?int $chunkSize = null - ) { - if (!extension_loaded('openssl')) { - throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FleepHookHandler'); - } - - $this->token = $token; - - $connectionString = 'ssl://' . static::FLEEP_HOST . ':443'; - parent::__construct( - $connectionString, - $level, - $bubble, - $persistent, - $timeout, - $writingTimeout, - $connectionTimeout, - $chunkSize - ); - } - - /** - * Returns the default formatter to use with this handler - * - * Overloaded to remove empty context and extra arrays from the end of the log message. - * - * @return LineFormatter - */ - protected function getDefaultFormatter(): FormatterInterface - { - return new LineFormatter(null, null, true, true); - } - - /** - * Handles a log record - */ - public function write(LogRecord $record): void - { - parent::write($record); - $this->closeSocket(); - } - - /** - * @inheritDoc - */ - protected function generateDataStream(LogRecord $record): string - { - $content = $this->buildContent($record); - - return $this->buildHeader($content) . $content; - } - - /** - * Builds the header of the API Call - */ - private function buildHeader(string $content): string - { - $header = "POST " . static::FLEEP_HOOK_URI . $this->token . " HTTP/1.1\r\n"; - $header .= "Host: " . static::FLEEP_HOST . "\r\n"; - $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; - $header .= "Content-Length: " . strlen($content) . "\r\n"; - $header .= "\r\n"; - - return $header; - } - - /** - * Builds the body of API call - */ - private function buildContent(LogRecord $record): string - { - $dataArray = [ - 'message' => $record->formatted, - ]; - - return http_build_query($dataArray); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php deleted file mode 100644 index d24bec40b7e6..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php +++ /dev/null @@ -1,127 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Monolog\Utils; -use Monolog\Formatter\FlowdockFormatter; -use Monolog\Formatter\FormatterInterface; -use Monolog\LogRecord; - -/** - * Sends notifications through the Flowdock push API - * - * This must be configured with a FlowdockFormatter instance via setFormatter() - * - * Notes: - * API token - Flowdock API token - * - * @author Dominik Liebler - * @see https://www.flowdock.com/api/push - * @deprecated Since 2.9.0 and 3.3.0, Flowdock was shutdown we will thus drop this handler in Monolog 4 - */ -class FlowdockHandler extends SocketHandler -{ - protected string $apiToken; - - /** - * @throws MissingExtensionException if OpenSSL is missing - */ - public function __construct( - string $apiToken, - $level = Level::Debug, - bool $bubble = true, - bool $persistent = false, - float $timeout = 0.0, - float $writingTimeout = 10.0, - ?float $connectionTimeout = null, - ?int $chunkSize = null - ) { - if (!extension_loaded('openssl')) { - throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FlowdockHandler'); - } - - parent::__construct( - 'ssl://api.flowdock.com:443', - $level, - $bubble, - $persistent, - $timeout, - $writingTimeout, - $connectionTimeout, - $chunkSize - ); - $this->apiToken = $apiToken; - } - - /** - * @inheritDoc - */ - public function setFormatter(FormatterInterface $formatter): HandlerInterface - { - if (!$formatter instanceof FlowdockFormatter) { - throw new \InvalidArgumentException('The FlowdockHandler requires an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); - } - - return parent::setFormatter($formatter); - } - - /** - * Gets the default formatter. - */ - protected function getDefaultFormatter(): FormatterInterface - { - throw new \InvalidArgumentException('The FlowdockHandler must be configured (via setFormatter) with an instance of Monolog\Formatter\FlowdockFormatter to function correctly'); - } - - /** - * @inheritDoc - */ - protected function write(LogRecord $record): void - { - parent::write($record); - - $this->closeSocket(); - } - - /** - * @inheritDoc - */ - protected function generateDataStream(LogRecord $record): string - { - $content = $this->buildContent($record); - - return $this->buildHeader($content) . $content; - } - - /** - * Builds the body of API call - */ - private function buildContent(LogRecord $record): string - { - return Utils::jsonEncode($record->formatted); - } - - /** - * Builds the header of the API Call - */ - private function buildHeader(string $content): string - { - $header = "POST /v1/messages/team_inbox/" . $this->apiToken . " HTTP/1.1\r\n"; - $header .= "Host: api.flowdock.com\r\n"; - $header .= "Content-Type: application/json\r\n"; - $header .= "Content-Length: " . strlen($content) . "\r\n"; - $header .= "\r\n"; - - return $header; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php deleted file mode 100644 index 72da59e1c587..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Formatter\FormatterInterface; - -/** - * Interface to describe loggers that have a formatter - * - * @author Jordi Boggiano - */ -interface FormattableHandlerInterface -{ - /** - * Sets the formatter. - * - * @return HandlerInterface self - */ - public function setFormatter(FormatterInterface $formatter): HandlerInterface; - - /** - * Gets the formatter. - */ - public function getFormatter(): FormatterInterface; -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php deleted file mode 100644 index c044e0786ca6..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php +++ /dev/null @@ -1,57 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Formatter\FormatterInterface; -use Monolog\Formatter\LineFormatter; - -/** - * Helper trait for implementing FormattableInterface - * - * @author Jordi Boggiano - */ -trait FormattableHandlerTrait -{ - protected FormatterInterface|null $formatter = null; - - /** - * @inheritDoc - */ - public function setFormatter(FormatterInterface $formatter): HandlerInterface - { - $this->formatter = $formatter; - - return $this; - } - - /** - * @inheritDoc - */ - public function getFormatter(): FormatterInterface - { - if (null === $this->formatter) { - $this->formatter = $this->getDefaultFormatter(); - } - - return $this->formatter; - } - - /** - * Gets the default formatter. - * - * Overwrite this if the LineFormatter is not a good default for your handler. - */ - protected function getDefaultFormatter(): FormatterInterface - { - return new LineFormatter(); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/GelfHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/GelfHandler.php deleted file mode 100644 index ba5bb975d85b..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/GelfHandler.php +++ /dev/null @@ -1,58 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Gelf\PublisherInterface; -use Monolog\Level; -use Monolog\Formatter\GelfMessageFormatter; -use Monolog\Formatter\FormatterInterface; -use Monolog\LogRecord; - -/** - * Handler to send messages to a Graylog2 (http://www.graylog2.org) server - * - * @author Matt Lehner - * @author Benjamin Zikarsky - */ -class GelfHandler extends AbstractProcessingHandler -{ - /** - * @var PublisherInterface the publisher object that sends the message to the server - */ - protected PublisherInterface $publisher; - - /** - * @param PublisherInterface $publisher a gelf publisher object - */ - public function __construct(PublisherInterface $publisher, int|string|Level $level = Level::Debug, bool $bubble = true) - { - parent::__construct($level, $bubble); - - $this->publisher = $publisher; - } - - /** - * @inheritDoc - */ - protected function write(LogRecord $record): void - { - $this->publisher->publish($record->formatted); - } - - /** - * @inheritDoc - */ - protected function getDefaultFormatter(): FormatterInterface - { - return new GelfMessageFormatter(); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/GroupHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/GroupHandler.php deleted file mode 100644 index 854b31abf62e..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/GroupHandler.php +++ /dev/null @@ -1,130 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Formatter\FormatterInterface; -use Monolog\ResettableInterface; -use Monolog\LogRecord; - -/** - * Forwards records to multiple handlers - * - * @author Lenar Lõhmus - */ -class GroupHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface -{ - use ProcessableHandlerTrait; - - /** @var HandlerInterface[] */ - protected array $handlers; - protected bool $bubble; - - /** - * @param HandlerInterface[] $handlers Array of Handlers. - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - * - * @throws \InvalidArgumentException if an unsupported handler is set - */ - public function __construct(array $handlers, bool $bubble = true) - { - foreach ($handlers as $handler) { - if (!$handler instanceof HandlerInterface) { - throw new \InvalidArgumentException('The first argument of the GroupHandler must be an array of HandlerInterface instances.'); - } - } - - $this->handlers = $handlers; - $this->bubble = $bubble; - } - - /** - * @inheritDoc - */ - public function isHandling(LogRecord $record): bool - { - foreach ($this->handlers as $handler) { - if ($handler->isHandling($record)) { - return true; - } - } - - return false; - } - - /** - * @inheritDoc - */ - public function handle(LogRecord $record): bool - { - if (\count($this->processors) > 0) { - $record = $this->processRecord($record); - } - - foreach ($this->handlers as $handler) { - $handler->handle($record); - } - - return false === $this->bubble; - } - - /** - * @inheritDoc - */ - public function handleBatch(array $records): void - { - if (\count($this->processors) > 0) { - $processed = []; - foreach ($records as $record) { - $processed[] = $this->processRecord($record); - } - $records = $processed; - } - - foreach ($this->handlers as $handler) { - $handler->handleBatch($records); - } - } - - public function reset(): void - { - $this->resetProcessors(); - - foreach ($this->handlers as $handler) { - if ($handler instanceof ResettableInterface) { - $handler->reset(); - } - } - } - - public function close(): void - { - parent::close(); - - foreach ($this->handlers as $handler) { - $handler->close(); - } - } - - /** - * @inheritDoc - */ - public function setFormatter(FormatterInterface $formatter): HandlerInterface - { - foreach ($this->handlers as $handler) { - if ($handler instanceof FormattableHandlerInterface) { - $handler->setFormatter($formatter); - } - } - - return $this; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/Handler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/Handler.php deleted file mode 100644 index e89f969b8ffd..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/Handler.php +++ /dev/null @@ -1,62 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -/** - * Base Handler class providing basic close() support as well as handleBatch - * - * @author Jordi Boggiano - */ -abstract class Handler implements HandlerInterface -{ - /** - * @inheritDoc - */ - public function handleBatch(array $records): void - { - foreach ($records as $record) { - $this->handle($record); - } - } - - /** - * @inheritDoc - */ - public function close(): void - { - } - - public function __destruct() - { - try { - $this->close(); - } catch (\Throwable $e) { - // do nothing - } - } - - public function __sleep() - { - $this->close(); - - $reflClass = new \ReflectionClass($this); - - $keys = []; - foreach ($reflClass->getProperties() as $reflProp) { - if (!$reflProp->isStatic()) { - $keys[] = $reflProp->getName(); - } - } - - return $keys; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/HandlerInterface.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/HandlerInterface.php deleted file mode 100644 index 83905c323d15..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/HandlerInterface.php +++ /dev/null @@ -1,76 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\LogRecord; - -/** - * Interface that all Monolog Handlers must implement - * - * @author Jordi Boggiano - */ -interface HandlerInterface -{ - /** - * Checks whether the given record will be handled by this handler. - * - * This is mostly done for performance reasons, to avoid calling processors for nothing. - * - * Handlers should still check the record levels within handle(), returning false in isHandling() - * is no guarantee that handle() will not be called, and isHandling() might not be called - * for a given record. - * - * @param LogRecord $record Partial log record having only a level initialized - */ - public function isHandling(LogRecord $record): bool; - - /** - * Handles a record. - * - * All records may be passed to this method, and the handler should discard - * those that it does not want to handle. - * - * The return value of this function controls the bubbling process of the handler stack. - * Unless the bubbling is interrupted (by returning true), the Logger class will keep on - * calling further handlers in the stack with a given log record. - * - * @param LogRecord $record The record to handle - * @return bool true means that this handler handled the record, and that bubbling is not permitted. - * false means the record was either not processed or that this handler allows bubbling. - */ - public function handle(LogRecord $record): bool; - - /** - * Handles a set of records at once. - * - * @param array $records The records to handle - */ - public function handleBatch(array $records): void; - - /** - * Closes the handler. - * - * Ends a log cycle and frees all resources used by the handler. - * - * Closing a Handler means flushing all buffers and freeing any open resources/handles. - * - * Implementations have to be idempotent (i.e. it should be possible to call close several times without breakage) - * and ideally handlers should be able to reopen themselves on handle() after they have been closed. - * - * This is useful at the end of a request and will be called automatically when the object - * is destroyed if you extend Monolog\Handler\Handler. - * - * If you are thinking of calling this method yourself, most likely you should be - * calling ResettableInterface::reset instead. Have a look. - */ - public function close(): void; -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php deleted file mode 100644 index 541ec2541a23..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php +++ /dev/null @@ -1,134 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\ResettableInterface; -use Monolog\Formatter\FormatterInterface; -use Monolog\LogRecord; - -/** - * This simple wrapper class can be used to extend handlers functionality. - * - * Example: A custom filtering that can be applied to any handler. - * - * Inherit from this class and override handle() like this: - * - * public function handle(LogRecord $record) - * { - * if ($record meets certain conditions) { - * return false; - * } - * return $this->handler->handle($record); - * } - * - * @author Alexey Karapetov - */ -class HandlerWrapper implements HandlerInterface, ProcessableHandlerInterface, FormattableHandlerInterface, ResettableInterface -{ - protected HandlerInterface $handler; - - public function __construct(HandlerInterface $handler) - { - $this->handler = $handler; - } - - /** - * @inheritDoc - */ - public function isHandling(LogRecord $record): bool - { - return $this->handler->isHandling($record); - } - - /** - * @inheritDoc - */ - public function handle(LogRecord $record): bool - { - return $this->handler->handle($record); - } - - /** - * @inheritDoc - */ - public function handleBatch(array $records): void - { - $this->handler->handleBatch($records); - } - - /** - * @inheritDoc - */ - public function close(): void - { - $this->handler->close(); - } - - /** - * @inheritDoc - */ - public function pushProcessor(callable $callback): HandlerInterface - { - if ($this->handler instanceof ProcessableHandlerInterface) { - $this->handler->pushProcessor($callback); - - return $this; - } - - throw new \LogicException('The wrapped handler does not implement ' . ProcessableHandlerInterface::class); - } - - /** - * @inheritDoc - */ - public function popProcessor(): callable - { - if ($this->handler instanceof ProcessableHandlerInterface) { - return $this->handler->popProcessor(); - } - - throw new \LogicException('The wrapped handler does not implement ' . ProcessableHandlerInterface::class); - } - - /** - * @inheritDoc - */ - public function setFormatter(FormatterInterface $formatter): HandlerInterface - { - if ($this->handler instanceof FormattableHandlerInterface) { - $this->handler->setFormatter($formatter); - - return $this; - } - - throw new \LogicException('The wrapped handler does not implement ' . FormattableHandlerInterface::class); - } - - /** - * @inheritDoc - */ - public function getFormatter(): FormatterInterface - { - if ($this->handler instanceof FormattableHandlerInterface) { - return $this->handler->getFormatter(); - } - - throw new \LogicException('The wrapped handler does not implement ' . FormattableHandlerInterface::class); - } - - public function reset(): void - { - if ($this->handler instanceof ResettableInterface) { - $this->handler->reset(); - } - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php deleted file mode 100644 index 418f2ba03d1c..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php +++ /dev/null @@ -1,75 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Monolog\Utils; -use Monolog\LogRecord; - -/** - * IFTTTHandler uses cURL to trigger IFTTT Maker actions - * - * Register a secret key and trigger/event name at https://ifttt.com/maker - * - * value1 will be the channel from monolog's Logger constructor, - * value2 will be the level name (ERROR, WARNING, ..) - * value3 will be the log record's message - * - * @author Nehal Patel - */ -class IFTTTHandler extends AbstractProcessingHandler -{ - private string $eventName; - private string $secretKey; - - /** - * @param string $eventName The name of the IFTTT Maker event that should be triggered - * @param string $secretKey A valid IFTTT secret key - * - * @throws MissingExtensionException If the curl extension is missing - */ - public function __construct(string $eventName, string $secretKey, int|string|Level $level = Level::Error, bool $bubble = true) - { - if (!extension_loaded('curl')) { - throw new MissingExtensionException('The curl extension is needed to use the IFTTTHandler'); - } - - $this->eventName = $eventName; - $this->secretKey = $secretKey; - - parent::__construct($level, $bubble); - } - - /** - * @inheritDoc - */ - public function write(LogRecord $record): void - { - $postData = [ - "value1" => $record->channel, - "value2" => $record["level_name"], - "value3" => $record->message, - ]; - $postString = Utils::jsonEncode($postData); - - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, "https://maker.ifttt.com/trigger/" . $this->eventName . "/with/key/" . $this->secretKey); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $postString); - curl_setopt($ch, CURLOPT_HTTPHEADER, [ - "Content-Type: application/json", - ]); - - Curl\Util::execute($ch); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php deleted file mode 100644 index abb2f88f7cf6..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php +++ /dev/null @@ -1,74 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Monolog\LogRecord; - -/** - * Inspired on LogEntriesHandler. - * - * @author Robert Kaufmann III - * @author Gabriel Machado - */ -class InsightOpsHandler extends SocketHandler -{ - protected string $logToken; - - /** - * @param string $token Log token supplied by InsightOps - * @param string $region Region where InsightOps account is hosted. Could be 'us' or 'eu'. - * @param bool $useSSL Whether or not SSL encryption should be used - * - * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing - */ - public function __construct( - string $token, - string $region = 'us', - bool $useSSL = true, - $level = Level::Debug, - bool $bubble = true, - bool $persistent = false, - float $timeout = 0.0, - float $writingTimeout = 10.0, - ?float $connectionTimeout = null, - ?int $chunkSize = null - ) { - if ($useSSL && !extension_loaded('openssl')) { - throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for InsightOpsHandler'); - } - - $endpoint = $useSSL - ? 'ssl://' . $region . '.data.logs.insight.rapid7.com:443' - : $region . '.data.logs.insight.rapid7.com:80'; - - parent::__construct( - $endpoint, - $level, - $bubble, - $persistent, - $timeout, - $writingTimeout, - $connectionTimeout, - $chunkSize - ); - $this->logToken = $token; - } - - /** - * @inheritDoc - */ - protected function generateDataStream(LogRecord $record): string - { - return $this->logToken . ' ' . $record->formatted; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php deleted file mode 100644 index 00259834eb3e..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php +++ /dev/null @@ -1,68 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Monolog\LogRecord; - -/** - * @author Robert Kaufmann III - */ -class LogEntriesHandler extends SocketHandler -{ - protected string $logToken; - - /** - * @param string $token Log token supplied by LogEntries - * @param bool $useSSL Whether or not SSL encryption should be used. - * @param string $host Custom hostname to send the data to if needed - * - * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing - */ - public function __construct( - string $token, - bool $useSSL = true, - $level = Level::Debug, - bool $bubble = true, - string $host = 'data.logentries.com', - bool $persistent = false, - float $timeout = 0.0, - float $writingTimeout = 10.0, - ?float $connectionTimeout = null, - ?int $chunkSize = null - ) { - if ($useSSL && !extension_loaded('openssl')) { - throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for LogEntriesHandler'); - } - - $endpoint = $useSSL ? 'ssl://' . $host . ':443' : $host . ':80'; - parent::__construct( - $endpoint, - $level, - $bubble, - $persistent, - $timeout, - $writingTimeout, - $connectionTimeout, - $chunkSize - ); - $this->logToken = $token; - } - - /** - * @inheritDoc - */ - protected function generateDataStream(LogRecord $record): string - { - return $this->logToken . ' ' . $record->formatted; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/LogglyHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/LogglyHandler.php deleted file mode 100644 index 2d8e66f18017..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/LogglyHandler.php +++ /dev/null @@ -1,155 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Monolog\Formatter\FormatterInterface; -use Monolog\Formatter\LogglyFormatter; -use function array_key_exists; -use CurlHandle; -use Monolog\LogRecord; - -/** - * Sends errors to Loggly. - * - * @author Przemek Sobstel - * @author Adam Pancutt - * @author Gregory Barchard - */ -class LogglyHandler extends AbstractProcessingHandler -{ - protected const HOST = 'logs-01.loggly.com'; - protected const ENDPOINT_SINGLE = 'inputs'; - protected const ENDPOINT_BATCH = 'bulk'; - - /** - * Caches the curl handlers for every given endpoint. - * - * @var CurlHandle[] - */ - protected array $curlHandlers = []; - - protected string $token; - - /** @var string[] */ - protected array $tag = []; - - /** - * @param string $token API token supplied by Loggly - * - * @throws MissingExtensionException If the curl extension is missing - */ - public function __construct(string $token, int|string|Level $level = Level::Debug, bool $bubble = true) - { - if (!extension_loaded('curl')) { - throw new MissingExtensionException('The curl extension is needed to use the LogglyHandler'); - } - - $this->token = $token; - - parent::__construct($level, $bubble); - } - - /** - * Loads and returns the shared curl handler for the given endpoint. - */ - protected function getCurlHandler(string $endpoint): CurlHandle - { - if (!array_key_exists($endpoint, $this->curlHandlers)) { - $this->curlHandlers[$endpoint] = $this->loadCurlHandle($endpoint); - } - - return $this->curlHandlers[$endpoint]; - } - - /** - * Starts a fresh curl session for the given endpoint and returns its handler. - */ - private function loadCurlHandle(string $endpoint): CurlHandle - { - $url = sprintf("https://%s/%s/%s/", static::HOST, $endpoint, $this->token); - - $ch = curl_init(); - - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - return $ch; - } - - /** - * @param string[]|string $tag - */ - public function setTag(string|array $tag): self - { - if ('' === $tag || [] === $tag) { - $this->tag = []; - } else { - $this->tag = is_array($tag) ? $tag : [$tag]; - } - - return $this; - } - - /** - * @param string[]|string $tag - */ - public function addTag(string|array $tag): self - { - if ('' !== $tag) { - $tag = is_array($tag) ? $tag : [$tag]; - $this->tag = array_unique(array_merge($this->tag, $tag)); - } - - return $this; - } - - protected function write(LogRecord $record): void - { - $this->send($record->formatted, static::ENDPOINT_SINGLE); - } - - public function handleBatch(array $records): void - { - $level = $this->level; - - $records = array_filter($records, function ($record) use ($level) { - return ($record->level >= $level); - }); - - if (\count($records) > 0) { - $this->send($this->getFormatter()->formatBatch($records), static::ENDPOINT_BATCH); - } - } - - protected function send(string $data, string $endpoint): void - { - $ch = $this->getCurlHandler($endpoint); - - $headers = ['Content-Type: application/json']; - - if (\count($this->tag) > 0) { - $headers[] = 'X-LOGGLY-TAG: '.implode(',', $this->tag); - } - - curl_setopt($ch, CURLOPT_POSTFIELDS, $data); - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - - Curl\Util::execute($ch, 5, false); - } - - protected function getDefaultFormatter(): FormatterInterface - { - return new LogglyFormatter(); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php deleted file mode 100644 index 876b1a953ffb..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php +++ /dev/null @@ -1,98 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Monolog\Formatter\FormatterInterface; -use Monolog\Formatter\LogmaticFormatter; -use Monolog\LogRecord; - -/** - * @author Julien Breux - */ -class LogmaticHandler extends SocketHandler -{ - private string $logToken; - - private string $hostname; - - private string $appName; - - /** - * @param string $token Log token supplied by Logmatic. - * @param string $hostname Host name supplied by Logmatic. - * @param string $appName Application name supplied by Logmatic. - * @param bool $useSSL Whether or not SSL encryption should be used. - * - * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing - */ - public function __construct( - string $token, - string $hostname = '', - string $appName = '', - bool $useSSL = true, - $level = Level::Debug, - bool $bubble = true, - bool $persistent = false, - float $timeout = 0.0, - float $writingTimeout = 10.0, - ?float $connectionTimeout = null, - ?int $chunkSize = null - ) { - if ($useSSL && !extension_loaded('openssl')) { - throw new MissingExtensionException('The OpenSSL PHP extension is required to use SSL encrypted connection for LogmaticHandler'); - } - - $endpoint = $useSSL ? 'ssl://api.logmatic.io:10515' : 'api.logmatic.io:10514'; - $endpoint .= '/v1/'; - - parent::__construct( - $endpoint, - $level, - $bubble, - $persistent, - $timeout, - $writingTimeout, - $connectionTimeout, - $chunkSize - ); - - $this->logToken = $token; - $this->hostname = $hostname; - $this->appName = $appName; - } - - /** - * @inheritDoc - */ - protected function generateDataStream(LogRecord $record): string - { - return $this->logToken . ' ' . $record->formatted; - } - - /** - * @inheritDoc - */ - protected function getDefaultFormatter(): FormatterInterface - { - $formatter = new LogmaticFormatter(); - - if ($this->hostname !== '') { - $formatter->setHostname($this->hostname); - } - if ($this->appName !== '') { - $formatter->setAppName($this->appName); - } - - return $formatter; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/MailHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/MailHandler.php deleted file mode 100644 index b6c822772664..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/MailHandler.php +++ /dev/null @@ -1,91 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Formatter\FormatterInterface; -use Monolog\Formatter\HtmlFormatter; -use Monolog\LogRecord; - -/** - * Base class for all mail handlers - * - * @author Gyula Sallai - */ -abstract class MailHandler extends AbstractProcessingHandler -{ - /** - * @inheritDoc - */ - public function handleBatch(array $records): void - { - $messages = []; - - foreach ($records as $record) { - if ($record->level->isLowerThan($this->level)) { - continue; - } - - $message = $this->processRecord($record); - $messages[] = $message; - } - - if (\count($messages) > 0) { - $this->send((string) $this->getFormatter()->formatBatch($messages), $messages); - } - } - - /** - * Send a mail with the given content - * - * @param string $content formatted email body to be sent - * @param array $records the array of log records that formed this content - * - * @phpstan-param non-empty-array $records - */ - abstract protected function send(string $content, array $records): void; - - /** - * @inheritDoc - */ - protected function write(LogRecord $record): void - { - $this->send((string) $record->formatted, [$record]); - } - - /** - * @phpstan-param non-empty-array $records - */ - protected function getHighestRecord(array $records): LogRecord - { - $highestRecord = null; - foreach ($records as $record) { - if ($highestRecord === null || $record->level->isHigherThan($highestRecord->level)) { - $highestRecord = $record; - } - } - - return $highestRecord; - } - - protected function isHtmlBody(string $body): bool - { - return ($body[0] ?? null) === '<'; - } - - /** - * Gets the default formatter. - */ - protected function getDefaultFormatter(): FormatterInterface - { - return new HtmlFormatter(); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/MandrillHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/MandrillHandler.php deleted file mode 100644 index 64e16c9deeb5..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/MandrillHandler.php +++ /dev/null @@ -1,83 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Swift; -use Swift_Message; - -/** - * MandrillHandler uses cURL to send the emails to the Mandrill API - * - * @author Adam Nicholson - */ -class MandrillHandler extends MailHandler -{ - protected Swift_Message $message; - protected string $apiKey; - - /** - * @phpstan-param (Swift_Message|callable(): Swift_Message) $message - * - * @param string $apiKey A valid Mandrill API key - * @param callable|Swift_Message $message An example message for real messages, only the body will be replaced - * - * @throws \InvalidArgumentException if not a Swift Message is set - */ - public function __construct(string $apiKey, callable|Swift_Message $message, int|string|Level $level = Level::Error, bool $bubble = true) - { - parent::__construct($level, $bubble); - - if (!$message instanceof Swift_Message) { - $message = $message(); - } - if (!$message instanceof Swift_Message) { - throw new \InvalidArgumentException('You must provide either a Swift_Message instance or a callable returning it'); - } - $this->message = $message; - $this->apiKey = $apiKey; - } - - /** - * @inheritDoc - */ - protected function send(string $content, array $records): void - { - $mime = 'text/plain'; - if ($this->isHtmlBody($content)) { - $mime = 'text/html'; - } - - $message = clone $this->message; - $message->setBody($content, $mime); - /** @phpstan-ignore-next-line */ - if (version_compare(Swift::VERSION, '6.0.0', '>=')) { - $message->setDate(new \DateTimeImmutable()); - } else { - /** @phpstan-ignore-next-line */ - $message->setDate(time()); - } - - $ch = curl_init(); - - curl_setopt($ch, CURLOPT_URL, 'https://mandrillapp.com/api/1.0/messages/send-raw.json'); - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([ - 'key' => $this->apiKey, - 'raw_message' => (string) $message, - 'async' => false, - ])); - - Curl\Util::execute($ch); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php deleted file mode 100644 index 33ab68c6d080..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php +++ /dev/null @@ -1,82 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use MongoDB\Driver\BulkWrite; -use MongoDB\Driver\Manager; -use MongoDB\Client; -use Monolog\Level; -use Monolog\Formatter\FormatterInterface; -use Monolog\Formatter\MongoDBFormatter; -use Monolog\LogRecord; - -/** - * Logs to a MongoDB database. - * - * Usage example: - * - * $log = new \Monolog\Logger('application'); - * $client = new \MongoDB\Client('mongodb://localhost:27017'); - * $mongodb = new \Monolog\Handler\MongoDBHandler($client, 'logs', 'prod'); - * $log->pushHandler($mongodb); - * - * The above examples uses the MongoDB PHP library's client class; however, the - * MongoDB\Driver\Manager class from ext-mongodb is also supported. - */ -class MongoDBHandler extends AbstractProcessingHandler -{ - private \MongoDB\Collection $collection; - - private Client|Manager $manager; - - private string|null $namespace = null; - - /** - * Constructor. - * - * @param Client|Manager $mongodb MongoDB library or driver client - * @param string $database Database name - * @param string $collection Collection name - */ - public function __construct(Client|Manager $mongodb, string $database, string $collection, int|string|Level $level = Level::Debug, bool $bubble = true) - { - if ($mongodb instanceof Client) { - $this->collection = $mongodb->selectCollection($database, $collection); - } else { - $this->manager = $mongodb; - $this->namespace = $database . '.' . $collection; - } - - parent::__construct($level, $bubble); - } - - protected function write(LogRecord $record): void - { - if (isset($this->collection)) { - $this->collection->insertOne($record->formatted); - } - - if (isset($this->manager, $this->namespace)) { - $bulk = new BulkWrite; - $bulk->insert($record->formatted); - $this->manager->executeBulkWrite($this->namespace, $bulk); - } - } - - /** - * @inheritDoc - */ - protected function getDefaultFormatter(): FormatterInterface - { - return new MongoDBFormatter; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php deleted file mode 100644 index d4c9d801079c..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php +++ /dev/null @@ -1,167 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Monolog\Formatter\LineFormatter; - -/** - * NativeMailerHandler uses the mail() function to send the emails - * - * @author Christophe Coevoet - * @author Mark Garrett - */ -class NativeMailerHandler extends MailHandler -{ - /** - * The email addresses to which the message will be sent - * @var string[] - */ - protected array $to; - - /** - * The subject of the email - */ - protected string $subject; - - /** - * Optional headers for the message - * @var string[] - */ - protected array $headers = []; - - /** - * Optional parameters for the message - * @var string[] - */ - protected array $parameters = []; - - /** - * The wordwrap length for the message - */ - protected int $maxColumnWidth; - - /** - * The Content-type for the message - */ - protected string|null $contentType = null; - - /** - * The encoding for the message - */ - protected string $encoding = 'utf-8'; - - /** - * @param string|string[] $to The receiver of the mail - * @param string $subject The subject of the mail - * @param string $from The sender of the mail - * @param int $maxColumnWidth The maximum column width that the message lines will have - */ - public function __construct(string|array $to, string $subject, string $from, int|string|Level $level = Level::Error, bool $bubble = true, int $maxColumnWidth = 70) - { - parent::__construct($level, $bubble); - $this->to = (array) $to; - $this->subject = $subject; - $this->addHeader(sprintf('From: %s', $from)); - $this->maxColumnWidth = $maxColumnWidth; - } - - /** - * Add headers to the message - * - * @param string|string[] $headers Custom added headers - */ - public function addHeader($headers): self - { - foreach ((array) $headers as $header) { - if (strpos($header, "\n") !== false || strpos($header, "\r") !== false) { - throw new \InvalidArgumentException('Headers can not contain newline characters for security reasons'); - } - $this->headers[] = $header; - } - - return $this; - } - - /** - * Add parameters to the message - * - * @param string|string[] $parameters Custom added parameters - */ - public function addParameter($parameters): self - { - $this->parameters = array_merge($this->parameters, (array) $parameters); - - return $this; - } - - /** - * @inheritDoc - */ - protected function send(string $content, array $records): void - { - $contentType = $this->getContentType() ?? ($this->isHtmlBody($content) ? 'text/html' : 'text/plain'); - - if ($contentType !== 'text/html') { - $content = wordwrap($content, $this->maxColumnWidth); - } - - $headers = ltrim(implode("\r\n", $this->headers) . "\r\n", "\r\n"); - $headers .= 'Content-type: ' . $contentType . '; charset=' . $this->getEncoding() . "\r\n"; - if ($contentType === 'text/html' && false === strpos($headers, 'MIME-Version:')) { - $headers .= 'MIME-Version: 1.0' . "\r\n"; - } - - $subjectFormatter = new LineFormatter($this->subject); - $subject = $subjectFormatter->format($this->getHighestRecord($records)); - - $parameters = implode(' ', $this->parameters); - foreach ($this->to as $to) { - mail($to, $subject, $content, $headers, $parameters); - } - } - - public function getContentType(): ?string - { - return $this->contentType; - } - - public function getEncoding(): string - { - return $this->encoding; - } - - /** - * @param string $contentType The content type of the email - Defaults to text/plain. Use text/html for HTML messages. - */ - public function setContentType(string $contentType): self - { - if (strpos($contentType, "\n") !== false || strpos($contentType, "\r") !== false) { - throw new \InvalidArgumentException('The content type can not contain newline characters to prevent email header injection'); - } - - $this->contentType = $contentType; - - return $this; - } - - public function setEncoding(string $encoding): self - { - if (strpos($encoding, "\n") !== false || strpos($encoding, "\r") !== false) { - throw new \InvalidArgumentException('The encoding can not contain newline characters to prevent email header injection'); - } - - $this->encoding = $encoding; - - return $this; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php deleted file mode 100644 index b8cb3785b830..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php +++ /dev/null @@ -1,180 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Monolog\Utils; -use Monolog\Formatter\NormalizerFormatter; -use Monolog\Formatter\FormatterInterface; -use Monolog\LogRecord; - -/** - * Class to record a log on a NewRelic application. - * Enabling New Relic High Security mode may prevent capture of useful information. - * - * This handler requires a NormalizerFormatter to function and expects an array in $record->formatted - * - * @see https://docs.newrelic.com/docs/agents/php-agent - * @see https://docs.newrelic.com/docs/accounts-partnerships/accounts/security/high-security - */ -class NewRelicHandler extends AbstractProcessingHandler -{ - /** - * @inheritDoc - */ - public function __construct( - int|string|Level $level = Level::Error, - bool $bubble = true, - - /** - * Name of the New Relic application that will receive logs from this handler. - */ - protected string|null $appName = null, - - /** - * Some context and extra data is passed into the handler as arrays of values. Do we send them as is - * (useful if we are using the API), or explode them for display on the NewRelic RPM website? - */ - protected bool $explodeArrays = false, - - /** - * Name of the current transaction - */ - protected string|null $transactionName = null - ) { - parent::__construct($level, $bubble); - } - - /** - * @inheritDoc - */ - protected function write(LogRecord $record): void - { - if (!$this->isNewRelicEnabled()) { - throw new MissingExtensionException('The newrelic PHP extension is required to use the NewRelicHandler'); - } - - if (null !== ($appName = $this->getAppName($record->context))) { - $this->setNewRelicAppName($appName); - } - - if (null !== ($transactionName = $this->getTransactionName($record->context))) { - $this->setNewRelicTransactionName($transactionName); - unset($record->formatted['context']['transaction_name']); - } - - if (isset($record->context['exception']) && $record->context['exception'] instanceof \Throwable) { - newrelic_notice_error($record->message, $record->context['exception']); - unset($record->formatted['context']['exception']); - } else { - newrelic_notice_error($record->message); - } - - if (isset($record->formatted['context']) && is_array($record->formatted['context'])) { - foreach ($record->formatted['context'] as $key => $parameter) { - if (is_array($parameter) && $this->explodeArrays) { - foreach ($parameter as $paramKey => $paramValue) { - $this->setNewRelicParameter('context_' . $key . '_' . $paramKey, $paramValue); - } - } else { - $this->setNewRelicParameter('context_' . $key, $parameter); - } - } - } - - if (isset($record->formatted['extra']) && is_array($record->formatted['extra'])) { - foreach ($record->formatted['extra'] as $key => $parameter) { - if (is_array($parameter) && $this->explodeArrays) { - foreach ($parameter as $paramKey => $paramValue) { - $this->setNewRelicParameter('extra_' . $key . '_' . $paramKey, $paramValue); - } - } else { - $this->setNewRelicParameter('extra_' . $key, $parameter); - } - } - } - } - - /** - * Checks whether the NewRelic extension is enabled in the system. - */ - protected function isNewRelicEnabled(): bool - { - return extension_loaded('newrelic'); - } - - /** - * Returns the appname where this log should be sent. Each log can override the default appname, set in this - * handler's constructor, by providing the appname in it's context. - * - * @param mixed[] $context - */ - protected function getAppName(array $context): ?string - { - if (isset($context['appname'])) { - return $context['appname']; - } - - return $this->appName; - } - - /** - * Returns the name of the current transaction. Each log can override the default transaction name, set in this - * handler's constructor, by providing the transaction_name in it's context - * - * @param mixed[] $context - */ - protected function getTransactionName(array $context): ?string - { - if (isset($context['transaction_name'])) { - return $context['transaction_name']; - } - - return $this->transactionName; - } - - /** - * Sets the NewRelic application that should receive this log. - */ - protected function setNewRelicAppName(string $appName): void - { - newrelic_set_appname($appName); - } - - /** - * Overwrites the name of the current transaction - */ - protected function setNewRelicTransactionName(string $transactionName): void - { - newrelic_name_transaction($transactionName); - } - - /** - * @param mixed $value - */ - protected function setNewRelicParameter(string $key, $value): void - { - if (null === $value || is_scalar($value)) { - newrelic_add_custom_parameter($key, $value); - } else { - newrelic_add_custom_parameter($key, Utils::jsonEncode($value, null, true)); - } - } - - /** - * @inheritDoc - */ - protected function getDefaultFormatter(): FormatterInterface - { - return new NormalizerFormatter(); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NoopHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NoopHandler.php deleted file mode 100644 index d9fea180c50d..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NoopHandler.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\LogRecord; - -/** - * No-op - * - * This handler handles anything, but does nothing, and does not stop bubbling to the rest of the stack. - * This can be used for testing, or to disable a handler when overriding a configuration without - * influencing the rest of the stack. - * - * @author Roel Harbers - */ -class NoopHandler extends Handler -{ - /** - * @inheritDoc - */ - public function isHandling(LogRecord $record): bool - { - return true; - } - - /** - * @inheritDoc - */ - public function handle(LogRecord $record): bool - { - return false; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NullHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NullHandler.php deleted file mode 100644 index 1aa84e4f8d71..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/NullHandler.php +++ /dev/null @@ -1,56 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Psr\Log\LogLevel; -use Monolog\Logger; -use Monolog\LogRecord; - -/** - * Blackhole - * - * Any record it can handle will be thrown away. This can be used - * to put on top of an existing stack to override it temporarily. - * - * @author Jordi Boggiano - */ -class NullHandler extends Handler -{ - private Level $level; - - /** - * @param string|int|Level $level The minimum logging level at which this handler will be triggered - * - * @phpstan-param value-of|value-of|Level|LogLevel::* $level - */ - public function __construct(string|int|Level $level = Level::Debug) - { - $this->level = Logger::toMonologLevel($level); - } - - /** - * @inheritDoc - */ - public function isHandling(LogRecord $record): bool - { - return $record->level->value >= $this->level->value; - } - - /** - * @inheritDoc - */ - public function handle(LogRecord $record): bool - { - return $record->level->value >= $this->level->value; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/OverflowHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/OverflowHandler.php deleted file mode 100644 index a72b7a11d771..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/OverflowHandler.php +++ /dev/null @@ -1,139 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Monolog\Formatter\FormatterInterface; -use Monolog\LogRecord; - -/** - * Handler to only pass log messages when a certain threshold of number of messages is reached. - * - * This can be useful in cases of processing a batch of data, but you're for example only interested - * in case it fails catastrophically instead of a warning for 1 or 2 events. Worse things can happen, right? - * - * Usage example: - * - * ``` - * $log = new Logger('application'); - * $handler = new SomeHandler(...) - * - * // Pass all warnings to the handler when more than 10 & all error messages when more then 5 - * $overflow = new OverflowHandler($handler, [Level::Warning->value => 10, Level::Error->value => 5]); - * - * $log->pushHandler($overflow); - *``` - * - * @author Kris Buist - */ -class OverflowHandler extends AbstractHandler implements FormattableHandlerInterface -{ - private HandlerInterface $handler; - - /** @var array */ - private array $thresholdMap = []; - - /** - * Buffer of all messages passed to the handler before the threshold was reached - * - * @var mixed[][] - */ - private array $buffer = []; - - /** - * @param array $thresholdMap Dictionary of log level value => threshold - */ - public function __construct( - HandlerInterface $handler, - array $thresholdMap = [], - $level = Level::Debug, - bool $bubble = true - ) { - $this->handler = $handler; - foreach ($thresholdMap as $thresholdLevel => $threshold) { - $this->thresholdMap[$thresholdLevel] = $threshold; - } - parent::__construct($level, $bubble); - } - - /** - * Handles a record. - * - * All records may be passed to this method, and the handler should discard - * those that it does not want to handle. - * - * The return value of this function controls the bubbling process of the handler stack. - * Unless the bubbling is interrupted (by returning true), the Logger class will keep on - * calling further handlers in the stack with a given log record. - * - * @inheritDoc - */ - public function handle(LogRecord $record): bool - { - if ($record->level->isLowerThan($this->level)) { - return false; - } - - $level = $record->level->value; - - if (!isset($this->thresholdMap[$level])) { - $this->thresholdMap[$level] = 0; - } - - if ($this->thresholdMap[$level] > 0) { - // The overflow threshold is not yet reached, so we're buffering the record and lowering the threshold by 1 - $this->thresholdMap[$level]--; - $this->buffer[$level][] = $record; - - return false === $this->bubble; - } - - if ($this->thresholdMap[$level] == 0) { - // This current message is breaking the threshold. Flush the buffer and continue handling the current record - foreach ($this->buffer[$level] ?? [] as $buffered) { - $this->handler->handle($buffered); - } - $this->thresholdMap[$level]--; - unset($this->buffer[$level]); - } - - $this->handler->handle($record); - - return false === $this->bubble; - } - - /** - * @inheritDoc - */ - public function setFormatter(FormatterInterface $formatter): HandlerInterface - { - if ($this->handler instanceof FormattableHandlerInterface) { - $this->handler->setFormatter($formatter); - - return $this; - } - - throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); - } - - /** - * @inheritDoc - */ - public function getFormatter(): FormatterInterface - { - if ($this->handler instanceof FormattableHandlerInterface) { - return $this->handler->getFormatter(); - } - - throw new \UnexpectedValueException('The nested handler of type '.get_class($this->handler).' does not support formatters.'); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php deleted file mode 100644 index 8aa78e4c4298..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php +++ /dev/null @@ -1,303 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Formatter\LineFormatter; -use Monolog\Formatter\FormatterInterface; -use Monolog\Level; -use Monolog\Utils; -use PhpConsole\Connector; -use PhpConsole\Handler as VendorPhpConsoleHandler; -use PhpConsole\Helper; -use Monolog\LogRecord; -use PhpConsole\Storage; - -/** - * Monolog handler for Google Chrome extension "PHP Console" - * - * Display PHP error/debug log messages in Google Chrome console and notification popups, executes PHP code remotely - * - * Usage: - * 1. Install Google Chrome extension [now dead and removed from the chrome store] - * 2. See overview https://github.com/barbushin/php-console#overview - * 3. Install PHP Console library https://github.com/barbushin/php-console#installation - * 4. Example (result will looks like http://i.hizliresim.com/vg3Pz4.png) - * - * $logger = new \Monolog\Logger('all', array(new \Monolog\Handler\PHPConsoleHandler())); - * \Monolog\ErrorHandler::register($logger); - * echo $undefinedVar; - * $logger->debug('SELECT * FROM users', array('db', 'time' => 0.012)); - * PC::debug($_SERVER); // PHP Console debugger for any type of vars - * - * @author Sergey Barbushin https://www.linkedin.com/in/barbushin - * @phpstan-type Options array{ - * enabled: bool, - * classesPartialsTraceIgnore: string[], - * debugTagsKeysInContext: array, - * useOwnErrorsHandler: bool, - * useOwnExceptionsHandler: bool, - * sourcesBasePath: string|null, - * registerHelper: bool, - * serverEncoding: string|null, - * headersLimit: int|null, - * password: string|null, - * enableSslOnlyMode: bool, - * ipMasks: string[], - * enableEvalListener: bool, - * dumperDetectCallbacks: bool, - * dumperLevelLimit: int, - * dumperItemsCountLimit: int, - * dumperItemSizeLimit: int, - * dumperDumpSizeLimit: int, - * detectDumpTraceAndSource: bool, - * dataStorage: Storage|null - * } - * @phpstan-type InputOptions array{ - * enabled?: bool, - * classesPartialsTraceIgnore?: string[], - * debugTagsKeysInContext?: array, - * useOwnErrorsHandler?: bool, - * useOwnExceptionsHandler?: bool, - * sourcesBasePath?: string|null, - * registerHelper?: bool, - * serverEncoding?: string|null, - * headersLimit?: int|null, - * password?: string|null, - * enableSslOnlyMode?: bool, - * ipMasks?: string[], - * enableEvalListener?: bool, - * dumperDetectCallbacks?: bool, - * dumperLevelLimit?: int, - * dumperItemsCountLimit?: int, - * dumperItemSizeLimit?: int, - * dumperDumpSizeLimit?: int, - * detectDumpTraceAndSource?: bool, - * dataStorage?: Storage|null - * } - * - * @deprecated Since 2.8.0 and 3.2.0, PHPConsole is abandoned and thus we will drop this handler in Monolog 4 - */ -class PHPConsoleHandler extends AbstractProcessingHandler -{ - /** - * @phpstan-var Options - */ - private array $options = [ - 'enabled' => true, // bool Is PHP Console server enabled - 'classesPartialsTraceIgnore' => ['Monolog\\'], // array Hide calls of classes started with... - 'debugTagsKeysInContext' => [0, 'tag'], // bool Is PHP Console server enabled - 'useOwnErrorsHandler' => false, // bool Enable errors handling - 'useOwnExceptionsHandler' => false, // bool Enable exceptions handling - 'sourcesBasePath' => null, // string Base path of all project sources to strip in errors source paths - 'registerHelper' => true, // bool Register PhpConsole\Helper that allows short debug calls like PC::debug($var, 'ta.g.s') - 'serverEncoding' => null, // string|null Server internal encoding - 'headersLimit' => null, // int|null Set headers size limit for your web-server - 'password' => null, // string|null Protect PHP Console connection by password - 'enableSslOnlyMode' => false, // bool Force connection by SSL for clients with PHP Console installed - 'ipMasks' => [], // array Set IP masks of clients that will be allowed to connect to PHP Console: array('192.168.*.*', '127.0.0.1') - 'enableEvalListener' => false, // bool Enable eval request to be handled by eval dispatcher(if enabled, 'password' option is also required) - 'dumperDetectCallbacks' => false, // bool Convert callback items in dumper vars to (callback SomeClass::someMethod) strings - 'dumperLevelLimit' => 5, // int Maximum dumped vars array or object nested dump level - 'dumperItemsCountLimit' => 100, // int Maximum dumped var same level array items or object properties number - 'dumperItemSizeLimit' => 5000, // int Maximum length of any string or dumped array item - 'dumperDumpSizeLimit' => 500000, // int Maximum approximate size of dumped vars result formatted in JSON - 'detectDumpTraceAndSource' => false, // bool Autodetect and append trace data to debug - 'dataStorage' => null, // \PhpConsole\Storage|null Fixes problem with custom $_SESSION handler(see http://goo.gl/Ne8juJ) - ]; - - private Connector $connector; - - /** - * @param array $options See \Monolog\Handler\PHPConsoleHandler::$options for more details - * @param Connector|null $connector Instance of \PhpConsole\Connector class (optional) - * @throws \RuntimeException - * @phpstan-param InputOptions $options - */ - public function __construct(array $options = [], ?Connector $connector = null, int|string|Level $level = Level::Debug, bool $bubble = true) - { - if (!class_exists('PhpConsole\Connector')) { - throw new \RuntimeException('PHP Console library not found. See https://github.com/barbushin/php-console#installation'); - } - parent::__construct($level, $bubble); - $this->options = $this->initOptions($options); - $this->connector = $this->initConnector($connector); - } - - /** - * @param array $options - * @return array - * - * @phpstan-param InputOptions $options - * @phpstan-return Options - */ - private function initOptions(array $options): array - { - $wrongOptions = array_diff(array_keys($options), array_keys($this->options)); - if (\count($wrongOptions) > 0) { - throw new \RuntimeException('Unknown options: ' . implode(', ', $wrongOptions)); - } - - return array_replace($this->options, $options); - } - - private function initConnector(?Connector $connector = null): Connector - { - if (null === $connector) { - if ($this->options['dataStorage'] instanceof Storage) { - Connector::setPostponeStorage($this->options['dataStorage']); - } - $connector = Connector::getInstance(); - } - - if ($this->options['registerHelper'] && !Helper::isRegistered()) { - Helper::register(); - } - - if ($this->options['enabled'] && $connector->isActiveClient()) { - if ($this->options['useOwnErrorsHandler'] || $this->options['useOwnExceptionsHandler']) { - $handler = VendorPhpConsoleHandler::getInstance(); - $handler->setHandleErrors($this->options['useOwnErrorsHandler']); - $handler->setHandleExceptions($this->options['useOwnExceptionsHandler']); - $handler->start(); - } - if (null !== $this->options['sourcesBasePath']) { - $connector->setSourcesBasePath($this->options['sourcesBasePath']); - } - if (null !== $this->options['serverEncoding']) { - $connector->setServerEncoding($this->options['serverEncoding']); - } - if (null !== $this->options['password']) { - $connector->setPassword($this->options['password']); - } - if ($this->options['enableSslOnlyMode']) { - $connector->enableSslOnlyMode(); - } - if (\count($this->options['ipMasks']) > 0) { - $connector->setAllowedIpMasks($this->options['ipMasks']); - } - if (null !== $this->options['headersLimit'] && $this->options['headersLimit'] > 0) { - $connector->setHeadersLimit($this->options['headersLimit']); - } - if ($this->options['detectDumpTraceAndSource']) { - $connector->getDebugDispatcher()->detectTraceAndSource = true; - } - $dumper = $connector->getDumper(); - $dumper->levelLimit = $this->options['dumperLevelLimit']; - $dumper->itemsCountLimit = $this->options['dumperItemsCountLimit']; - $dumper->itemSizeLimit = $this->options['dumperItemSizeLimit']; - $dumper->dumpSizeLimit = $this->options['dumperDumpSizeLimit']; - $dumper->detectCallbacks = $this->options['dumperDetectCallbacks']; - if ($this->options['enableEvalListener']) { - $connector->startEvalRequestsListener(); - } - } - - return $connector; - } - - public function getConnector(): Connector - { - return $this->connector; - } - - /** - * @return array - */ - public function getOptions(): array - { - return $this->options; - } - - public function handle(LogRecord $record): bool - { - if ($this->options['enabled'] && $this->connector->isActiveClient()) { - return parent::handle($record); - } - - return !$this->bubble; - } - - /** - * Writes the record down to the log of the implementing handler - */ - protected function write(LogRecord $record): void - { - if ($record->level->isLowerThan(Level::Notice)) { - $this->handleDebugRecord($record); - } elseif (isset($record->context['exception']) && $record->context['exception'] instanceof \Throwable) { - $this->handleExceptionRecord($record); - } else { - $this->handleErrorRecord($record); - } - } - - private function handleDebugRecord(LogRecord $record): void - { - [$tags, $filteredContext] = $this->getRecordTags($record); - $message = $record->message; - if (\count($filteredContext) > 0) { - $message .= ' ' . Utils::jsonEncode($this->connector->getDumper()->dump(array_filter($filteredContext)), null, true); - } - $this->connector->getDebugDispatcher()->dispatchDebug($message, $tags, $this->options['classesPartialsTraceIgnore']); - } - - private function handleExceptionRecord(LogRecord $record): void - { - $this->connector->getErrorsDispatcher()->dispatchException($record->context['exception']); - } - - private function handleErrorRecord(LogRecord $record): void - { - $context = $record->context; - - $this->connector->getErrorsDispatcher()->dispatchError( - $context['code'] ?? null, - $context['message'] ?? $record->message, - $context['file'] ?? null, - $context['line'] ?? null, - $this->options['classesPartialsTraceIgnore'] - ); - } - - /** - * @return array{string, mixed[]} - */ - private function getRecordTags(LogRecord $record): array - { - $tags = null; - $filteredContext = []; - if ($record->context !== []) { - $filteredContext = $record->context; - foreach ($this->options['debugTagsKeysInContext'] as $key) { - if (isset($filteredContext[$key])) { - $tags = $filteredContext[$key]; - if ($key === 0) { - array_shift($filteredContext); - } else { - unset($filteredContext[$key]); - } - break; - } - } - } - - return [$tags ?? $record->level->toPsrLogLevel(), $filteredContext]; - } - - /** - * @inheritDoc - */ - protected function getDefaultFormatter(): FormatterInterface - { - return new LineFormatter('%message%'); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ProcessHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ProcessHandler.php deleted file mode 100644 index 9edc9ac543d6..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ProcessHandler.php +++ /dev/null @@ -1,186 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Monolog\LogRecord; - -/** - * Stores to STDIN of any process, specified by a command. - * - * Usage example: - *
- * $log = new Logger('myLogger');
- * $log->pushHandler(new ProcessHandler('/usr/bin/php /var/www/monolog/someScript.php'));
- * 
- * - * @author Kolja Zuelsdorf - */ -class ProcessHandler extends AbstractProcessingHandler -{ - /** - * Holds the process to receive data on its STDIN. - * - * @var resource|bool|null - */ - private $process; - - private string $command; - - private ?string $cwd; - - /** - * @var resource[] - */ - private array $pipes = []; - - /** - * @var array - */ - protected const DESCRIPTOR_SPEC = [ - 0 => ['pipe', 'r'], // STDIN is a pipe that the child will read from - 1 => ['pipe', 'w'], // STDOUT is a pipe that the child will write to - 2 => ['pipe', 'w'], // STDERR is a pipe to catch the any errors - ]; - - /** - * @param string $command Command for the process to start. Absolute paths are recommended, - * especially if you do not use the $cwd parameter. - * @param string|null $cwd "Current working directory" (CWD) for the process to be executed in. - * @throws \InvalidArgumentException - */ - public function __construct(string $command, int|string|Level $level = Level::Debug, bool $bubble = true, ?string $cwd = null) - { - if ($command === '') { - throw new \InvalidArgumentException('The command argument must be a non-empty string.'); - } - if ($cwd === '') { - throw new \InvalidArgumentException('The optional CWD argument must be a non-empty string or null.'); - } - - parent::__construct($level, $bubble); - - $this->command = $command; - $this->cwd = $cwd; - } - - /** - * Writes the record down to the log of the implementing handler - * - * @throws \UnexpectedValueException - */ - protected function write(LogRecord $record): void - { - $this->ensureProcessIsStarted(); - - $this->writeProcessInput($record->formatted); - - $errors = $this->readProcessErrors(); - if ($errors !== '') { - throw new \UnexpectedValueException(sprintf('Errors while writing to process: %s', $errors)); - } - } - - /** - * Makes sure that the process is actually started, and if not, starts it, - * assigns the stream pipes, and handles startup errors, if any. - */ - private function ensureProcessIsStarted(): void - { - if (is_resource($this->process) === false) { - $this->startProcess(); - - $this->handleStartupErrors(); - } - } - - /** - * Starts the actual process and sets all streams to non-blocking. - */ - private function startProcess(): void - { - $this->process = proc_open($this->command, static::DESCRIPTOR_SPEC, $this->pipes, $this->cwd); - - foreach ($this->pipes as $pipe) { - stream_set_blocking($pipe, false); - } - } - - /** - * Selects the STDERR stream, handles upcoming startup errors, and throws an exception, if any. - * - * @throws \UnexpectedValueException - */ - private function handleStartupErrors(): void - { - $selected = $this->selectErrorStream(); - if (false === $selected) { - throw new \UnexpectedValueException('Something went wrong while selecting a stream.'); - } - - $errors = $this->readProcessErrors(); - - if (is_resource($this->process) === false || $errors !== '') { - throw new \UnexpectedValueException( - sprintf('The process "%s" could not be opened: ' . $errors, $this->command) - ); - } - } - - /** - * Selects the STDERR stream. - * - * @return int|bool - */ - protected function selectErrorStream() - { - $empty = []; - $errorPipes = [$this->pipes[2]]; - - return stream_select($errorPipes, $empty, $empty, 1); - } - - /** - * Reads the errors of the process, if there are any. - * - * @codeCoverageIgnore - * @return string Empty string if there are no errors. - */ - protected function readProcessErrors(): string - { - return (string) stream_get_contents($this->pipes[2]); - } - - /** - * Writes to the input stream of the opened process. - * - * @codeCoverageIgnore - */ - protected function writeProcessInput(string $string): void - { - fwrite($this->pipes[0], $string); - } - - /** - * @inheritDoc - */ - public function close(): void - { - if (is_resource($this->process)) { - foreach ($this->pipes as $pipe) { - fclose($pipe); - } - proc_close($this->process); - $this->process = null; - } - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php deleted file mode 100644 index 9fb290faadbc..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php +++ /dev/null @@ -1,43 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Processor\ProcessorInterface; -use Monolog\LogRecord; - -/** - * Interface to describe loggers that have processors - * - * @author Jordi Boggiano - */ -interface ProcessableHandlerInterface -{ - /** - * Adds a processor in the stack. - * - * @phpstan-param ProcessorInterface|(callable(LogRecord): LogRecord) $callback - * - * @param ProcessorInterface|callable $callback - * @return HandlerInterface self - */ - public function pushProcessor(callable $callback): HandlerInterface; - - /** - * Removes the processor on top of the stack and returns it. - * - * @phpstan-return ProcessorInterface|(callable(LogRecord): LogRecord) $callback - * - * @throws \LogicException In case the processor stack is empty - * @return callable|ProcessorInterface - */ - public function popProcessor(): callable; -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php deleted file mode 100644 index 74eeddddcec3..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php +++ /dev/null @@ -1,70 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\ResettableInterface; -use Monolog\Processor\ProcessorInterface; -use Monolog\LogRecord; - -/** - * Helper trait for implementing ProcessableInterface - * - * @author Jordi Boggiano - */ -trait ProcessableHandlerTrait -{ - /** - * @var callable[] - * @phpstan-var array<(callable(LogRecord): LogRecord)|ProcessorInterface> - */ - protected array $processors = []; - - /** - * @inheritDoc - */ - public function pushProcessor(callable $callback): HandlerInterface - { - array_unshift($this->processors, $callback); - - return $this; - } - - /** - * @inheritDoc - */ - public function popProcessor(): callable - { - if (\count($this->processors) === 0) { - throw new \LogicException('You tried to pop from an empty processor stack.'); - } - - return array_shift($this->processors); - } - - protected function processRecord(LogRecord $record): LogRecord - { - foreach ($this->processors as $processor) { - $record = $processor($record); - } - - return $record; - } - - protected function resetProcessors(): void - { - foreach ($this->processors as $processor) { - if ($processor instanceof ResettableInterface) { - $processor->reset(); - } - } - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/PsrHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/PsrHandler.php deleted file mode 100644 index 6599a83b4245..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/PsrHandler.php +++ /dev/null @@ -1,87 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Psr\Log\LoggerInterface; -use Monolog\Formatter\FormatterInterface; -use Monolog\LogRecord; - -/** - * Proxies log messages to an existing PSR-3 compliant logger. - * - * If a formatter is configured, the formatter's output MUST be a string and the - * formatted message will be fed to the wrapped PSR logger instead of the original - * log record's message. - * - * @author Michael Moussa - */ -class PsrHandler extends AbstractHandler implements FormattableHandlerInterface -{ - /** - * PSR-3 compliant logger - */ - protected LoggerInterface $logger; - - protected FormatterInterface|null $formatter = null; - - /** - * @param LoggerInterface $logger The underlying PSR-3 compliant logger to which messages will be proxied - */ - public function __construct(LoggerInterface $logger, int|string|Level $level = Level::Debug, bool $bubble = true) - { - parent::__construct($level, $bubble); - - $this->logger = $logger; - } - - /** - * @inheritDoc - */ - public function handle(LogRecord $record): bool - { - if (!$this->isHandling($record)) { - return false; - } - - if ($this->formatter !== null) { - $formatted = $this->formatter->format($record); - $this->logger->log($record->level->toPsrLogLevel(), (string) $formatted, $record->context); - } else { - $this->logger->log($record->level->toPsrLogLevel(), $record->message, $record->context); - } - - return false === $this->bubble; - } - - /** - * Sets the formatter. - */ - public function setFormatter(FormatterInterface $formatter): HandlerInterface - { - $this->formatter = $formatter; - - return $this; - } - - /** - * Gets the formatter. - */ - public function getFormatter(): FormatterInterface - { - if ($this->formatter === null) { - throw new \LogicException('No formatter has been set and this handler does not have a default formatter'); - } - - return $this->formatter; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/PushoverHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/PushoverHandler.php deleted file mode 100644 index 118f5760a110..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/PushoverHandler.php +++ /dev/null @@ -1,242 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Monolog\Logger; -use Monolog\Utils; -use Psr\Log\LogLevel; -use Monolog\LogRecord; - -/** - * Sends notifications through the pushover api to mobile phones - * - * @author Sebastian Göttschkes - * @see https://www.pushover.net/api - */ -class PushoverHandler extends SocketHandler -{ - private string $token; - - /** @var array */ - private array $users; - - private string $title; - - private string|int|null $user = null; - - private int $retry; - - private int $expire; - - private Level $highPriorityLevel; - - private Level $emergencyLevel; - - private bool $useFormattedMessage = false; - - /** - * All parameters that can be sent to Pushover - * @see https://pushover.net/api - * @var array - */ - private array $parameterNames = [ - 'token' => true, - 'user' => true, - 'message' => true, - 'device' => true, - 'title' => true, - 'url' => true, - 'url_title' => true, - 'priority' => true, - 'timestamp' => true, - 'sound' => true, - 'retry' => true, - 'expire' => true, - 'callback' => true, - ]; - - /** - * Sounds the api supports by default - * @see https://pushover.net/api#sounds - * @var string[] - */ - private array $sounds = [ - 'pushover', 'bike', 'bugle', 'cashregister', 'classical', 'cosmic', 'falling', 'gamelan', 'incoming', - 'intermission', 'magic', 'mechanical', 'pianobar', 'siren', 'spacealarm', 'tugboat', 'alien', 'climb', - 'persistent', 'echo', 'updown', 'none', - ]; - - /** - * @param string $token Pushover api token - * @param string|array $users Pushover user id or array of ids the message will be sent to - * @param string|null $title Title sent to the Pushover API - * @param bool $useSSL Whether to connect via SSL. Required when pushing messages to users that are not - * the pushover.net app owner. OpenSSL is required for this option. - * @param int $retry The retry parameter specifies how often (in seconds) the Pushover servers will - * send the same notification to the user. - * @param int $expire The expire parameter specifies how many seconds your notification will continue - * to be retried for (every retry seconds). - * - * @param int|string|Level|LogLevel::* $highPriorityLevel The minimum logging level at which this handler will start - * sending "high priority" requests to the Pushover API - * @param int|string|Level|LogLevel::* $emergencyLevel The minimum logging level at which this handler will start - * sending "emergency" requests to the Pushover API - * - * - * @phpstan-param string|array $users - * @phpstan-param value-of|value-of|Level|LogLevel::* $highPriorityLevel - * @phpstan-param value-of|value-of|Level|LogLevel::* $emergencyLevel - */ - public function __construct( - string $token, - $users, - ?string $title = null, - int|string|Level $level = Level::Critical, - bool $bubble = true, - bool $useSSL = true, - int|string|Level $highPriorityLevel = Level::Critical, - int|string|Level $emergencyLevel = Level::Emergency, - int $retry = 30, - int $expire = 25200, - bool $persistent = false, - float $timeout = 0.0, - float $writingTimeout = 10.0, - ?float $connectionTimeout = null, - ?int $chunkSize = null - ) { - $connectionString = $useSSL ? 'ssl://api.pushover.net:443' : 'api.pushover.net:80'; - parent::__construct( - $connectionString, - $level, - $bubble, - $persistent, - $timeout, - $writingTimeout, - $connectionTimeout, - $chunkSize - ); - - $this->token = $token; - $this->users = (array) $users; - $this->title = $title ?? (string) gethostname(); - $this->highPriorityLevel = Logger::toMonologLevel($highPriorityLevel); - $this->emergencyLevel = Logger::toMonologLevel($emergencyLevel); - $this->retry = $retry; - $this->expire = $expire; - } - - protected function generateDataStream(LogRecord $record): string - { - $content = $this->buildContent($record); - - return $this->buildHeader($content) . $content; - } - - private function buildContent(LogRecord $record): string - { - // Pushover has a limit of 512 characters on title and message combined. - $maxMessageLength = 512 - strlen($this->title); - - $message = ($this->useFormattedMessage) ? $record->formatted : $record->message; - $message = Utils::substr($message, 0, $maxMessageLength); - - $timestamp = $record->datetime->getTimestamp(); - - $dataArray = [ - 'token' => $this->token, - 'user' => $this->user, - 'message' => $message, - 'title' => $this->title, - 'timestamp' => $timestamp, - ]; - - if ($record->level->value >= $this->emergencyLevel->value) { - $dataArray['priority'] = 2; - $dataArray['retry'] = $this->retry; - $dataArray['expire'] = $this->expire; - } elseif ($record->level->value >= $this->highPriorityLevel->value) { - $dataArray['priority'] = 1; - } - - // First determine the available parameters - $context = array_intersect_key($record->context, $this->parameterNames); - $extra = array_intersect_key($record->extra, $this->parameterNames); - - // Least important info should be merged with subsequent info - $dataArray = array_merge($extra, $context, $dataArray); - - // Only pass sounds that are supported by the API - if (isset($dataArray['sound']) && !in_array($dataArray['sound'], $this->sounds, true)) { - unset($dataArray['sound']); - } - - return http_build_query($dataArray); - } - - private function buildHeader(string $content): string - { - $header = "POST /1/messages.json HTTP/1.1\r\n"; - $header .= "Host: api.pushover.net\r\n"; - $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; - $header .= "Content-Length: " . strlen($content) . "\r\n"; - $header .= "\r\n"; - - return $header; - } - - protected function write(LogRecord $record): void - { - foreach ($this->users as $user) { - $this->user = $user; - - parent::write($record); - $this->closeSocket(); - } - - $this->user = null; - } - - /** - * @param int|string|Level|LogLevel::* $level - * - * @phpstan-param value-of|value-of|Level|LogLevel::* $level - */ - public function setHighPriorityLevel(int|string|Level $level): self - { - $this->highPriorityLevel = Logger::toMonologLevel($level); - - return $this; - } - - /** - * @param int|string|Level|LogLevel::* $level - * - * @phpstan-param value-of|value-of|Level|LogLevel::* $level - */ - public function setEmergencyLevel(int|string|Level $level): self - { - $this->emergencyLevel = Logger::toMonologLevel($level); - - return $this; - } - - /** - * Use the formatted message? - */ - public function useFormattedMessage(bool $useFormattedMessage): self - { - $this->useFormattedMessage = $useFormattedMessage; - - return $this; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RedisHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RedisHandler.php deleted file mode 100644 index 5eee5dc693bb..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RedisHandler.php +++ /dev/null @@ -1,94 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Formatter\LineFormatter; -use Monolog\Formatter\FormatterInterface; -use Monolog\Level; -use Monolog\LogRecord; -use Predis\Client as Predis; -use Redis; - -/** - * Logs to a Redis key using rpush - * - * usage example: - * - * $log = new Logger('application'); - * $redis = new RedisHandler(new Predis\Client("tcp://localhost:6379"), "logs", "prod"); - * $log->pushHandler($redis); - * - * @author Thomas Tourlourat - */ -class RedisHandler extends AbstractProcessingHandler -{ - /** @var Predis|Redis */ - private Predis|Redis $redisClient; - private string $redisKey; - protected int $capSize; - - /** - * @param Predis|Redis $redis The redis instance - * @param string $key The key name to push records to - * @param int $capSize Number of entries to limit list size to, 0 = unlimited - */ - public function __construct(Predis|Redis $redis, string $key, int|string|Level $level = Level::Debug, bool $bubble = true, int $capSize = 0) - { - $this->redisClient = $redis; - $this->redisKey = $key; - $this->capSize = $capSize; - - parent::__construct($level, $bubble); - } - - /** - * @inheritDoc - */ - protected function write(LogRecord $record): void - { - if ($this->capSize > 0) { - $this->writeCapped($record); - } else { - $this->redisClient->rpush($this->redisKey, $record->formatted); - } - } - - /** - * Write and cap the collection - * Writes the record to the redis list and caps its - */ - protected function writeCapped(LogRecord $record): void - { - if ($this->redisClient instanceof Redis) { - $mode = defined('Redis::MULTI') ? Redis::MULTI : 1; - $this->redisClient->multi($mode) - ->rPush($this->redisKey, $record->formatted) - ->ltrim($this->redisKey, -$this->capSize, -1) - ->exec(); - } else { - $redisKey = $this->redisKey; - $capSize = $this->capSize; - $this->redisClient->transaction(function ($tx) use ($record, $redisKey, $capSize) { - $tx->rpush($redisKey, $record->formatted); - $tx->ltrim($redisKey, -$capSize, -1); - }); - } - } - - /** - * @inheritDoc - */ - protected function getDefaultFormatter(): FormatterInterface - { - return new LineFormatter(); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php deleted file mode 100644 index fa8e9e9ffd33..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php +++ /dev/null @@ -1,65 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Formatter\LineFormatter; -use Monolog\Formatter\FormatterInterface; -use Monolog\Level; -use Monolog\LogRecord; -use Predis\Client as Predis; -use Redis; - -/** - * Sends the message to a Redis Pub/Sub channel using PUBLISH - * - * usage example: - * - * $log = new Logger('application'); - * $redis = new RedisPubSubHandler(new Predis\Client("tcp://localhost:6379"), "logs", Level::Warning); - * $log->pushHandler($redis); - * - * @author Gaëtan Faugère - */ -class RedisPubSubHandler extends AbstractProcessingHandler -{ - /** @var Predis|Redis */ - private Predis|Redis $redisClient; - private string $channelKey; - - /** - * @param Predis|Redis $redis The redis instance - * @param string $key The channel key to publish records to - */ - public function __construct(Predis|Redis $redis, string $key, int|string|Level $level = Level::Debug, bool $bubble = true) - { - $this->redisClient = $redis; - $this->channelKey = $key; - - parent::__construct($level, $bubble); - } - - /** - * @inheritDoc - */ - protected function write(LogRecord $record): void - { - $this->redisClient->publish($this->channelKey, $record->formatted); - } - - /** - * @inheritDoc - */ - protected function getDefaultFormatter(): FormatterInterface - { - return new LineFormatter(); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RollbarHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RollbarHandler.php deleted file mode 100644 index 1d124723b49d..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RollbarHandler.php +++ /dev/null @@ -1,133 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Rollbar\RollbarLogger; -use Throwable; -use Monolog\LogRecord; - -/** - * Sends errors to Rollbar - * - * If the context data contains a `payload` key, that is used as an array - * of payload options to RollbarLogger's log method. - * - * Rollbar's context info will contain the context + extra keys from the log record - * merged, and then on top of that a few keys: - * - * - level (rollbar level name) - * - monolog_level (monolog level name, raw level, as rollbar only has 5 but monolog 8) - * - channel - * - datetime (unix timestamp) - * - * @author Paul Statezny - */ -class RollbarHandler extends AbstractProcessingHandler -{ - protected RollbarLogger $rollbarLogger; - - /** - * Records whether any log records have been added since the last flush of the rollbar notifier - */ - private bool $hasRecords = false; - - protected bool $initialized = false; - - /** - * @param RollbarLogger $rollbarLogger RollbarLogger object constructed with valid token - */ - public function __construct(RollbarLogger $rollbarLogger, int|string|Level $level = Level::Error, bool $bubble = true) - { - $this->rollbarLogger = $rollbarLogger; - - parent::__construct($level, $bubble); - } - - /** - * Translates Monolog log levels to Rollbar levels. - * - * @return 'debug'|'info'|'warning'|'error'|'critical' - */ - protected function toRollbarLevel(Level $level): string - { - return match ($level) { - Level::Debug => 'debug', - Level::Info => 'info', - Level::Notice => 'info', - Level::Warning => 'warning', - Level::Error => 'error', - Level::Critical => 'critical', - Level::Alert => 'critical', - Level::Emergency => 'critical', - }; - } - - /** - * @inheritDoc - */ - protected function write(LogRecord $record): void - { - if (!$this->initialized) { - // __destructor() doesn't get called on Fatal errors - register_shutdown_function([$this, 'close']); - $this->initialized = true; - } - - $context = $record->context; - $context = array_merge($context, $record->extra, [ - 'level' => $this->toRollbarLevel($record->level), - 'monolog_level' => $record->level->getName(), - 'channel' => $record->channel, - 'datetime' => $record->datetime->format('U'), - ]); - - if (isset($context['exception']) && $context['exception'] instanceof Throwable) { - $exception = $context['exception']; - unset($context['exception']); - $toLog = $exception; - } else { - $toLog = $record->message; - } - - // @phpstan-ignore-next-line - $this->rollbarLogger->log($context['level'], $toLog, $context); - - $this->hasRecords = true; - } - - public function flush(): void - { - if ($this->hasRecords) { - $this->rollbarLogger->flush(); - $this->hasRecords = false; - } - } - - /** - * @inheritDoc - */ - public function close(): void - { - $this->flush(); - } - - /** - * @inheritDoc - */ - public function reset(): void - { - $this->flush(); - - parent::reset(); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php deleted file mode 100644 index 75081db5f591..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php +++ /dev/null @@ -1,214 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use InvalidArgumentException; -use Monolog\Level; -use Monolog\Utils; -use Monolog\LogRecord; - -/** - * Stores logs to files that are rotated every day and a limited number of files are kept. - * - * This rotation is only intended to be used as a workaround. Using logrotate to - * handle the rotation is strongly encouraged when you can use it. - * - * @author Christophe Coevoet - * @author Jordi Boggiano - */ -class RotatingFileHandler extends StreamHandler -{ - public const FILE_PER_DAY = 'Y-m-d'; - public const FILE_PER_MONTH = 'Y-m'; - public const FILE_PER_YEAR = 'Y'; - - protected string $filename; - protected int $maxFiles; - protected bool|null $mustRotate = null; - protected \DateTimeImmutable $nextRotation; - protected string $filenameFormat; - protected string $dateFormat; - - /** - * @param int $maxFiles The maximal amount of files to keep (0 means unlimited) - * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) - * @param bool $useLocking Try to lock log file before doing any writes - */ - public function __construct(string $filename, int $maxFiles = 0, int|string|Level $level = Level::Debug, bool $bubble = true, ?int $filePermission = null, bool $useLocking = false, string $dateFormat = self::FILE_PER_DAY, string $filenameFormat = '{filename}-{date}') - { - $this->filename = Utils::canonicalizePath($filename); - $this->maxFiles = $maxFiles; - $this->setFilenameFormat($filenameFormat, $dateFormat); - $this->nextRotation = $this->getNextRotation(); - - parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking); - } - - /** - * @inheritDoc - */ - public function close(): void - { - parent::close(); - - if (true === $this->mustRotate) { - $this->rotate(); - } - } - - /** - * @inheritDoc - */ - public function reset(): void - { - parent::reset(); - - if (true === $this->mustRotate) { - $this->rotate(); - } - } - - public function setFilenameFormat(string $filenameFormat, string $dateFormat): self - { - $this->setDateFormat($dateFormat); - if (substr_count($filenameFormat, '{date}') === 0) { - throw new InvalidArgumentException( - 'Invalid filename format - format must contain at least `{date}`, because otherwise rotating is impossible.' - ); - } - $this->filenameFormat = $filenameFormat; - $this->url = $this->getTimedFilename(); - $this->close(); - - return $this; - } - - /** - * @inheritDoc - */ - protected function write(LogRecord $record): void - { - // on the first record written, if the log is new, we should rotate (once per day) - if (null === $this->mustRotate) { - $this->mustRotate = null === $this->url || !file_exists($this->url); - } - - if ($this->nextRotation <= $record->datetime) { - $this->mustRotate = true; - $this->close(); - } - - parent::write($record); - } - - /** - * Rotates the files. - */ - protected function rotate(): void - { - // update filename - $this->url = $this->getTimedFilename(); - $this->nextRotation = $this->getNextRotation(); - - // skip GC of old logs if files are unlimited - if (0 === $this->maxFiles) { - return; - } - - $logFiles = glob($this->getGlobPattern()); - if (false === $logFiles) { - // failed to glob - return; - } - - if ($this->maxFiles >= count($logFiles)) { - // no files to remove - return; - } - - // Sorting the files by name to remove the older ones - usort($logFiles, function ($a, $b) { - return strcmp($b, $a); - }); - - foreach (array_slice($logFiles, $this->maxFiles) as $file) { - if (is_writable($file)) { - // suppress errors here as unlink() might fail if two processes - // are cleaning up/rotating at the same time - set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline): bool { - return false; - }); - unlink($file); - restore_error_handler(); - } - } - - $this->mustRotate = false; - } - - protected function getTimedFilename(): string - { - $fileInfo = pathinfo($this->filename); - $timedFilename = str_replace( - ['{filename}', '{date}'], - [$fileInfo['filename'], date($this->dateFormat)], - ($fileInfo['dirname'] ?? '') . '/' . $this->filenameFormat - ); - - if (isset($fileInfo['extension'])) { - $timedFilename .= '.'.$fileInfo['extension']; - } - - return $timedFilename; - } - - protected function getGlobPattern(): string - { - $fileInfo = pathinfo($this->filename); - $glob = str_replace( - ['{filename}', '{date}'], - [$fileInfo['filename'], str_replace( - ['Y', 'y', 'm', 'd'], - ['[0-9][0-9][0-9][0-9]', '[0-9][0-9]', '[0-9][0-9]', '[0-9][0-9]'], - $this->dateFormat) - ], - ($fileInfo['dirname'] ?? '') . '/' . $this->filenameFormat - ); - if (isset($fileInfo['extension'])) { - $glob .= '.'.$fileInfo['extension']; - } - - return $glob; - } - - protected function setDateFormat(string $dateFormat): void - { - if (0 === preg_match('{^[Yy](([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) { - throw new InvalidArgumentException( - 'Invalid date format - format must be one of '. - 'RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), RotatingFileHandler::FILE_PER_MONTH ("Y-m") '. - 'or RotatingFileHandler::FILE_PER_YEAR ("Y"), or you can set one of the '. - 'date formats using slashes, underscores and/or dots instead of dashes.' - ); - } - $this->dateFormat = $dateFormat; - } - - protected function getNextRotation(): \DateTimeImmutable - { - return match (str_replace(['/','_','.'], '-', $this->dateFormat)) { - self::FILE_PER_MONTH => (new \DateTimeImmutable('first day of next month'))->setTime(0, 0, 0), - self::FILE_PER_YEAR => (new \DateTimeImmutable('first day of January next year'))->setTime(0, 0, 0), - default => (new \DateTimeImmutable('tomorrow'))->setTime(0, 0, 0), - }; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SamplingHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SamplingHandler.php deleted file mode 100644 index 511ec585421f..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SamplingHandler.php +++ /dev/null @@ -1,121 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Closure; -use Monolog\Formatter\FormatterInterface; -use Monolog\LogRecord; - -/** - * Sampling handler - * - * A sampled event stream can be useful for logging high frequency events in - * a production environment where you only need an idea of what is happening - * and are not concerned with capturing every occurrence. Since the decision to - * handle or not handle a particular event is determined randomly, the - * resulting sampled log is not guaranteed to contain 1/N of the events that - * occurred in the application, but based on the Law of large numbers, it will - * tend to be close to this ratio with a large number of attempts. - * - * @author Bryan Davis - * @author Kunal Mehta - */ -class SamplingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface -{ - use ProcessableHandlerTrait; - - /** - * Handler or factory Closure($record, $this) - * - * @phpstan-var (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface - */ - protected Closure|HandlerInterface $handler; - - protected int $factor; - - /** - * @phpstan-param (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface $handler - * - * @param Closure|HandlerInterface $handler Handler or factory Closure($record|null, $samplingHandler). - * @param int $factor Sample factor (e.g. 10 means every ~10th record is sampled) - */ - public function __construct(Closure|HandlerInterface $handler, int $factor) - { - parent::__construct(); - $this->handler = $handler; - $this->factor = $factor; - } - - public function isHandling(LogRecord $record): bool - { - return $this->getHandler($record)->isHandling($record); - } - - public function handle(LogRecord $record): bool - { - if ($this->isHandling($record) && mt_rand(1, $this->factor) === 1) { - if (\count($this->processors) > 0) { - $record = $this->processRecord($record); - } - - $this->getHandler($record)->handle($record); - } - - return false === $this->bubble; - } - - /** - * Return the nested handler - * - * If the handler was provided as a factory, this will trigger the handler's instantiation. - */ - public function getHandler(LogRecord $record = null): HandlerInterface - { - if (!$this->handler instanceof HandlerInterface) { - $handler = ($this->handler)($record, $this); - if (!$handler instanceof HandlerInterface) { - throw new \RuntimeException("The factory Closure should return a HandlerInterface"); - } - $this->handler = $handler; - } - - return $this->handler; - } - - /** - * @inheritDoc - */ - public function setFormatter(FormatterInterface $formatter): HandlerInterface - { - $handler = $this->getHandler(); - if ($handler instanceof FormattableHandlerInterface) { - $handler->setFormatter($formatter); - - return $this; - } - - throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); - } - - /** - * @inheritDoc - */ - public function getFormatter(): FormatterInterface - { - $handler = $this->getHandler(); - if ($handler instanceof FormattableHandlerInterface) { - return $handler->getFormatter(); - } - - throw new \UnexpectedValueException('The nested handler of type '.get_class($handler).' does not support formatters.'); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SendGridHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SendGridHandler.php deleted file mode 100644 index b8f574bb44cc..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SendGridHandler.php +++ /dev/null @@ -1,100 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; - -/** - * SendGridrHandler uses the SendGrid API v2 function to send Log emails, more information in https://sendgrid.com/docs/API_Reference/Web_API/mail.html - * - * @author Ricardo Fontanelli - */ -class SendGridHandler extends MailHandler -{ - /** - * The SendGrid API User - */ - protected string $apiUser; - - /** - * The SendGrid API Key - */ - protected string $apiKey; - - /** - * The email addresses to which the message will be sent - */ - protected string $from; - - /** - * The email addresses to which the message will be sent - * @var string[] - */ - protected array $to; - - /** - * The subject of the email - */ - protected string $subject; - - /** - * @param string $apiUser The SendGrid API User - * @param string $apiKey The SendGrid API Key - * @param string $from The sender of the email - * @param string|string[] $to The recipients of the email - * @param string $subject The subject of the mail - * - * @throws MissingExtensionException If the curl extension is missing - */ - public function __construct(string $apiUser, string $apiKey, string $from, string|array $to, string $subject, int|string|Level $level = Level::Error, bool $bubble = true) - { - if (!extension_loaded('curl')) { - throw new MissingExtensionException('The curl extension is needed to use the SendGridHandler'); - } - - parent::__construct($level, $bubble); - $this->apiUser = $apiUser; - $this->apiKey = $apiKey; - $this->from = $from; - $this->to = (array) $to; - $this->subject = $subject; - } - - /** - * @inheritDoc - */ - protected function send(string $content, array $records): void - { - $message = []; - $message['api_user'] = $this->apiUser; - $message['api_key'] = $this->apiKey; - $message['from'] = $this->from; - foreach ($this->to as $recipient) { - $message['to[]'] = $recipient; - } - $message['subject'] = $this->subject; - $message['date'] = date('r'); - - if ($this->isHtmlBody($content)) { - $message['html'] = $content; - } else { - $message['text'] = $content; - } - - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, 'https://api.sendgrid.com/api/mail.send.json'); - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($message)); - Curl\Util::execute($ch, 2); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php deleted file mode 100644 index 7e9cccc9df4a..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php +++ /dev/null @@ -1,367 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler\Slack; - -use Monolog\Level; -use Monolog\Utils; -use Monolog\Formatter\NormalizerFormatter; -use Monolog\Formatter\FormatterInterface; -use Monolog\LogRecord; - -/** - * Slack record utility helping to log to Slack webhooks or API. - * - * @author Greg Kedzierski - * @author Haralan Dobrev - * @see https://api.slack.com/incoming-webhooks - * @see https://api.slack.com/docs/message-attachments - */ -class SlackRecord -{ - public const COLOR_DANGER = 'danger'; - - public const COLOR_WARNING = 'warning'; - - public const COLOR_GOOD = 'good'; - - public const COLOR_DEFAULT = '#e3e4e6'; - - /** - * Slack channel (encoded ID or name) - */ - private string|null $channel; - - /** - * Name of a bot - */ - private string|null $username; - - /** - * User icon e.g. 'ghost', 'http://example.com/user.png' - */ - private string|null $userIcon; - - /** - * Whether the message should be added to Slack as attachment (plain text otherwise) - */ - private bool $useAttachment; - - /** - * Whether the the context/extra messages added to Slack as attachments are in a short style - */ - private bool $useShortAttachment; - - /** - * Whether the attachment should include context and extra data - */ - private bool $includeContextAndExtra; - - /** - * Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] - * @var string[] - */ - private array $excludeFields; - - private FormatterInterface|null $formatter; - - private NormalizerFormatter $normalizerFormatter; - - /** - * @param string[] $excludeFields - */ - public function __construct( - ?string $channel = null, - ?string $username = null, - bool $useAttachment = true, - ?string $userIcon = null, - bool $useShortAttachment = false, - bool $includeContextAndExtra = false, - array $excludeFields = [], - FormatterInterface $formatter = null - ) { - $this - ->setChannel($channel) - ->setUsername($username) - ->useAttachment($useAttachment) - ->setUserIcon($userIcon) - ->useShortAttachment($useShortAttachment) - ->includeContextAndExtra($includeContextAndExtra) - ->excludeFields($excludeFields) - ->setFormatter($formatter); - - if ($this->includeContextAndExtra) { - $this->normalizerFormatter = new NormalizerFormatter(); - } - } - - /** - * Returns required data in format that Slack - * is expecting. - * - * @phpstan-return mixed[] - */ - public function getSlackData(LogRecord $record): array - { - $dataArray = []; - - if ($this->username !== null) { - $dataArray['username'] = $this->username; - } - - if ($this->channel !== null) { - $dataArray['channel'] = $this->channel; - } - - if ($this->formatter !== null && !$this->useAttachment) { - $message = $this->formatter->format($record); - } else { - $message = $record->message; - } - - $recordData = $this->removeExcludedFields($record); - - if ($this->useAttachment) { - $attachment = [ - 'fallback' => $message, - 'text' => $message, - 'color' => $this->getAttachmentColor($record->level), - 'fields' => [], - 'mrkdwn_in' => ['fields'], - 'ts' => $recordData['datetime']->getTimestamp(), - 'footer' => $this->username, - 'footer_icon' => $this->userIcon, - ]; - - if ($this->useShortAttachment) { - $attachment['title'] = $recordData['level_name']; - } else { - $attachment['title'] = 'Message'; - $attachment['fields'][] = $this->generateAttachmentField('Level', $recordData['level_name']); - } - - if ($this->includeContextAndExtra) { - foreach (['extra', 'context'] as $key) { - if (!isset($recordData[$key]) || \count($recordData[$key]) === 0) { - continue; - } - - if ($this->useShortAttachment) { - $attachment['fields'][] = $this->generateAttachmentField( - $key, - $recordData[$key] - ); - } else { - // Add all extra fields as individual fields in attachment - $attachment['fields'] = array_merge( - $attachment['fields'], - $this->generateAttachmentFields($recordData[$key]) - ); - } - } - } - - $dataArray['attachments'] = [$attachment]; - } else { - $dataArray['text'] = $message; - } - - if ($this->userIcon !== null) { - if (false !== ($iconUrl = filter_var($this->userIcon, FILTER_VALIDATE_URL))) { - $dataArray['icon_url'] = $iconUrl; - } else { - $dataArray['icon_emoji'] = ":{$this->userIcon}:"; - } - } - - return $dataArray; - } - - /** - * Returns a Slack message attachment color associated with - * provided level. - */ - public function getAttachmentColor(Level $level): string - { - return match ($level) { - Level::Error, Level::Critical, Level::Alert, Level::Emergency => static::COLOR_DANGER, - Level::Warning => static::COLOR_WARNING, - Level::Info, Level::Notice => static::COLOR_GOOD, - Level::Debug => static::COLOR_DEFAULT - }; - } - - /** - * Stringifies an array of key/value pairs to be used in attachment fields - * - * @param mixed[] $fields - */ - public function stringify(array $fields): string - { - /** @var array $normalized */ - $normalized = $this->normalizerFormatter->normalizeValue($fields); - - $hasSecondDimension = \count(array_filter($normalized, 'is_array')) > 0; - $hasOnlyNonNumericKeys = \count(array_filter(array_keys($normalized), 'is_numeric')) === 0; - - return $hasSecondDimension || $hasOnlyNonNumericKeys - ? Utils::jsonEncode($normalized, JSON_PRETTY_PRINT|Utils::DEFAULT_JSON_FLAGS) - : Utils::jsonEncode($normalized, Utils::DEFAULT_JSON_FLAGS); - } - - /** - * Channel used by the bot when posting - * - * @param ?string $channel - * - * @return static - */ - public function setChannel(?string $channel = null): self - { - $this->channel = $channel; - - return $this; - } - - /** - * Username used by the bot when posting - * - * @param ?string $username - * - * @return static - */ - public function setUsername(?string $username = null): self - { - $this->username = $username; - - return $this; - } - - public function useAttachment(bool $useAttachment = true): self - { - $this->useAttachment = $useAttachment; - - return $this; - } - - public function setUserIcon(?string $userIcon = null): self - { - $this->userIcon = $userIcon; - - if (\is_string($userIcon)) { - $this->userIcon = trim($userIcon, ':'); - } - - return $this; - } - - public function useShortAttachment(bool $useShortAttachment = false): self - { - $this->useShortAttachment = $useShortAttachment; - - return $this; - } - - public function includeContextAndExtra(bool $includeContextAndExtra = false): self - { - $this->includeContextAndExtra = $includeContextAndExtra; - - if ($this->includeContextAndExtra) { - $this->normalizerFormatter = new NormalizerFormatter(); - } - - return $this; - } - - /** - * @param string[] $excludeFields - */ - public function excludeFields(array $excludeFields = []): self - { - $this->excludeFields = $excludeFields; - - return $this; - } - - public function setFormatter(?FormatterInterface $formatter = null): self - { - $this->formatter = $formatter; - - return $this; - } - - /** - * Generates attachment field - * - * @param string|mixed[] $value - * - * @return array{title: string, value: string, short: false} - */ - private function generateAttachmentField(string $title, $value): array - { - $value = is_array($value) - ? sprintf('```%s```', substr($this->stringify($value), 0, 1990)) - : $value; - - return [ - 'title' => ucfirst($title), - 'value' => $value, - 'short' => false, - ]; - } - - /** - * Generates a collection of attachment fields from array - * - * @param mixed[] $data - * - * @return array - */ - private function generateAttachmentFields(array $data): array - { - /** @var array $normalized */ - $normalized = $this->normalizerFormatter->normalizeValue($data); - - $fields = []; - foreach ($normalized as $key => $value) { - $fields[] = $this->generateAttachmentField((string) $key, $value); - } - - return $fields; - } - - /** - * Get a copy of record with fields excluded according to $this->excludeFields - * - * @return mixed[] - */ - private function removeExcludedFields(LogRecord $record): array - { - $recordData = $record->toArray(); - foreach ($this->excludeFields as $field) { - $keys = explode('.', $field); - $node = &$recordData; - $lastKey = end($keys); - foreach ($keys as $key) { - if (!isset($node[$key])) { - break; - } - if ($lastKey === $key) { - unset($node[$key]); - break; - } - $node = &$node[$key]; - } - } - - return $recordData; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SlackHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SlackHandler.php deleted file mode 100644 index 321d8660faaf..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SlackHandler.php +++ /dev/null @@ -1,250 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Formatter\FormatterInterface; -use Monolog\Level; -use Monolog\Utils; -use Monolog\Handler\Slack\SlackRecord; -use Monolog\LogRecord; - -/** - * Sends notifications through Slack API - * - * @author Greg Kedzierski - * @see https://api.slack.com/ - */ -class SlackHandler extends SocketHandler -{ - /** - * Slack API token - */ - private string $token; - - /** - * Instance of the SlackRecord util class preparing data for Slack API. - */ - private SlackRecord $slackRecord; - - /** - * @param string $token Slack API token - * @param string $channel Slack channel (encoded ID or name) - * @param string|null $username Name of a bot - * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) - * @param string|null $iconEmoji The emoji name to use (or null) - * @param bool $useShortAttachment Whether the context/extra messages added to Slack as attachments are in a short style - * @param bool $includeContextAndExtra Whether the attachment should include context and extra data - * @param string[] $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] - * @throws MissingExtensionException If no OpenSSL PHP extension configured - */ - public function __construct( - string $token, - string $channel, - ?string $username = null, - bool $useAttachment = true, - ?string $iconEmoji = null, - $level = Level::Critical, - bool $bubble = true, - bool $useShortAttachment = false, - bool $includeContextAndExtra = false, - array $excludeFields = [], - bool $persistent = false, - float $timeout = 0.0, - float $writingTimeout = 10.0, - ?float $connectionTimeout = null, - ?int $chunkSize = null - ) { - if (!extension_loaded('openssl')) { - throw new MissingExtensionException('The OpenSSL PHP extension is required to use the SlackHandler'); - } - - parent::__construct( - 'ssl://slack.com:443', - $level, - $bubble, - $persistent, - $timeout, - $writingTimeout, - $connectionTimeout, - $chunkSize - ); - - $this->slackRecord = new SlackRecord( - $channel, - $username, - $useAttachment, - $iconEmoji, - $useShortAttachment, - $includeContextAndExtra, - $excludeFields - ); - - $this->token = $token; - } - - public function getSlackRecord(): SlackRecord - { - return $this->slackRecord; - } - - public function getToken(): string - { - return $this->token; - } - - /** - * @inheritDoc - */ - protected function generateDataStream(LogRecord $record): string - { - $content = $this->buildContent($record); - - return $this->buildHeader($content) . $content; - } - - /** - * Builds the body of API call - */ - private function buildContent(LogRecord $record): string - { - $dataArray = $this->prepareContentData($record); - - return http_build_query($dataArray); - } - - /** - * @return string[] - */ - protected function prepareContentData(LogRecord $record): array - { - $dataArray = $this->slackRecord->getSlackData($record); - $dataArray['token'] = $this->token; - - if (isset($dataArray['attachments']) && is_array($dataArray['attachments']) && \count($dataArray['attachments']) > 0) { - $dataArray['attachments'] = Utils::jsonEncode($dataArray['attachments']); - } - - return $dataArray; - } - - /** - * Builds the header of the API Call - */ - private function buildHeader(string $content): string - { - $header = "POST /api/chat.postMessage HTTP/1.1\r\n"; - $header .= "Host: slack.com\r\n"; - $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; - $header .= "Content-Length: " . strlen($content) . "\r\n"; - $header .= "\r\n"; - - return $header; - } - - /** - * @inheritDoc - */ - protected function write(LogRecord $record): void - { - parent::write($record); - $this->finalizeWrite(); - } - - /** - * Finalizes the request by reading some bytes and then closing the socket - * - * If we do not read some but close the socket too early, slack sometimes - * drops the request entirely. - */ - protected function finalizeWrite(): void - { - $res = $this->getResource(); - if (is_resource($res)) { - @fread($res, 2048); - } - $this->closeSocket(); - } - - public function setFormatter(FormatterInterface $formatter): HandlerInterface - { - parent::setFormatter($formatter); - $this->slackRecord->setFormatter($formatter); - - return $this; - } - - public function getFormatter(): FormatterInterface - { - $formatter = parent::getFormatter(); - $this->slackRecord->setFormatter($formatter); - - return $formatter; - } - - /** - * Channel used by the bot when posting - */ - public function setChannel(string $channel): self - { - $this->slackRecord->setChannel($channel); - - return $this; - } - - /** - * Username used by the bot when posting - */ - public function setUsername(string $username): self - { - $this->slackRecord->setUsername($username); - - return $this; - } - - public function useAttachment(bool $useAttachment): self - { - $this->slackRecord->useAttachment($useAttachment); - - return $this; - } - - public function setIconEmoji(string $iconEmoji): self - { - $this->slackRecord->setUserIcon($iconEmoji); - - return $this; - } - - public function useShortAttachment(bool $useShortAttachment): self - { - $this->slackRecord->useShortAttachment($useShortAttachment); - - return $this; - } - - public function includeContextAndExtra(bool $includeContextAndExtra): self - { - $this->slackRecord->includeContextAndExtra($includeContextAndExtra); - - return $this; - } - - /** - * @param string[] $excludeFields - */ - public function excludeFields(array $excludeFields): self - { - $this->slackRecord->excludeFields($excludeFields); - - return $this; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php deleted file mode 100644 index 6466ba3af238..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php +++ /dev/null @@ -1,131 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Formatter\FormatterInterface; -use Monolog\Level; -use Monolog\Utils; -use Monolog\Handler\Slack\SlackRecord; -use Monolog\LogRecord; - -/** - * Sends notifications through Slack Webhooks - * - * @author Haralan Dobrev - * @see https://api.slack.com/incoming-webhooks - */ -class SlackWebhookHandler extends AbstractProcessingHandler -{ - /** - * Slack Webhook token - */ - private string $webhookUrl; - - /** - * Instance of the SlackRecord util class preparing data for Slack API. - */ - private SlackRecord $slackRecord; - - /** - * @param string $webhookUrl Slack Webhook URL - * @param string|null $channel Slack channel (encoded ID or name) - * @param string|null $username Name of a bot - * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) - * @param string|null $iconEmoji The emoji name to use (or null) - * @param bool $useShortAttachment Whether the the context/extra messages added to Slack as attachments are in a short style - * @param bool $includeContextAndExtra Whether the attachment should include context and extra data - * @param string[] $excludeFields Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] - * - * @throws MissingExtensionException If the curl extension is missing - */ - public function __construct( - string $webhookUrl, - ?string $channel = null, - ?string $username = null, - bool $useAttachment = true, - ?string $iconEmoji = null, - bool $useShortAttachment = false, - bool $includeContextAndExtra = false, - $level = Level::Critical, - bool $bubble = true, - array $excludeFields = [] - ) { - if (!extension_loaded('curl')) { - throw new MissingExtensionException('The curl extension is needed to use the SlackWebhookHandler'); - } - - parent::__construct($level, $bubble); - - $this->webhookUrl = $webhookUrl; - - $this->slackRecord = new SlackRecord( - $channel, - $username, - $useAttachment, - $iconEmoji, - $useShortAttachment, - $includeContextAndExtra, - $excludeFields - ); - } - - public function getSlackRecord(): SlackRecord - { - return $this->slackRecord; - } - - public function getWebhookUrl(): string - { - return $this->webhookUrl; - } - - /** - * @inheritDoc - */ - protected function write(LogRecord $record): void - { - $postData = $this->slackRecord->getSlackData($record); - $postString = Utils::jsonEncode($postData); - - $ch = curl_init(); - $options = [ - CURLOPT_URL => $this->webhookUrl, - CURLOPT_POST => true, - CURLOPT_RETURNTRANSFER => true, - CURLOPT_HTTPHEADER => ['Content-type: application/json'], - CURLOPT_POSTFIELDS => $postString, - ]; - if (defined('CURLOPT_SAFE_UPLOAD')) { - $options[CURLOPT_SAFE_UPLOAD] = true; - } - - curl_setopt_array($ch, $options); - - Curl\Util::execute($ch); - } - - public function setFormatter(FormatterInterface $formatter): HandlerInterface - { - parent::setFormatter($formatter); - $this->slackRecord->setFormatter($formatter); - - return $this; - } - - public function getFormatter(): FormatterInterface - { - $formatter = parent::getFormatter(); - $this->slackRecord->setFormatter($formatter); - - return $formatter; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SocketHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SocketHandler.php deleted file mode 100644 index c5f70888476a..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SocketHandler.php +++ /dev/null @@ -1,429 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Monolog\LogRecord; - -/** - * Stores to any socket - uses fsockopen() or pfsockopen(). - * - * @author Pablo de Leon Belloc - * @see http://php.net/manual/en/function.fsockopen.php - */ -class SocketHandler extends AbstractProcessingHandler -{ - private string $connectionString; - private float $connectionTimeout; - /** @var resource|null */ - private $resource; - private float $timeout; - private float $writingTimeout; - private int|null $lastSentBytes = null; - private int|null $chunkSize; - private bool $persistent; - private int|null $errno = null; - private string|null $errstr = null; - private float|null $lastWritingAt = null; - - /** - * @param string $connectionString Socket connection string - * @param bool $persistent Flag to enable/disable persistent connections - * @param float $timeout Socket timeout to wait until the request is being aborted - * @param float $writingTimeout Socket timeout to wait until the request should've been sent/written - * @param float|null $connectionTimeout Socket connect timeout to wait until the connection should've been - * established - * @param int|null $chunkSize Sets the chunk size. Only has effect during connection in the writing cycle - * - * @throws \InvalidArgumentException If an invalid timeout value (less than 0) is passed. - */ - public function __construct( - string $connectionString, - $level = Level::Debug, - bool $bubble = true, - bool $persistent = false, - float $timeout = 0.0, - float $writingTimeout = 10.0, - ?float $connectionTimeout = null, - ?int $chunkSize = null - ) { - parent::__construct($level, $bubble); - $this->connectionString = $connectionString; - - if ($connectionTimeout !== null) { - $this->validateTimeout($connectionTimeout); - } - - $this->connectionTimeout = $connectionTimeout ?? (float) ini_get('default_socket_timeout'); - $this->persistent = $persistent; - $this->validateTimeout($timeout); - $this->timeout = $timeout; - $this->validateTimeout($writingTimeout); - $this->writingTimeout = $writingTimeout; - $this->chunkSize = $chunkSize; - } - - /** - * Connect (if necessary) and write to the socket - * - * @inheritDoc - * - * @throws \UnexpectedValueException - * @throws \RuntimeException - */ - protected function write(LogRecord $record): void - { - $this->connectIfNotConnected(); - $data = $this->generateDataStream($record); - $this->writeToSocket($data); - } - - /** - * We will not close a PersistentSocket instance so it can be reused in other requests. - */ - public function close(): void - { - if (!$this->isPersistent()) { - $this->closeSocket(); - } - } - - /** - * Close socket, if open - */ - public function closeSocket(): void - { - if (is_resource($this->resource)) { - fclose($this->resource); - $this->resource = null; - } - } - - /** - * Set socket connection to be persistent. It only has effect before the connection is initiated. - */ - public function setPersistent(bool $persistent): self - { - $this->persistent = $persistent; - - return $this; - } - - /** - * Set connection timeout. Only has effect before we connect. - * - * @see http://php.net/manual/en/function.fsockopen.php - */ - public function setConnectionTimeout(float $seconds): self - { - $this->validateTimeout($seconds); - $this->connectionTimeout = $seconds; - - return $this; - } - - /** - * Set write timeout. Only has effect before we connect. - * - * @see http://php.net/manual/en/function.stream-set-timeout.php - */ - public function setTimeout(float $seconds): self - { - $this->validateTimeout($seconds); - $this->timeout = $seconds; - - return $this; - } - - /** - * Set writing timeout. Only has effect during connection in the writing cycle. - * - * @param float $seconds 0 for no timeout - */ - public function setWritingTimeout(float $seconds): self - { - $this->validateTimeout($seconds); - $this->writingTimeout = $seconds; - - return $this; - } - - /** - * Set chunk size. Only has effect during connection in the writing cycle. - */ - public function setChunkSize(int $bytes): self - { - $this->chunkSize = $bytes; - - return $this; - } - - /** - * Get current connection string - */ - public function getConnectionString(): string - { - return $this->connectionString; - } - - /** - * Get persistent setting - */ - public function isPersistent(): bool - { - return $this->persistent; - } - - /** - * Get current connection timeout setting - */ - public function getConnectionTimeout(): float - { - return $this->connectionTimeout; - } - - /** - * Get current in-transfer timeout - */ - public function getTimeout(): float - { - return $this->timeout; - } - - /** - * Get current local writing timeout - */ - public function getWritingTimeout(): float - { - return $this->writingTimeout; - } - - /** - * Get current chunk size - */ - public function getChunkSize(): ?int - { - return $this->chunkSize; - } - - /** - * Check to see if the socket is currently available. - * - * UDP might appear to be connected but might fail when writing. See http://php.net/fsockopen for details. - */ - public function isConnected(): bool - { - return is_resource($this->resource) - && !feof($this->resource); // on TCP - other party can close connection. - } - - /** - * Wrapper to allow mocking - * - * @return resource|false - */ - protected function pfsockopen() - { - return @pfsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); - } - - /** - * Wrapper to allow mocking - * - * @return resource|false - */ - protected function fsockopen() - { - return @fsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); - } - - /** - * Wrapper to allow mocking - * - * @see http://php.net/manual/en/function.stream-set-timeout.php - */ - protected function streamSetTimeout(): bool - { - $seconds = floor($this->timeout); - $microseconds = round(($this->timeout - $seconds) * 1e6); - - if (!is_resource($this->resource)) { - throw new \LogicException('streamSetTimeout called but $this->resource is not a resource'); - } - - return stream_set_timeout($this->resource, (int) $seconds, (int) $microseconds); - } - - /** - * Wrapper to allow mocking - * - * @see http://php.net/manual/en/function.stream-set-chunk-size.php - * - * @return int|false - */ - protected function streamSetChunkSize(): int|bool - { - if (!is_resource($this->resource)) { - throw new \LogicException('streamSetChunkSize called but $this->resource is not a resource'); - } - - if (null === $this->chunkSize) { - throw new \LogicException('streamSetChunkSize called but $this->chunkSize is not set'); - } - - return stream_set_chunk_size($this->resource, $this->chunkSize); - } - - /** - * Wrapper to allow mocking - * - * @return int|false - */ - protected function fwrite(string $data): int|bool - { - if (!is_resource($this->resource)) { - throw new \LogicException('fwrite called but $this->resource is not a resource'); - } - - return @fwrite($this->resource, $data); - } - - /** - * Wrapper to allow mocking - * - * @return mixed[]|bool - */ - protected function streamGetMetadata(): array|bool - { - if (!is_resource($this->resource)) { - throw new \LogicException('streamGetMetadata called but $this->resource is not a resource'); - } - - return stream_get_meta_data($this->resource); - } - - private function validateTimeout(float $value): void - { - if ($value < 0) { - throw new \InvalidArgumentException("Timeout must be 0 or a positive float (got $value)"); - } - } - - private function connectIfNotConnected(): void - { - if ($this->isConnected()) { - return; - } - $this->connect(); - } - - protected function generateDataStream(LogRecord $record): string - { - return (string) $record->formatted; - } - - /** - * @return resource|null - */ - protected function getResource() - { - return $this->resource; - } - - private function connect(): void - { - $this->createSocketResource(); - $this->setSocketTimeout(); - $this->setStreamChunkSize(); - } - - private function createSocketResource(): void - { - if ($this->isPersistent()) { - $resource = $this->pfsockopen(); - } else { - $resource = $this->fsockopen(); - } - if (is_bool($resource)) { - throw new \UnexpectedValueException("Failed connecting to $this->connectionString ($this->errno: $this->errstr)"); - } - $this->resource = $resource; - } - - private function setSocketTimeout(): void - { - if (!$this->streamSetTimeout()) { - throw new \UnexpectedValueException("Failed setting timeout with stream_set_timeout()"); - } - } - - private function setStreamChunkSize(): void - { - if (null !== $this->chunkSize && false === $this->streamSetChunkSize()) { - throw new \UnexpectedValueException("Failed setting chunk size with stream_set_chunk_size()"); - } - } - - private function writeToSocket(string $data): void - { - $length = strlen($data); - $sent = 0; - $this->lastSentBytes = $sent; - while ($this->isConnected() && $sent < $length) { - if (0 == $sent) { - $chunk = $this->fwrite($data); - } else { - $chunk = $this->fwrite(substr($data, $sent)); - } - if ($chunk === false) { - throw new \RuntimeException("Could not write to socket"); - } - $sent += $chunk; - $socketInfo = $this->streamGetMetadata(); - if (is_array($socketInfo) && (bool) $socketInfo['timed_out']) { - throw new \RuntimeException("Write timed-out"); - } - - if ($this->writingIsTimedOut($sent)) { - throw new \RuntimeException("Write timed-out, no data sent for `{$this->writingTimeout}` seconds, probably we got disconnected (sent $sent of $length)"); - } - } - if (!$this->isConnected() && $sent < $length) { - throw new \RuntimeException("End-of-file reached, probably we got disconnected (sent $sent of $length)"); - } - } - - private function writingIsTimedOut(int $sent): bool - { - // convert to ms - if (0.0 == $this->writingTimeout) { - return false; - } - - if ($sent !== $this->lastSentBytes) { - $this->lastWritingAt = microtime(true); - $this->lastSentBytes = $sent; - - return false; - } else { - usleep(100); - } - - if ((microtime(true) - (float) $this->lastWritingAt) >= $this->writingTimeout) { - $this->closeSocket(); - - return true; - } - - return false; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SqsHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SqsHandler.php deleted file mode 100644 index b4512a601b0c..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SqsHandler.php +++ /dev/null @@ -1,61 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Aws\Sqs\SqsClient; -use Monolog\Level; -use Monolog\Utils; -use Monolog\LogRecord; - -/** - * Writes to any sqs queue. - * - * @author Martijn van Calker - */ -class SqsHandler extends AbstractProcessingHandler -{ - /** 256 KB in bytes - maximum message size in SQS */ - protected const MAX_MESSAGE_SIZE = 262144; - /** 100 KB in bytes - head message size for new error log */ - protected const HEAD_MESSAGE_SIZE = 102400; - - private SqsClient $client; - private string $queueUrl; - - public function __construct(SqsClient $sqsClient, string $queueUrl, int|string|Level $level = Level::Debug, bool $bubble = true) - { - parent::__construct($level, $bubble); - - $this->client = $sqsClient; - $this->queueUrl = $queueUrl; - } - - /** - * @inheritDoc - */ - protected function write(LogRecord $record): void - { - if (!isset($record->formatted) || 'string' !== gettype($record->formatted)) { - throw new \InvalidArgumentException('SqsHandler accepts only formatted records as a string' . Utils::getRecordMessageForException($record)); - } - - $messageBody = $record->formatted; - if (strlen($messageBody) >= static::MAX_MESSAGE_SIZE) { - $messageBody = Utils::substr($messageBody, 0, static::HEAD_MESSAGE_SIZE); - } - - $this->client->sendMessage([ - 'QueueUrl' => $this->queueUrl, - 'MessageBody' => $messageBody, - ]); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/StreamHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/StreamHandler.php deleted file mode 100644 index 027a7217d503..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/StreamHandler.php +++ /dev/null @@ -1,202 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Monolog\Utils; -use Monolog\LogRecord; - -/** - * Stores to any stream resource - * - * Can be used to store into php://stderr, remote and local files, etc. - * - * @author Jordi Boggiano - */ -class StreamHandler extends AbstractProcessingHandler -{ - protected const MAX_CHUNK_SIZE = 2147483647; - /** 10MB */ - protected const DEFAULT_CHUNK_SIZE = 10 * 1024 * 1024; - protected int $streamChunkSize; - /** @var resource|null */ - protected $stream; - protected string|null $url = null; - private string|null $errorMessage = null; - protected int|null $filePermission; - protected bool $useLocking; - /** @var true|null */ - private bool|null $dirCreated = null; - - /** - * @param resource|string $stream If a missing path can't be created, an UnexpectedValueException will be thrown on first write - * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) - * @param bool $useLocking Try to lock log file before doing any writes - * - * @throws \InvalidArgumentException If stream is not a resource or string - */ - public function __construct($stream, int|string|Level $level = Level::Debug, bool $bubble = true, ?int $filePermission = null, bool $useLocking = false) - { - parent::__construct($level, $bubble); - - if (($phpMemoryLimit = Utils::expandIniShorthandBytes(ini_get('memory_limit'))) !== false) { - if ($phpMemoryLimit > 0) { - // use max 10% of allowed memory for the chunk size, and at least 100KB - $this->streamChunkSize = min(static::MAX_CHUNK_SIZE, max((int) ($phpMemoryLimit / 10), 100 * 1024)); - } else { - // memory is unlimited, set to the default 10MB - $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE; - } - } else { - // no memory limit information, set to the default 10MB - $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE; - } - - if (is_resource($stream)) { - $this->stream = $stream; - - stream_set_chunk_size($this->stream, $this->streamChunkSize); - } elseif (is_string($stream)) { - $this->url = Utils::canonicalizePath($stream); - } else { - throw new \InvalidArgumentException('A stream must either be a resource or a string.'); - } - - $this->filePermission = $filePermission; - $this->useLocking = $useLocking; - } - - /** - * @inheritDoc - */ - public function close(): void - { - if (null !== $this->url && is_resource($this->stream)) { - fclose($this->stream); - } - $this->stream = null; - $this->dirCreated = null; - } - - /** - * Return the currently active stream if it is open - * - * @return resource|null - */ - public function getStream() - { - return $this->stream; - } - - /** - * Return the stream URL if it was configured with a URL and not an active resource - */ - public function getUrl(): ?string - { - return $this->url; - } - - public function getStreamChunkSize(): int - { - return $this->streamChunkSize; - } - - /** - * @inheritDoc - */ - protected function write(LogRecord $record): void - { - if (!is_resource($this->stream)) { - $url = $this->url; - if (null === $url || '' === $url) { - throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().' . Utils::getRecordMessageForException($record)); - } - $this->createDir($url); - $this->errorMessage = null; - set_error_handler([$this, 'customErrorHandler']); - $stream = fopen($url, 'a'); - if ($this->filePermission !== null) { - @chmod($url, $this->filePermission); - } - restore_error_handler(); - if (!is_resource($stream)) { - $this->stream = null; - - throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened in append mode: '.$this->errorMessage, $url) . Utils::getRecordMessageForException($record)); - } - stream_set_chunk_size($stream, $this->streamChunkSize); - $this->stream = $stream; - } - - $stream = $this->stream; - if ($this->useLocking) { - // ignoring errors here, there's not much we can do about them - flock($stream, LOCK_EX); - } - - $this->streamWrite($stream, $record); - - if ($this->useLocking) { - flock($stream, LOCK_UN); - } - } - - /** - * Write to stream - * @param resource $stream - */ - protected function streamWrite($stream, LogRecord $record): void - { - fwrite($stream, (string) $record->formatted); - } - - private function customErrorHandler(int $code, string $msg): bool - { - $this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg); - - return true; - } - - private function getDirFromStream(string $stream): ?string - { - $pos = strpos($stream, '://'); - if ($pos === false) { - return dirname($stream); - } - - if ('file://' === substr($stream, 0, 7)) { - return dirname(substr($stream, 7)); - } - - return null; - } - - private function createDir(string $url): void - { - // Do not try to create dir if it has already been tried. - if (true === $this->dirCreated) { - return; - } - - $dir = $this->getDirFromStream($url); - if (null !== $dir && !is_dir($dir)) { - $this->errorMessage = null; - set_error_handler([$this, 'customErrorHandler']); - $status = mkdir($dir, 0777, true); - restore_error_handler(); - if (false === $status && !is_dir($dir) && strpos((string) $this->errorMessage, 'File exists') === false) { - throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and it could not be created: '.$this->errorMessage, $dir)); - } - } - $this->dirCreated = true; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php deleted file mode 100644 index 842b6577fa48..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SymfonyMailerHandler.php +++ /dev/null @@ -1,109 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Closure; -use Monolog\Level; -use Monolog\Logger; -use Monolog\LogRecord; -use Monolog\Utils; -use Monolog\Formatter\FormatterInterface; -use Monolog\Formatter\LineFormatter; -use Symfony\Component\Mailer\MailerInterface; -use Symfony\Component\Mailer\Transport\TransportInterface; -use Symfony\Component\Mime\Email; - -/** - * SymfonyMailerHandler uses Symfony's Mailer component to send the emails - * - * @author Jordi Boggiano - */ -class SymfonyMailerHandler extends MailHandler -{ - protected MailerInterface|TransportInterface $mailer; - /** @var Email|Closure(string, LogRecord[]): Email */ - private Email|Closure $emailTemplate; - - /** - * @phpstan-param Email|Closure(string, LogRecord[]): Email $email - * - * @param MailerInterface|TransportInterface $mailer The mailer to use - * @param Closure|Email $email An email template, the subject/body will be replaced - */ - public function __construct($mailer, Email|Closure $email, int|string|Level $level = Level::Error, bool $bubble = true) - { - parent::__construct($level, $bubble); - - $this->mailer = $mailer; - $this->emailTemplate = $email; - } - - /** - * {@inheritDoc} - */ - protected function send(string $content, array $records): void - { - $this->mailer->send($this->buildMessage($content, $records)); - } - - /** - * Gets the formatter for the Swift_Message subject. - * - * @param string|null $format The format of the subject - */ - protected function getSubjectFormatter(?string $format): FormatterInterface - { - return new LineFormatter($format); - } - - /** - * Creates instance of Email to be sent - * - * @param string $content formatted email body to be sent - * @param LogRecord[] $records Log records that formed the content - */ - protected function buildMessage(string $content, array $records): Email - { - $message = null; - if ($this->emailTemplate instanceof Email) { - $message = clone $this->emailTemplate; - } elseif (is_callable($this->emailTemplate)) { - $message = ($this->emailTemplate)($content, $records); - } - - if (!$message instanceof Email) { - $record = reset($records); - throw new \InvalidArgumentException('Could not resolve message as instance of Email or a callable returning it' . ($record instanceof LogRecord ? Utils::getRecordMessageForException($record) : '')); - } - - if (\count($records) > 0) { - $subjectFormatter = $this->getSubjectFormatter($message->getSubject()); - $message->subject($subjectFormatter->format($this->getHighestRecord($records))); - } - - if ($this->isHtmlBody($content)) { - if (null !== ($charset = $message->getHtmlCharset())) { - $message->html($content, $charset); - } else { - $message->html($content); - } - } else { - if (null !== ($charset = $message->getTextCharset())) { - $message->text($content, $charset); - } else { - $message->text($content); - } - } - - return $message->date(new \DateTimeImmutable()); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SyslogHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SyslogHandler.php deleted file mode 100644 index 99507a170ec3..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SyslogHandler.php +++ /dev/null @@ -1,64 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Monolog\Utils; -use Monolog\LogRecord; - -/** - * Logs to syslog service. - * - * usage example: - * - * $log = new Logger('application'); - * $syslog = new SyslogHandler('myfacility', 'local6'); - * $formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%"); - * $syslog->setFormatter($formatter); - * $log->pushHandler($syslog); - * - * @author Sven Paulus - */ -class SyslogHandler extends AbstractSyslogHandler -{ - protected string $ident; - protected int $logopts; - - /** - * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant - * @param int $logopts Option flags for the openlog() call, defaults to LOG_PID - */ - public function __construct(string $ident, string|int $facility = LOG_USER, int|string|Level $level = Level::Debug, bool $bubble = true, int $logopts = LOG_PID) - { - parent::__construct($facility, $level, $bubble); - - $this->ident = $ident; - $this->logopts = $logopts; - } - - /** - * @inheritDoc - */ - public function close(): void - { - closelog(); - } - - /** - * @inheritDoc - */ - protected function write(LogRecord $record): void - { - openlog($this->ident, $this->logopts, $this->facility); - syslog($this->toSyslogPriority($record->level), (string) $record->formatted); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php deleted file mode 100644 index 6a4833450be1..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.php +++ /dev/null @@ -1,77 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler\SyslogUdp; - -use Monolog\Utils; -use Socket; - -class UdpSocket -{ - protected const DATAGRAM_MAX_LENGTH = 65023; - - protected string $ip; - protected int $port; - protected ?Socket $socket = null; - - public function __construct(string $ip, int $port = 514) - { - $this->ip = $ip; - $this->port = $port; - } - - public function write(string $line, string $header = ""): void - { - $this->send($this->assembleMessage($line, $header)); - } - - public function close(): void - { - if ($this->socket instanceof Socket) { - socket_close($this->socket); - $this->socket = null; - } - } - - protected function getSocket(): Socket - { - if (null !== $this->socket) { - return $this->socket; - } - - $domain = AF_INET; - $protocol = SOL_UDP; - // Check if we are using unix sockets. - if ($this->port === 0) { - $domain = AF_UNIX; - $protocol = IPPROTO_IP; - } - - $socket = socket_create($domain, SOCK_DGRAM, $protocol); - if ($socket instanceof Socket) { - return $this->socket = $socket; - } - - throw new \RuntimeException('The UdpSocket to '.$this->ip.':'.$this->port.' could not be opened via socket_create'); - } - - protected function send(string $chunk): void - { - socket_sendto($this->getSocket(), $chunk, strlen($chunk), $flags = 0, $this->ip, $this->port); - } - - protected function assembleMessage(string $line, string $header): string - { - $chunkSize = static::DATAGRAM_MAX_LENGTH - strlen($header); - - return $header . Utils::substr($line, 0, $chunkSize); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php deleted file mode 100644 index 16a7286a1996..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php +++ /dev/null @@ -1,152 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use DateTimeInterface; -use Monolog\Handler\SyslogUdp\UdpSocket; -use Monolog\Level; -use Monolog\LogRecord; -use Monolog\Utils; - -/** - * A Handler for logging to a remote syslogd server. - * - * @author Jesper Skovgaard Nielsen - * @author Dominik Kukacka - */ -class SyslogUdpHandler extends AbstractSyslogHandler -{ - const RFC3164 = 0; - const RFC5424 = 1; - const RFC5424e = 2; - - /** @var array */ - private array $dateFormats = [ - self::RFC3164 => 'M d H:i:s', - self::RFC5424 => \DateTime::RFC3339, - self::RFC5424e => \DateTime::RFC3339_EXTENDED, - ]; - - protected UdpSocket $socket; - protected string $ident; - /** @var self::RFC* */ - protected int $rfc; - - /** - * @param string $host Either IP/hostname or a path to a unix socket (port must be 0 then) - * @param int $port Port number, or 0 if $host is a unix socket - * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not - * @param string $ident Program name or tag for each log message. - * @param int $rfc RFC to format the message for. - * @throws MissingExtensionException when there is no socket extension - * - * @phpstan-param self::RFC* $rfc - */ - public function __construct(string $host, int $port = 514, string|int $facility = LOG_USER, int|string|Level $level = Level::Debug, bool $bubble = true, string $ident = 'php', int $rfc = self::RFC5424) - { - if (!extension_loaded('sockets')) { - throw new MissingExtensionException('The sockets extension is required to use the SyslogUdpHandler'); - } - - parent::__construct($facility, $level, $bubble); - - $this->ident = $ident; - $this->rfc = $rfc; - - $this->socket = new UdpSocket($host, $port); - } - - protected function write(LogRecord $record): void - { - $lines = $this->splitMessageIntoLines($record->formatted); - - $header = $this->makeCommonSyslogHeader($this->toSyslogPriority($record->level), $record->datetime); - - foreach ($lines as $line) { - $this->socket->write($line, $header); - } - } - - public function close(): void - { - $this->socket->close(); - } - - /** - * @param string|string[] $message - * @return string[] - */ - private function splitMessageIntoLines($message): array - { - if (is_array($message)) { - $message = implode("\n", $message); - } - - $lines = preg_split('/$\R?^/m', (string) $message, -1, PREG_SPLIT_NO_EMPTY); - if (false === $lines) { - $pcreErrorCode = preg_last_error(); - - throw new \RuntimeException('Could not preg_split: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode)); - } - - return $lines; - } - - /** - * Make common syslog header (see rfc5424 or rfc3164) - */ - protected function makeCommonSyslogHeader(int $severity, DateTimeInterface $datetime): string - { - $priority = $severity + $this->facility; - - $pid = getmypid(); - if (false === $pid) { - $pid = '-'; - } - - $hostname = gethostname(); - if (false === $hostname) { - $hostname = '-'; - } - - if ($this->rfc === self::RFC3164) { - // see https://github.com/phpstan/phpstan/issues/5348 - // @phpstan-ignore-next-line - $dateNew = $datetime->setTimezone(new \DateTimeZone('UTC')); - $date = $dateNew->format($this->dateFormats[$this->rfc]); - - return "<$priority>" . - $date . " " . - $hostname . " " . - $this->ident . "[" . $pid . "]: "; - } - - $date = $datetime->format($this->dateFormats[$this->rfc]); - - return "<$priority>1 " . - $date . " " . - $hostname . " " . - $this->ident . " " . - $pid . " - - "; - } - - /** - * Inject your own socket, mainly used for testing - */ - public function setSocket(UdpSocket $socket): self - { - $this->socket = $socket; - - return $this; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php deleted file mode 100644 index 4302a03a9bf4..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/TelegramBotHandler.php +++ /dev/null @@ -1,279 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use RuntimeException; -use Monolog\Level; -use Monolog\Utils; -use Monolog\LogRecord; - -/** - * Handler send logs to Telegram using Telegram Bot API. - * - * How to use: - * 1) Create telegram bot with https://telegram.me/BotFather - * 2) Create a telegram channel where logs will be recorded. - * 3) Add created bot from step 1 to the created channel from step 2. - * - * Use telegram bot API key from step 1 and channel name with '@' prefix from step 2 to create instance of TelegramBotHandler - * - * @link https://core.telegram.org/bots/api - * - * @author Mazur Alexandr - */ -class TelegramBotHandler extends AbstractProcessingHandler -{ - private const BOT_API = 'https://api.telegram.org/bot'; - - /** - * The available values of parseMode according to the Telegram api documentation - */ - private const AVAILABLE_PARSE_MODES = [ - 'HTML', - 'MarkdownV2', - 'Markdown', // legacy mode without underline and strikethrough, use MarkdownV2 instead - ]; - - /** - * The maximum number of characters allowed in a message according to the Telegram api documentation - */ - private const MAX_MESSAGE_LENGTH = 4096; - - /** - * Telegram bot access token provided by BotFather. - * Create telegram bot with https://telegram.me/BotFather and use access token from it. - */ - private string $apiKey; - - /** - * Telegram channel name. - * Since to start with '@' symbol as prefix. - */ - private string $channel; - - /** - * The kind of formatting that is used for the message. - * See available options at https://core.telegram.org/bots/api#formatting-options - * or in AVAILABLE_PARSE_MODES - */ - private string|null $parseMode; - - /** - * Disables link previews for links in the message. - */ - private bool|null $disableWebPagePreview; - - /** - * Sends the message silently. Users will receive a notification with no sound. - */ - private bool|null $disableNotification; - - /** - * True - split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages. - * False - truncates a message that is too long. - */ - private bool $splitLongMessages; - - /** - * Adds 1-second delay between sending a split message (according to Telegram API to avoid 429 Too Many Requests). - */ - private bool $delayBetweenMessages; - - /** - * Telegram message thread id, unique identifier for the target message thread (topic) of the forum; for forum supergroups only - * See how to get the `message_thread_id` https://stackoverflow.com/a/75178418 - */ - private int|null $topic; - - /** - * @param string $apiKey Telegram bot access token provided by BotFather - * @param string $channel Telegram channel name - * @param bool $splitLongMessages Split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages - * @param bool $delayBetweenMessages Adds delay between sending a split message according to Telegram API - * @param int $topic Telegram message thread id, unique identifier for the target message thread (topic) of the forum - * @throws MissingExtensionException If the curl extension is missing - */ - public function __construct( - string $apiKey, - string $channel, - $level = Level::Debug, - bool $bubble = true, - string $parseMode = null, - bool $disableWebPagePreview = null, - bool $disableNotification = null, - bool $splitLongMessages = false, - bool $delayBetweenMessages = false, - int $topic = null - ) { - if (!extension_loaded('curl')) { - throw new MissingExtensionException('The curl extension is needed to use the TelegramBotHandler'); - } - - parent::__construct($level, $bubble); - - $this->apiKey = $apiKey; - $this->channel = $channel; - $this->setParseMode($parseMode); - $this->disableWebPagePreview($disableWebPagePreview); - $this->disableNotification($disableNotification); - $this->splitLongMessages($splitLongMessages); - $this->delayBetweenMessages($delayBetweenMessages); - $this->setTopic($topic); - } - - public function setParseMode(string $parseMode = null): self - { - if ($parseMode !== null && !in_array($parseMode, self::AVAILABLE_PARSE_MODES, true)) { - throw new \InvalidArgumentException('Unknown parseMode, use one of these: ' . implode(', ', self::AVAILABLE_PARSE_MODES) . '.'); - } - - $this->parseMode = $parseMode; - - return $this; - } - - public function disableWebPagePreview(bool $disableWebPagePreview = null): self - { - $this->disableWebPagePreview = $disableWebPagePreview; - - return $this; - } - - public function disableNotification(bool $disableNotification = null): self - { - $this->disableNotification = $disableNotification; - - return $this; - } - - /** - * True - split a message longer than MAX_MESSAGE_LENGTH into parts and send in multiple messages. - * False - truncates a message that is too long. - * @return $this - */ - public function splitLongMessages(bool $splitLongMessages = false): self - { - $this->splitLongMessages = $splitLongMessages; - - return $this; - } - - /** - * Adds 1-second delay between sending a split message (according to Telegram API to avoid 429 Too Many Requests). - * @return $this - */ - public function delayBetweenMessages(bool $delayBetweenMessages = false): self - { - $this->delayBetweenMessages = $delayBetweenMessages; - - return $this; - } - - public function setTopic(int $topic = null): self - { - $this->topic = $topic; - - return $this; - } - - /** - * @inheritDoc - */ - public function handleBatch(array $records): void - { - $messages = []; - - foreach ($records as $record) { - if (!$this->isHandling($record)) { - continue; - } - - if (\count($this->processors) > 0) { - $record = $this->processRecord($record); - } - - $messages[] = $record; - } - - if (\count($messages) > 0) { - $this->send((string) $this->getFormatter()->formatBatch($messages)); - } - } - - /** - * @inheritDoc - */ - protected function write(LogRecord $record): void - { - $this->send($record->formatted); - } - - /** - * Send request to @link https://api.telegram.org/bot on SendMessage action. - */ - protected function send(string $message): void - { - $messages = $this->handleMessageLength($message); - - foreach ($messages as $key => $msg) { - if ($this->delayBetweenMessages && $key > 0) { - sleep(1); - } - - $this->sendCurl($msg); - } - } - - protected function sendCurl(string $message): void - { - $ch = curl_init(); - $url = self::BOT_API . $this->apiKey . '/SendMessage'; - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); - $params = [ - 'text' => $message, - 'chat_id' => $this->channel, - 'parse_mode' => $this->parseMode, - 'disable_web_page_preview' => $this->disableWebPagePreview, - 'disable_notification' => $this->disableNotification, - ]; - if ($this->topic !== null) { - $params['message_thread_id'] = $this->topic; - } - curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params)); - - $result = Curl\Util::execute($ch); - if (!is_string($result)) { - throw new RuntimeException('Telegram API error. Description: No response'); - } - $result = json_decode($result, true); - - if ($result['ok'] === false) { - throw new RuntimeException('Telegram API error. Description: ' . $result['description']); - } - } - - /** - * Handle a message that is too long: truncates or splits into several - * @return string[] - */ - private function handleMessageLength(string $message): array - { - $truncatedMarker = ' (...truncated)'; - if (!$this->splitLongMessages && strlen($message) > self::MAX_MESSAGE_LENGTH) { - return [Utils::substr($message, 0, self::MAX_MESSAGE_LENGTH - strlen($truncatedMarker)) . $truncatedMarker]; - } - - return str_split($message, self::MAX_MESSAGE_LENGTH); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/TestHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/TestHandler.php deleted file mode 100644 index 8e356ef31c02..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/TestHandler.php +++ /dev/null @@ -1,195 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Level; -use Monolog\Logger; -use Psr\Log\LogLevel; -use Monolog\LogRecord; - -/** - * Used for testing purposes. - * - * It records all records and gives you access to them for verification. - * - * @author Jordi Boggiano - * - * @method bool hasEmergency(string|array $recordAssertions) - * @method bool hasAlert(string|array $recordAssertions) - * @method bool hasCritical(string|array $recordAssertions) - * @method bool hasError(string|array $recordAssertions) - * @method bool hasWarning(string|array $recordAssertions) - * @method bool hasNotice(string|array $recordAssertions) - * @method bool hasInfo(string|array $recordAssertions) - * @method bool hasDebug(string|array $recordAssertions) - * - * @method bool hasEmergencyRecords() - * @method bool hasAlertRecords() - * @method bool hasCriticalRecords() - * @method bool hasErrorRecords() - * @method bool hasWarningRecords() - * @method bool hasNoticeRecords() - * @method bool hasInfoRecords() - * @method bool hasDebugRecords() - * - * @method bool hasEmergencyThatContains(string $message) - * @method bool hasAlertThatContains(string $message) - * @method bool hasCriticalThatContains(string $message) - * @method bool hasErrorThatContains(string $message) - * @method bool hasWarningThatContains(string $message) - * @method bool hasNoticeThatContains(string $message) - * @method bool hasInfoThatContains(string $message) - * @method bool hasDebugThatContains(string $message) - * - * @method bool hasEmergencyThatMatches(string $regex) - * @method bool hasAlertThatMatches(string $regex) - * @method bool hasCriticalThatMatches(string $regex) - * @method bool hasErrorThatMatches(string $regex) - * @method bool hasWarningThatMatches(string $regex) - * @method bool hasNoticeThatMatches(string $regex) - * @method bool hasInfoThatMatches(string $regex) - * @method bool hasDebugThatMatches(string $regex) - * - * @method bool hasEmergencyThatPasses(callable $predicate) - * @method bool hasAlertThatPasses(callable $predicate) - * @method bool hasCriticalThatPasses(callable $predicate) - * @method bool hasErrorThatPasses(callable $predicate) - * @method bool hasWarningThatPasses(callable $predicate) - * @method bool hasNoticeThatPasses(callable $predicate) - * @method bool hasInfoThatPasses(callable $predicate) - * @method bool hasDebugThatPasses(callable $predicate) - */ -class TestHandler extends AbstractProcessingHandler -{ - /** @var LogRecord[] */ - protected array $records = []; - /** @phpstan-var array, LogRecord[]> */ - protected array $recordsByLevel = []; - private bool $skipReset = false; - - /** - * @return array - */ - public function getRecords(): array - { - return $this->records; - } - - public function clear(): void - { - $this->records = []; - $this->recordsByLevel = []; - } - - public function reset(): void - { - if (!$this->skipReset) { - $this->clear(); - } - } - - public function setSkipReset(bool $skipReset): void - { - $this->skipReset = $skipReset; - } - - /** - * @param int|string|Level|LogLevel::* $level Logging level value or name - * - * @phpstan-param value-of|value-of|Level|LogLevel::* $level - */ - public function hasRecords(int|string|Level $level): bool - { - return isset($this->recordsByLevel[Logger::toMonologLevel($level)->value]); - } - - /** - * @param string|array $recordAssertions Either a message string or an array containing message and optionally context keys that will be checked against all records - * - * @phpstan-param array{message: string, context?: mixed[]}|string $recordAssertions - */ - public function hasRecord(string|array $recordAssertions, Level $level): bool - { - if (is_string($recordAssertions)) { - $recordAssertions = ['message' => $recordAssertions]; - } - - return $this->hasRecordThatPasses(function (LogRecord $rec) use ($recordAssertions) { - if ($rec->message !== $recordAssertions['message']) { - return false; - } - if (isset($recordAssertions['context']) && $rec->context !== $recordAssertions['context']) { - return false; - } - - return true; - }, $level); - } - - public function hasRecordThatContains(string $message, Level $level): bool - { - return $this->hasRecordThatPasses(fn (LogRecord $rec) => str_contains($rec->message, $message), $level); - } - - public function hasRecordThatMatches(string $regex, Level $level): bool - { - return $this->hasRecordThatPasses(fn (LogRecord $rec) => preg_match($regex, $rec->message) > 0, $level); - } - - /** - * @phpstan-param callable(LogRecord, int): mixed $predicate - */ - public function hasRecordThatPasses(callable $predicate, Level $level): bool - { - $level = Logger::toMonologLevel($level); - - if (!isset($this->recordsByLevel[$level->value])) { - return false; - } - - foreach ($this->recordsByLevel[$level->value] as $i => $rec) { - if ((bool) $predicate($rec, $i)) { - return true; - } - } - - return false; - } - - /** - * @inheritDoc - */ - protected function write(LogRecord $record): void - { - $this->recordsByLevel[$record->level->value][] = $record; - $this->records[] = $record; - } - - /** - * @param mixed[] $args - */ - public function __call(string $method, array $args): bool - { - if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { - $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; - $level = constant(Level::class.'::' . $matches[2]); - $callback = [$this, $genericMethod]; - if (is_callable($callback)) { - $args[] = $level; - - return call_user_func_array($callback, $args); - } - } - - throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()'); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php deleted file mode 100644 index 9c12c3d5698e..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/WebRequestRecognizerTrait.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -trait WebRequestRecognizerTrait -{ - /** - * Checks if PHP's serving a web request - */ - protected function isWebRequest(): bool - { - return 'cli' !== \PHP_SAPI && 'phpdbg' !== \PHP_SAPI; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php deleted file mode 100644 index 2d4e817a0559..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php +++ /dev/null @@ -1,80 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\LogRecord; -use Throwable; - -/** - * Forwards records to multiple handlers suppressing failures of each handler - * and continuing through to give every handler a chance to succeed. - * - * @author Craig D'Amelio - */ -class WhatFailureGroupHandler extends GroupHandler -{ - /** - * @inheritDoc - */ - public function handle(LogRecord $record): bool - { - if (\count($this->processors) > 0) { - $record = $this->processRecord($record); - } - - foreach ($this->handlers as $handler) { - try { - $handler->handle($record); - } catch (Throwable) { - // What failure? - } - } - - return false === $this->bubble; - } - - /** - * @inheritDoc - */ - public function handleBatch(array $records): void - { - if (\count($this->processors) > 0) { - $processed = []; - foreach ($records as $record) { - $processed[] = $this->processRecord($record); - } - $records = $processed; - } - - foreach ($this->handlers as $handler) { - try { - $handler->handleBatch($records); - } catch (Throwable) { - // What failure? - } - } - } - - /** - * {@inheritDoc} - */ - public function close(): void - { - foreach ($this->handlers as $handler) { - try { - $handler->close(); - } catch (\Throwable $e) { - // What failure? - } - } - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php deleted file mode 100644 index 1e71194bc086..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php +++ /dev/null @@ -1,90 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Handler; - -use Monolog\Formatter\FormatterInterface; -use Monolog\Formatter\NormalizerFormatter; -use Monolog\Level; -use Monolog\LogRecord; - -/** - * Handler sending logs to Zend Monitor - * - * @author Christian Bergau - * @author Jason Davis - */ -class ZendMonitorHandler extends AbstractProcessingHandler -{ - /** - * @throws MissingExtensionException - */ - public function __construct(int|string|Level $level = Level::Debug, bool $bubble = true) - { - if (!function_exists('zend_monitor_custom_event')) { - throw new MissingExtensionException( - 'You must have Zend Server installed with Zend Monitor enabled in order to use this handler' - ); - } - - parent::__construct($level, $bubble); - } - - /** - * Translates Monolog log levels to ZendMonitor levels. - */ - protected function toZendMonitorLevel(Level $level): int - { - return match ($level) { - Level::Debug => \ZEND_MONITOR_EVENT_SEVERITY_INFO, - Level::Info => \ZEND_MONITOR_EVENT_SEVERITY_INFO, - Level::Notice => \ZEND_MONITOR_EVENT_SEVERITY_INFO, - Level::Warning => \ZEND_MONITOR_EVENT_SEVERITY_WARNING, - Level::Error => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, - Level::Critical => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, - Level::Alert => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, - Level::Emergency => \ZEND_MONITOR_EVENT_SEVERITY_ERROR, - }; - } - - /** - * @inheritDoc - */ - protected function write(LogRecord $record): void - { - $this->writeZendMonitorCustomEvent( - $record->level->getName(), - $record->message, - $record->formatted, - $this->toZendMonitorLevel($record->level) - ); - } - - /** - * Write to Zend Monitor Events - * @param string $type Text displayed in "Class Name (custom)" field - * @param string $message Text displayed in "Error String" - * @param array $formatted Displayed in Custom Variables tab - * @param int $severity Set the event severity level (-1,0,1) - */ - protected function writeZendMonitorCustomEvent(string $type, string $message, array $formatted, int $severity): void - { - zend_monitor_custom_event($type, $message, $formatted, $severity); - } - - /** - * @inheritDoc - */ - public function getDefaultFormatter(): FormatterInterface - { - return new NormalizerFormatter(); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Level.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Level.php deleted file mode 100644 index 097d42135bd9..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Level.php +++ /dev/null @@ -1,209 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog; - -use Psr\Log\LogLevel; - -/** - * Represents the log levels - * - * Monolog supports the logging levels described by RFC 5424 {@see https://datatracker.ietf.org/doc/html/rfc5424} - * but due to BC the severity values used internally are not 0-7. - * - * To get the level name/value out of a Level there are several options: - * - * - Use ->getName() to get the standard Monolog name which is full uppercased (e.g. "DEBUG") - * - Use ->toPsrLogLevel() to get the standard PSR-3 name which is full lowercased (e.g. "debug") - * - Use ->toRFC5424Level() to get the standard RFC 5424 value (e.g. 7 for debug, 0 for emergency) - * - Use ->name to get the enum case's name which is capitalized (e.g. "Debug") - * - * To get the internal value for filtering, if the includes/isLowerThan/isHigherThan methods are - * not enough, you can use ->value to get the enum case's integer value. - */ -enum Level: int -{ - /** - * Detailed debug information - */ - case Debug = 100; - - /** - * Interesting events - * - * Examples: User logs in, SQL logs. - */ - case Info = 200; - - /** - * Uncommon events - */ - case Notice = 250; - - /** - * Exceptional occurrences that are not errors - * - * Examples: Use of deprecated APIs, poor use of an API, - * undesirable things that are not necessarily wrong. - */ - case Warning = 300; - - /** - * Runtime errors - */ - case Error = 400; - - /** - * Critical conditions - * - * Example: Application component unavailable, unexpected exception. - */ - case Critical = 500; - - /** - * Action must be taken immediately - * - * Example: Entire website down, database unavailable, etc. - * This should trigger the SMS alerts and wake you up. - */ - case Alert = 550; - - /** - * Urgent alert. - */ - case Emergency = 600; - - /** - * @param value-of|LogLevel::*|'Debug'|'Info'|'Notice'|'Warning'|'Error'|'Critical'|'Alert'|'Emergency' $name - * @return static - */ - public static function fromName(string $name): self - { - return match ($name) { - 'debug', 'Debug', 'DEBUG' => self::Debug, - 'info', 'Info', 'INFO' => self::Info, - 'notice', 'Notice', 'NOTICE' => self::Notice, - 'warning', 'Warning', 'WARNING' => self::Warning, - 'error', 'Error', 'ERROR' => self::Error, - 'critical', 'Critical', 'CRITICAL' => self::Critical, - 'alert', 'Alert', 'ALERT' => self::Alert, - 'emergency', 'Emergency', 'EMERGENCY' => self::Emergency, - }; - } - - /** - * @param value-of $value - * @return static - */ - public static function fromValue(int $value): self - { - return self::from($value); - } - - /** - * Returns true if the passed $level is higher or equal to $this - */ - public function includes(Level $level): bool - { - return $this->value <= $level->value; - } - - public function isHigherThan(Level $level): bool - { - return $this->value > $level->value; - } - - public function isLowerThan(Level $level): bool - { - return $this->value < $level->value; - } - - /** - * Returns the monolog standardized all-capitals name of the level - * - * Use this instead of $level->name which returns the enum case name (e.g. Debug vs DEBUG if you use getName()) - * - * @return value-of - */ - public function getName(): string - { - return match ($this) { - self::Debug => 'DEBUG', - self::Info => 'INFO', - self::Notice => 'NOTICE', - self::Warning => 'WARNING', - self::Error => 'ERROR', - self::Critical => 'CRITICAL', - self::Alert => 'ALERT', - self::Emergency => 'EMERGENCY', - }; - } - - /** - * Returns the PSR-3 level matching this instance - * - * @phpstan-return \Psr\Log\LogLevel::* - */ - public function toPsrLogLevel(): string - { - return match ($this) { - self::Debug => LogLevel::DEBUG, - self::Info => LogLevel::INFO, - self::Notice => LogLevel::NOTICE, - self::Warning => LogLevel::WARNING, - self::Error => LogLevel::ERROR, - self::Critical => LogLevel::CRITICAL, - self::Alert => LogLevel::ALERT, - self::Emergency => LogLevel::EMERGENCY, - }; - } - - /** - * Returns the RFC 5424 level matching this instance - * - * @phpstan-return int<0, 7> - */ - public function toRFC5424Level(): int - { - return match ($this) { - self::Debug => 7, - self::Info => 6, - self::Notice => 5, - self::Warning => 4, - self::Error => 3, - self::Critical => 2, - self::Alert => 1, - self::Emergency => 0, - }; - } - - public const VALUES = [ - 100, - 200, - 250, - 300, - 400, - 500, - 550, - 600, - ]; - - public const NAMES = [ - 'DEBUG', - 'INFO', - 'NOTICE', - 'WARNING', - 'ERROR', - 'CRITICAL', - 'ALERT', - 'EMERGENCY', - ]; -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/LogRecord.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/LogRecord.php deleted file mode 100644 index df758c58bf9c..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/LogRecord.php +++ /dev/null @@ -1,124 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog; - -use ArrayAccess; - -/** - * Monolog log record - * - * @author Jordi Boggiano - * @template-implements ArrayAccess<'message'|'level'|'context'|'level_name'|'channel'|'datetime'|'extra', int|string|\DateTimeImmutable|array> - */ -class LogRecord implements ArrayAccess -{ - private const MODIFIABLE_FIELDS = [ - 'extra' => true, - 'formatted' => true, - ]; - - public function __construct( - public readonly \DateTimeImmutable $datetime, - public readonly string $channel, - public readonly Level $level, - public readonly string $message, - /** @var array */ - public readonly array $context = [], - /** @var array */ - public array $extra = [], - public mixed $formatted = null, - ) { - } - - public function offsetSet(mixed $offset, mixed $value): void - { - if ($offset === 'extra') { - if (!is_array($value)) { - throw new \InvalidArgumentException('extra must be an array'); - } - - $this->extra = $value; - - return; - } - - if ($offset === 'formatted') { - $this->formatted = $value; - - return; - } - - throw new \LogicException('Unsupported operation: setting '.$offset); - } - - public function offsetExists(mixed $offset): bool - { - if ($offset === 'level_name') { - return true; - } - - return isset($this->{$offset}); - } - - public function offsetUnset(mixed $offset): void - { - throw new \LogicException('Unsupported operation'); - } - - public function &offsetGet(mixed $offset): mixed - { - if ($offset === 'level_name' || $offset === 'level') { - // avoid returning readonly props by ref as this is illegal - if ($offset === 'level_name') { - $copy = $this->level->getName(); - } else { - $copy = $this->level->value; - } - - return $copy; - } - - if (isset(self::MODIFIABLE_FIELDS[$offset])) { - return $this->{$offset}; - } - - // avoid returning readonly props by ref as this is illegal - $copy = $this->{$offset}; - - return $copy; - } - - /** - * @phpstan-return array{message: string, context: mixed[], level: value-of, level_name: value-of, channel: string, datetime: \DateTimeImmutable, extra: mixed[]} - */ - public function toArray(): array - { - return [ - 'message' => $this->message, - 'context' => $this->context, - 'level' => $this->level->value, - 'level_name' => $this->level->getName(), - 'channel' => $this->channel, - 'datetime' => $this->datetime, - 'extra' => $this->extra, - ]; - } - - public function with(mixed ...$args): self - { - foreach (['message', 'context', 'level', 'channel', 'datetime', 'extra'] as $prop) { - $args[$prop] ??= $this->{$prop}; - } - - return new self(...$args); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Logger.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Logger.php deleted file mode 100644 index db8bc21babe8..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Logger.php +++ /dev/null @@ -1,735 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog; - -use Closure; -use DateTimeZone; -use Fiber; -use Monolog\Handler\HandlerInterface; -use Monolog\Processor\ProcessorInterface; -use Psr\Log\LoggerInterface; -use Psr\Log\InvalidArgumentException; -use Psr\Log\LogLevel; -use Throwable; -use Stringable; -use WeakMap; - -/** - * Monolog log channel - * - * It contains a stack of Handlers and a stack of Processors, - * and uses them to store records that are added to it. - * - * @author Jordi Boggiano - * @final - */ -class Logger implements LoggerInterface, ResettableInterface -{ - /** - * Detailed debug information - * - * @deprecated Use \Monolog\Level::Debug - */ - public const DEBUG = 100; - - /** - * Interesting events - * - * Examples: User logs in, SQL logs. - * - * @deprecated Use \Monolog\Level::Info - */ - public const INFO = 200; - - /** - * Uncommon events - * - * @deprecated Use \Monolog\Level::Notice - */ - public const NOTICE = 250; - - /** - * Exceptional occurrences that are not errors - * - * Examples: Use of deprecated APIs, poor use of an API, - * undesirable things that are not necessarily wrong. - * - * @deprecated Use \Monolog\Level::Warning - */ - public const WARNING = 300; - - /** - * Runtime errors - * - * @deprecated Use \Monolog\Level::Error - */ - public const ERROR = 400; - - /** - * Critical conditions - * - * Example: Application component unavailable, unexpected exception. - * - * @deprecated Use \Monolog\Level::Critical - */ - public const CRITICAL = 500; - - /** - * Action must be taken immediately - * - * Example: Entire website down, database unavailable, etc. - * This should trigger the SMS alerts and wake you up. - * - * @deprecated Use \Monolog\Level::Alert - */ - public const ALERT = 550; - - /** - * Urgent alert. - * - * @deprecated Use \Monolog\Level::Emergency - */ - public const EMERGENCY = 600; - - /** - * Monolog API version - * - * This is only bumped when API breaks are done and should - * follow the major version of the library - */ - public const API = 3; - - /** - * Mapping between levels numbers defined in RFC 5424 and Monolog ones - * - * @phpstan-var array $rfc_5424_levels - */ - private const RFC_5424_LEVELS = [ - 7 => Level::Debug, - 6 => Level::Info, - 5 => Level::Notice, - 4 => Level::Warning, - 3 => Level::Error, - 2 => Level::Critical, - 1 => Level::Alert, - 0 => Level::Emergency, - ]; - - protected string $name; - - /** - * The handler stack - * - * @var list - */ - protected array $handlers; - - /** - * Processors that will process all log records - * - * To process records of a single handler instead, add the processor on that specific handler - * - * @var array<(callable(LogRecord): LogRecord)|ProcessorInterface> - */ - protected array $processors; - - protected bool $microsecondTimestamps = true; - - protected DateTimeZone $timezone; - - protected Closure|null $exceptionHandler = null; - - /** - * Keeps track of depth to prevent infinite logging loops - */ - private int $logDepth = 0; - - /** - * @var WeakMap, int> Keeps track of depth inside fibers to prevent infinite logging loops - */ - private WeakMap $fiberLogDepth; - - /** - * Whether to detect infinite logging loops - * This can be disabled via {@see useLoggingLoopDetection} if you have async handlers that do not play well with this - */ - private bool $detectCycles = true; - - /** - * @param string $name The logging channel, a simple descriptive name that is attached to all log records - * @param HandlerInterface[] $handlers Optional stack of handlers, the first one in the array is called first, etc. - * @param callable[] $processors Optional array of processors - * @param DateTimeZone|null $timezone Optional timezone, if not provided date_default_timezone_get() will be used - * - * @phpstan-param array<(callable(LogRecord): LogRecord)|ProcessorInterface> $processors - */ - public function __construct(string $name, array $handlers = [], array $processors = [], DateTimeZone|null $timezone = null) - { - $this->name = $name; - $this->setHandlers($handlers); - $this->processors = $processors; - $this->timezone = $timezone ?? new DateTimeZone(date_default_timezone_get()); - $this->fiberLogDepth = new \WeakMap(); - } - - public function getName(): string - { - return $this->name; - } - - /** - * Return a new cloned instance with the name changed - */ - public function withName(string $name): self - { - $new = clone $this; - $new->name = $name; - - return $new; - } - - /** - * Pushes a handler on to the stack. - */ - public function pushHandler(HandlerInterface $handler): self - { - array_unshift($this->handlers, $handler); - - return $this; - } - - /** - * Pops a handler from the stack - * - * @throws \LogicException If empty handler stack - */ - public function popHandler(): HandlerInterface - { - if (0 === \count($this->handlers)) { - throw new \LogicException('You tried to pop from an empty handler stack.'); - } - - return array_shift($this->handlers); - } - - /** - * Set handlers, replacing all existing ones. - * - * If a map is passed, keys will be ignored. - * - * @param list $handlers - */ - public function setHandlers(array $handlers): self - { - $this->handlers = []; - foreach (array_reverse($handlers) as $handler) { - $this->pushHandler($handler); - } - - return $this; - } - - /** - * @return list - */ - public function getHandlers(): array - { - return $this->handlers; - } - - /** - * Adds a processor on to the stack. - * - * @phpstan-param ProcessorInterface|(callable(LogRecord): LogRecord) $callback - */ - public function pushProcessor(ProcessorInterface|callable $callback): self - { - array_unshift($this->processors, $callback); - - return $this; - } - - /** - * Removes the processor on top of the stack and returns it. - * - * @phpstan-return ProcessorInterface|(callable(LogRecord): LogRecord) - * @throws \LogicException If empty processor stack - */ - public function popProcessor(): callable - { - if (0 === \count($this->processors)) { - throw new \LogicException('You tried to pop from an empty processor stack.'); - } - - return array_shift($this->processors); - } - - /** - * @return callable[] - * @phpstan-return array - */ - public function getProcessors(): array - { - return $this->processors; - } - - /** - * Control the use of microsecond resolution timestamps in the 'datetime' - * member of new records. - * - * As of PHP7.1 microseconds are always included by the engine, so - * there is no performance penalty and Monolog 2 enabled microseconds - * by default. This function lets you disable them though in case you want - * to suppress microseconds from the output. - * - * @param bool $micro True to use microtime() to create timestamps - */ - public function useMicrosecondTimestamps(bool $micro): self - { - $this->microsecondTimestamps = $micro; - - return $this; - } - - public function useLoggingLoopDetection(bool $detectCycles): self - { - $this->detectCycles = $detectCycles; - - return $this; - } - - /** - * Adds a log record. - * - * @param int $level The logging level (a Monolog or RFC 5424 level) - * @param string $message The log message - * @param mixed[] $context The log context - * @param DateTimeImmutable $datetime Optional log date to log into the past or future - * @return bool Whether the record has been processed - * - * @phpstan-param value-of|Level $level - */ - public function addRecord(int|Level $level, string $message, array $context = [], DateTimeImmutable $datetime = null): bool - { - if (is_int($level) && isset(self::RFC_5424_LEVELS[$level])) { - $level = self::RFC_5424_LEVELS[$level]; - } - - if ($this->detectCycles) { - if (null !== ($fiber = Fiber::getCurrent())) { - $logDepth = $this->fiberLogDepth[$fiber] = ($this->fiberLogDepth[$fiber] ?? 0) + 1; - } else { - $logDepth = ++$this->logDepth; - } - } else { - $logDepth = 0; - } - - if ($logDepth === 3) { - $this->warning('A possible infinite logging loop was detected and aborted. It appears some of your handler code is triggering logging, see the previous log record for a hint as to what may be the cause.'); - return false; - } elseif ($logDepth >= 5) { // log depth 4 is let through, so we can log the warning above - return false; - } - - try { - $recordInitialized = count($this->processors) === 0; - - $record = new LogRecord( - message: $message, - context: $context, - level: self::toMonologLevel($level), - channel: $this->name, - datetime: $datetime ?? new DateTimeImmutable($this->microsecondTimestamps, $this->timezone), - extra: [], - ); - $handled = false; - - foreach ($this->handlers as $handler) { - if (false === $recordInitialized) { - // skip initializing the record as long as no handler is going to handle it - if (!$handler->isHandling($record)) { - continue; - } - - try { - foreach ($this->processors as $processor) { - $record = $processor($record); - } - $recordInitialized = true; - } catch (Throwable $e) { - $this->handleException($e, $record); - - return true; - } - } - - // once the record is initialized, send it to all handlers as long as the bubbling chain is not interrupted - try { - $handled = true; - if (true === $handler->handle($record)) { - break; - } - } catch (Throwable $e) { - $this->handleException($e, $record); - - return true; - } - } - - return $handled; - } finally { - if ($this->detectCycles) { - if (isset($fiber)) { - $this->fiberLogDepth[$fiber]--; - } else { - $this->logDepth--; - } - } - } - } - - /** - * Ends a log cycle and frees all resources used by handlers. - * - * Closing a Handler means flushing all buffers and freeing any open resources/handles. - * Handlers that have been closed should be able to accept log records again and re-open - * themselves on demand, but this may not always be possible depending on implementation. - * - * This is useful at the end of a request and will be called automatically on every handler - * when they get destructed. - */ - public function close(): void - { - foreach ($this->handlers as $handler) { - $handler->close(); - } - } - - /** - * Ends a log cycle and resets all handlers and processors to their initial state. - * - * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal - * state, and getting it back to a state in which it can receive log records again. - * - * This is useful in case you want to avoid logs leaking between two requests or jobs when you - * have a long running process like a worker or an application server serving multiple requests - * in one process. - */ - public function reset(): void - { - foreach ($this->handlers as $handler) { - if ($handler instanceof ResettableInterface) { - $handler->reset(); - } - } - - foreach ($this->processors as $processor) { - if ($processor instanceof ResettableInterface) { - $processor->reset(); - } - } - } - - /** - * Gets the name of the logging level as a string. - * - * This still returns a string instead of a Level for BC, but new code should not rely on this method. - * - * @throws \Psr\Log\InvalidArgumentException If level is not defined - * - * @phpstan-param value-of|Level $level - * @phpstan-return value-of - * - * @deprecated Since 3.0, use {@see toMonologLevel} or {@see \Monolog\Level->getName()} instead - */ - public static function getLevelName(int|Level $level): string - { - return self::toMonologLevel($level)->getName(); - } - - /** - * Converts PSR-3 levels to Monolog ones if necessary - * - * @param int|string|Level|LogLevel::* $level Level number (monolog) or name (PSR-3) - * @throws \Psr\Log\InvalidArgumentException If level is not defined - * - * @phpstan-param value-of|value-of|Level|LogLevel::* $level - */ - public static function toMonologLevel(string|int|Level $level): Level - { - if ($level instanceof Level) { - return $level; - } - - if (\is_string($level)) { - if (\is_numeric($level)) { - $levelEnum = Level::tryFrom((int) $level); - if ($levelEnum === null) { - throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', Level::NAMES + Level::VALUES)); - } - - return $levelEnum; - } - - // Contains first char of all log levels and avoids using strtoupper() which may have - // strange results depending on locale (for example, "i" will become "İ" in Turkish locale) - $upper = strtr(substr($level, 0, 1), 'dinweca', 'DINWECA') . strtolower(substr($level, 1)); - if (defined(Level::class.'::'.$upper)) { - return constant(Level::class . '::' . $upper); - } - - throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', Level::NAMES + Level::VALUES)); - } - - $levelEnum = Level::tryFrom($level); - if ($levelEnum === null) { - throw new InvalidArgumentException('Level "'.var_export($level, true).'" is not defined, use one of: '.implode(', ', Level::NAMES + Level::VALUES)); - } - - return $levelEnum; - } - - /** - * Checks whether the Logger has a handler that listens on the given level - * - * @phpstan-param value-of|value-of|Level|LogLevel::* $level - */ - public function isHandling(int|string|Level $level): bool - { - $record = new LogRecord( - datetime: new DateTimeImmutable($this->microsecondTimestamps, $this->timezone), - channel: $this->name, - message: '', - level: self::toMonologLevel($level), - ); - - foreach ($this->handlers as $handler) { - if ($handler->isHandling($record)) { - return true; - } - } - - return false; - } - - /** - * Set a custom exception handler that will be called if adding a new record fails - * - * The Closure will receive an exception object and the record that failed to be logged - */ - public function setExceptionHandler(Closure|null $callback): self - { - $this->exceptionHandler = $callback; - - return $this; - } - - public function getExceptionHandler(): Closure|null - { - return $this->exceptionHandler; - } - - /** - * Adds a log record at an arbitrary level. - * - * This method allows for compatibility with common interfaces. - * - * @param mixed $level The log level (a Monolog, PSR-3 or RFC 5424 level) - * @param string|Stringable $message The log message - * @param mixed[] $context The log context - * - * @phpstan-param Level|LogLevel::* $level - */ - public function log($level, string|\Stringable $message, array $context = []): void - { - if (!$level instanceof Level) { - if (!is_string($level) && !is_int($level)) { - throw new \InvalidArgumentException('$level is expected to be a string, int or '.Level::class.' instance'); - } - - if (isset(self::RFC_5424_LEVELS[$level])) { - $level = self::RFC_5424_LEVELS[$level]; - } - - $level = static::toMonologLevel($level); - } - - $this->addRecord($level, (string) $message, $context); - } - - /** - * Adds a log record at the DEBUG level. - * - * This method allows for compatibility with common interfaces. - * - * @param string|Stringable $message The log message - * @param mixed[] $context The log context - */ - public function debug(string|\Stringable $message, array $context = []): void - { - $this->addRecord(Level::Debug, (string) $message, $context); - } - - /** - * Adds a log record at the INFO level. - * - * This method allows for compatibility with common interfaces. - * - * @param string|Stringable $message The log message - * @param mixed[] $context The log context - */ - public function info(string|\Stringable $message, array $context = []): void - { - $this->addRecord(Level::Info, (string) $message, $context); - } - - /** - * Adds a log record at the NOTICE level. - * - * This method allows for compatibility with common interfaces. - * - * @param string|Stringable $message The log message - * @param mixed[] $context The log context - */ - public function notice(string|\Stringable $message, array $context = []): void - { - $this->addRecord(Level::Notice, (string) $message, $context); - } - - /** - * Adds a log record at the WARNING level. - * - * This method allows for compatibility with common interfaces. - * - * @param string|Stringable $message The log message - * @param mixed[] $context The log context - */ - public function warning(string|\Stringable $message, array $context = []): void - { - $this->addRecord(Level::Warning, (string) $message, $context); - } - - /** - * Adds a log record at the ERROR level. - * - * This method allows for compatibility with common interfaces. - * - * @param string|Stringable $message The log message - * @param mixed[] $context The log context - */ - public function error(string|\Stringable $message, array $context = []): void - { - $this->addRecord(Level::Error, (string) $message, $context); - } - - /** - * Adds a log record at the CRITICAL level. - * - * This method allows for compatibility with common interfaces. - * - * @param string|Stringable $message The log message - * @param mixed[] $context The log context - */ - public function critical(string|\Stringable $message, array $context = []): void - { - $this->addRecord(Level::Critical, (string) $message, $context); - } - - /** - * Adds a log record at the ALERT level. - * - * This method allows for compatibility with common interfaces. - * - * @param string|Stringable $message The log message - * @param mixed[] $context The log context - */ - public function alert(string|\Stringable $message, array $context = []): void - { - $this->addRecord(Level::Alert, (string) $message, $context); - } - - /** - * Adds a log record at the EMERGENCY level. - * - * This method allows for compatibility with common interfaces. - * - * @param string|Stringable $message The log message - * @param mixed[] $context The log context - */ - public function emergency(string|\Stringable $message, array $context = []): void - { - $this->addRecord(Level::Emergency, (string) $message, $context); - } - - /** - * Sets the timezone to be used for the timestamp of log records. - */ - public function setTimezone(DateTimeZone $tz): self - { - $this->timezone = $tz; - - return $this; - } - - /** - * Returns the timezone to be used for the timestamp of log records. - */ - public function getTimezone(): DateTimeZone - { - return $this->timezone; - } - - /** - * Delegates exception management to the custom exception handler, - * or throws the exception if no custom handler is set. - */ - protected function handleException(Throwable $e, LogRecord $record): void - { - if (null === $this->exceptionHandler) { - throw $e; - } - - ($this->exceptionHandler)($e, $record); - } - - /** - * @return array - */ - public function __serialize(): array - { - return [ - 'name' => $this->name, - 'handlers' => $this->handlers, - 'processors' => $this->processors, - 'microsecondTimestamps' => $this->microsecondTimestamps, - 'timezone' => $this->timezone, - 'exceptionHandler' => $this->exceptionHandler, - 'logDepth' => $this->logDepth, - 'detectCycles' => $this->detectCycles, - ]; - } - - /** - * @param array $data - */ - public function __unserialize(array $data): void - { - foreach (['name', 'handlers', 'processors', 'microsecondTimestamps', 'timezone', 'exceptionHandler', 'logDepth', 'detectCycles'] as $property) { - if (isset($data[$property])) { - $this->$property = $data[$property]; - } - } - - $this->fiberLogDepth = new \WeakMap(); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/ClosureContextProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/ClosureContextProcessor.php deleted file mode 100644 index 514b354781ad..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/ClosureContextProcessor.php +++ /dev/null @@ -1,51 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Processor; - -use Monolog\LogRecord; - -/** - * Generates a context from a Closure if the Closure is the only value - * in the context - * - * It helps reduce the performance impact of debug logs if they do - * need to create lots of context information. If this processor is added - * on the correct handler the context data will only be generated - * when the logs are actually logged to that handler, which is useful when - * using FingersCrossedHandler or other filtering handlers to conditionally - * log records. - */ -class ClosureContextProcessor implements ProcessorInterface -{ - public function __invoke(LogRecord $record): LogRecord - { - $context = $record->context; - if (isset($context[0]) && 1 === \count($context) && $context[0] instanceof \Closure) { - try { - $context = $context[0](); - } catch (\Throwable $e) { - $context = [ - 'error_on_context_generation' => $e->getMessage(), - 'exception' => $e, - ]; - } - - if (!\is_array($context)) { - $context = [$context]; - } - - $record = $record->with(context: $context); - } - - return $record; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/GitProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/GitProcessor.php deleted file mode 100644 index 5a70ac2e2e9a..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/GitProcessor.php +++ /dev/null @@ -1,75 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Processor; - -use Monolog\Level; -use Monolog\Logger; -use Psr\Log\LogLevel; -use Monolog\LogRecord; - -/** - * Injects Git branch and Git commit SHA in all records - * - * @author Nick Otter - * @author Jordi Boggiano - */ -class GitProcessor implements ProcessorInterface -{ - private Level $level; - /** @var array{branch: string, commit: string}|array|null */ - private static $cache = null; - - /** - * @param int|string|Level|LogLevel::* $level The minimum logging level at which this Processor will be triggered - * - * @phpstan-param value-of|value-of|Level|LogLevel::* $level - */ - public function __construct(int|string|Level $level = Level::Debug) - { - $this->level = Logger::toMonologLevel($level); - } - - /** - * @inheritDoc - */ - public function __invoke(LogRecord $record): LogRecord - { - // return if the level is not high enough - if ($record->level->isLowerThan($this->level)) { - return $record; - } - - $record->extra['git'] = self::getGitInfo(); - - return $record; - } - - /** - * @return array{branch: string, commit: string}|array - */ - private static function getGitInfo(): array - { - if (self::$cache !== null) { - return self::$cache; - } - - $branches = shell_exec('git branch -v --no-abbrev'); - if (is_string($branches) && 1 === preg_match('{^\* (.+?)\s+([a-f0-9]{40})(?:\s|$)}m', $branches, $matches)) { - return self::$cache = [ - 'branch' => $matches[1], - 'commit' => $matches[2], - ]; - } - - return self::$cache = []; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php deleted file mode 100644 index cba6e0963637..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/HostnameProcessor.php +++ /dev/null @@ -1,37 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Processor; - -use Monolog\LogRecord; - -/** - * Injects value of gethostname in all records - */ -class HostnameProcessor implements ProcessorInterface -{ - private static string $host; - - public function __construct() - { - self::$host = (string) gethostname(); - } - - /** - * @inheritDoc - */ - public function __invoke(LogRecord $record): LogRecord - { - $record->extra['hostname'] = self::$host; - - return $record; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php deleted file mode 100644 index 3a6fbfbef8c3..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php +++ /dev/null @@ -1,122 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Processor; - -use Monolog\Level; -use Monolog\Logger; -use Psr\Log\LogLevel; -use Monolog\LogRecord; - -/** - * Injects line/file:class/function where the log message came from - * - * Warning: This only works if the handler processes the logs directly. - * If you put the processor on a handler that is behind a FingersCrossedHandler - * for example, the processor will only be called once the trigger level is reached, - * and all the log records will have the same file/line/.. data from the call that - * triggered the FingersCrossedHandler. - * - * @author Jordi Boggiano - */ -class IntrospectionProcessor implements ProcessorInterface -{ - private Level $level; - - /** @var string[] */ - private array $skipClassesPartials; - - private int $skipStackFramesCount; - - private const SKIP_FUNCTIONS = [ - 'call_user_func', - 'call_user_func_array', - ]; - - /** - * @param string|int|Level $level The minimum logging level at which this Processor will be triggered - * @param string[] $skipClassesPartials - * - * @phpstan-param value-of|value-of|Level|LogLevel::* $level - */ - public function __construct(int|string|Level $level = Level::Debug, array $skipClassesPartials = [], int $skipStackFramesCount = 0) - { - $this->level = Logger::toMonologLevel($level); - $this->skipClassesPartials = array_merge(['Monolog\\'], $skipClassesPartials); - $this->skipStackFramesCount = $skipStackFramesCount; - } - - /** - * @inheritDoc - */ - public function __invoke(LogRecord $record): LogRecord - { - // return if the level is not high enough - if ($record->level->isLowerThan($this->level)) { - return $record; - } - - $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); - - // skip first since it's always the current method - array_shift($trace); - // the call_user_func call is also skipped - array_shift($trace); - - $i = 0; - - while ($this->isTraceClassOrSkippedFunction($trace, $i)) { - if (isset($trace[$i]['class'])) { - foreach ($this->skipClassesPartials as $part) { - if (strpos($trace[$i]['class'], $part) !== false) { - $i++; - - continue 2; - } - } - } elseif (in_array($trace[$i]['function'], self::SKIP_FUNCTIONS, true)) { - $i++; - - continue; - } - - break; - } - - $i += $this->skipStackFramesCount; - - // we should have the call source now - $record->extra = array_merge( - $record->extra, - [ - 'file' => $trace[$i - 1]['file'] ?? null, - 'line' => $trace[$i - 1]['line'] ?? null, - 'class' => $trace[$i]['class'] ?? null, - 'callType' => $trace[$i]['type'] ?? null, - 'function' => $trace[$i]['function'] ?? null, - ] - ); - - return $record; - } - - /** - * @param array $trace - */ - private function isTraceClassOrSkippedFunction(array $trace, int $index): bool - { - if (!isset($trace[$index])) { - return false; - } - - return isset($trace[$index]['class']) || in_array($trace[$index]['function'], self::SKIP_FUNCTIONS, true); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/LoadAverageProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/LoadAverageProcessor.php deleted file mode 100644 index 64e3c4747d26..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/LoadAverageProcessor.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Processor; - -use Monolog\LogRecord; - -/** - * Injects sys_getloadavg in all records @see https://www.php.net/manual/en/function.sys-getloadavg.php - * - * @author Johan Vlaar - */ -class LoadAverageProcessor implements ProcessorInterface -{ - public const LOAD_1_MINUTE = 0; - public const LOAD_5_MINUTE = 1; - public const LOAD_15_MINUTE = 2; - - private const AVAILABLE_LOAD = [ - self::LOAD_1_MINUTE, - self::LOAD_5_MINUTE, - self::LOAD_15_MINUTE, - ]; - - /** - * @var int - */ - protected $avgSystemLoad; - - /** - * @param self::LOAD_* $avgSystemLoad - */ - public function __construct(int $avgSystemLoad = self::LOAD_1_MINUTE) - { - if (!in_array($avgSystemLoad, self::AVAILABLE_LOAD, true)) { - throw new \InvalidArgumentException(sprintf('Invalid average system load: `%s`', $avgSystemLoad)); - } - $this->avgSystemLoad = $avgSystemLoad; - } - - /** - * {@inheritDoc} - */ - public function __invoke(LogRecord $record): LogRecord - { - if (!function_exists('sys_getloadavg')) { - return $record; - } - $usage = sys_getloadavg(); - if (false === $usage) { - return $record; - } - - $record->extra['load_average'] = $usage[$this->avgSystemLoad]; - - return $record; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php deleted file mode 100644 index adc32c65d4ea..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php +++ /dev/null @@ -1,39 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Processor; - -use Monolog\LogRecord; - -/** - * Injects memory_get_peak_usage in all records - * - * @see Monolog\Processor\MemoryProcessor::__construct() for options - * @author Rob Jensen - */ -class MemoryPeakUsageProcessor extends MemoryProcessor -{ - /** - * @inheritDoc - */ - public function __invoke(LogRecord $record): LogRecord - { - $usage = memory_get_peak_usage($this->realUsage); - - if ($this->useFormatting) { - $usage = $this->formatBytes($usage); - } - - $record->extra['memory_peak_usage'] = $usage; - - return $record; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php deleted file mode 100644 index f808e51b4a49..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php +++ /dev/null @@ -1,60 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Processor; - -/** - * Some methods that are common for all memory processors - * - * @author Rob Jensen - */ -abstract class MemoryProcessor implements ProcessorInterface -{ - /** - * @var bool If true, get the real size of memory allocated from system. Else, only the memory used by emalloc() is reported. - */ - protected bool $realUsage; - - /** - * @var bool If true, then format memory size to human readable string (MB, KB, B depending on size) - */ - protected bool $useFormatting; - - /** - * @param bool $realUsage Set this to true to get the real size of memory allocated from system. - * @param bool $useFormatting If true, then format memory size to human readable string (MB, KB, B depending on size) - */ - public function __construct(bool $realUsage = true, bool $useFormatting = true) - { - $this->realUsage = $realUsage; - $this->useFormatting = $useFormatting; - } - - /** - * Formats bytes into a human readable string if $this->useFormatting is true, otherwise return $bytes as is - * - * @return string|int Formatted string if $this->useFormatting is true, otherwise return $bytes as int - */ - protected function formatBytes(int $bytes) - { - if (!$this->useFormatting) { - return $bytes; - } - - if ($bytes > 1024 * 1024) { - return round($bytes / 1024 / 1024, 2).' MB'; - } elseif ($bytes > 1024) { - return round($bytes / 1024, 2).' KB'; - } - - return $bytes . ' B'; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php deleted file mode 100644 index a814b1df34b8..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php +++ /dev/null @@ -1,39 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Processor; - -use Monolog\LogRecord; - -/** - * Injects memory_get_usage in all records - * - * @see Monolog\Processor\MemoryProcessor::__construct() for options - * @author Rob Jensen - */ -class MemoryUsageProcessor extends MemoryProcessor -{ - /** - * @inheritDoc - */ - public function __invoke(LogRecord $record): LogRecord - { - $usage = memory_get_usage($this->realUsage); - - if ($this->useFormatting) { - $usage = $this->formatBytes($usage); - } - - $record->extra['memory_usage'] = $usage; - - return $record; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php deleted file mode 100644 index 47b1e64ff90e..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/MercurialProcessor.php +++ /dev/null @@ -1,75 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Processor; - -use Monolog\Level; -use Monolog\Logger; -use Psr\Log\LogLevel; -use Monolog\LogRecord; - -/** - * Injects Hg branch and Hg revision number in all records - * - * @author Jonathan A. Schweder - */ -class MercurialProcessor implements ProcessorInterface -{ - private Level $level; - /** @var array{branch: string, revision: string}|array|null */ - private static $cache = null; - - /** - * @param int|string|Level $level The minimum logging level at which this Processor will be triggered - * - * @phpstan-param value-of|value-of|Level|LogLevel::* $level - */ - public function __construct(int|string|Level $level = Level::Debug) - { - $this->level = Logger::toMonologLevel($level); - } - - /** - * @inheritDoc - */ - public function __invoke(LogRecord $record): LogRecord - { - // return if the level is not high enough - if ($record->level->isLowerThan($this->level)) { - return $record; - } - - $record->extra['hg'] = self::getMercurialInfo(); - - return $record; - } - - /** - * @return array{branch: string, revision: string}|array - */ - private static function getMercurialInfo(): array - { - if (self::$cache !== null) { - return self::$cache; - } - - $result = explode(' ', trim((string) shell_exec('hg id -nb'))); - - if (count($result) >= 3) { - return self::$cache = [ - 'branch' => $result[1], - 'revision' => $result[2], - ]; - } - - return self::$cache = []; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php deleted file mode 100644 index bb9a5224343d..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php +++ /dev/null @@ -1,32 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Processor; - -use Monolog\LogRecord; - -/** - * Adds value of getmypid into records - * - * @author Andreas Hörnicke - */ -class ProcessIdProcessor implements ProcessorInterface -{ - /** - * @inheritDoc - */ - public function __invoke(LogRecord $record): LogRecord - { - $record->extra['process_id'] = getmypid(); - - return $record; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php deleted file mode 100644 index ebe41fc20604..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/ProcessorInterface.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Processor; - -use Monolog\LogRecord; - -/** - * An optional interface to allow labelling Monolog processors. - * - * @author Nicolas Grekas - */ -interface ProcessorInterface -{ - /** - * @return LogRecord The processed record - */ - public function __invoke(LogRecord $record); -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php deleted file mode 100644 index aad2aad2f32e..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php +++ /dev/null @@ -1,87 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Processor; - -use Monolog\Utils; -use Monolog\LogRecord; - -/** - * Processes a record's message according to PSR-3 rules - * - * It replaces {foo} with the value from $context['foo'] - * - * @author Jordi Boggiano - */ -class PsrLogMessageProcessor implements ProcessorInterface -{ - public const SIMPLE_DATE = "Y-m-d\TH:i:s.uP"; - - private ?string $dateFormat; - - private bool $removeUsedContextFields; - - /** - * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format - * @param bool $removeUsedContextFields If set to true the fields interpolated into message gets unset - */ - public function __construct(?string $dateFormat = null, bool $removeUsedContextFields = false) - { - $this->dateFormat = $dateFormat; - $this->removeUsedContextFields = $removeUsedContextFields; - } - - /** - * @inheritDoc - */ - public function __invoke(LogRecord $record): LogRecord - { - if (false === strpos($record->message, '{')) { - return $record; - } - - $replacements = []; - $context = $record->context; - - foreach ($context as $key => $val) { - $placeholder = '{' . $key . '}'; - if (strpos($record->message, $placeholder) === false) { - continue; - } - - if (null === $val || is_scalar($val) || (is_object($val) && method_exists($val, "__toString"))) { - $replacements[$placeholder] = $val; - } elseif ($val instanceof \DateTimeInterface) { - if (null === $this->dateFormat && $val instanceof \Monolog\DateTimeImmutable) { - // handle monolog dates using __toString if no specific dateFormat was asked for - // so that it follows the useMicroseconds flag - $replacements[$placeholder] = (string) $val; - } else { - $replacements[$placeholder] = $val->format($this->dateFormat ?? static::SIMPLE_DATE); - } - } elseif ($val instanceof \UnitEnum) { - $replacements[$placeholder] = $val instanceof \BackedEnum ? $val->value : $val->name; - } elseif (is_object($val)) { - $replacements[$placeholder] = '[object '.Utils::getClass($val).']'; - } elseif (is_array($val)) { - $replacements[$placeholder] = 'array'.Utils::jsonEncode($val, null, true); - } else { - $replacements[$placeholder] = '['.gettype($val).']'; - } - - if ($this->removeUsedContextFields) { - unset($context[$key]); - } - } - - return $record->with(message: strtr($record->message, $replacements), context: $context); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/TagProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/TagProcessor.php deleted file mode 100644 index 4543ccf6112c..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/TagProcessor.php +++ /dev/null @@ -1,63 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Processor; - -use Monolog\LogRecord; - -/** - * Adds a tags array into record - * - * @author Martijn Riemers - */ -class TagProcessor implements ProcessorInterface -{ - /** @var string[] */ - private array $tags; - - /** - * @param string[] $tags - */ - public function __construct(array $tags = []) - { - $this->setTags($tags); - } - - /** - * @param string[] $tags - */ - public function addTags(array $tags = []): self - { - $this->tags = array_merge($this->tags, $tags); - - return $this; - } - - /** - * @param string[] $tags - */ - public function setTags(array $tags = []): self - { - $this->tags = $tags; - - return $this; - } - - /** - * @inheritDoc - */ - public function __invoke(LogRecord $record): LogRecord - { - $record->extra['tags'] = $this->tags; - - return $record; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/UidProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/UidProcessor.php deleted file mode 100644 index 3a0c128c2b6a..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/UidProcessor.php +++ /dev/null @@ -1,67 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Processor; - -use Monolog\ResettableInterface; -use Monolog\LogRecord; - -/** - * Adds a unique identifier into records - * - * @author Simon Mönch - */ -class UidProcessor implements ProcessorInterface, ResettableInterface -{ - /** @var non-empty-string */ - private string $uid; - - /** - * @param int<1, 32> $length - */ - public function __construct(int $length = 7) - { - if ($length > 32 || $length < 1) { - throw new \InvalidArgumentException('The uid length must be an integer between 1 and 32'); - } - - $this->uid = $this->generateUid($length); - } - - /** - * @inheritDoc - */ - public function __invoke(LogRecord $record): LogRecord - { - $record->extra['uid'] = $this->uid; - - return $record; - } - - public function getUid(): string - { - return $this->uid; - } - - public function reset(): void - { - $this->uid = $this->generateUid(strlen($this->uid)); - } - - /** - * @param positive-int $length - * @return non-empty-string - */ - private function generateUid(int $length): string - { - return substr(bin2hex(random_bytes((int) ceil($length / 2))), 0, $length); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/WebProcessor.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/WebProcessor.php deleted file mode 100644 index 2088b180b795..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Processor/WebProcessor.php +++ /dev/null @@ -1,112 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Processor; - -use ArrayAccess; -use Monolog\LogRecord; - -/** - * Injects url/method and remote IP of the current web request in all records - * - * @author Jordi Boggiano - */ -class WebProcessor implements ProcessorInterface -{ - /** - * @var array|ArrayAccess - */ - protected array|ArrayAccess $serverData; - - /** - * Default fields - * - * Array is structured as [key in record.extra => key in $serverData] - * - * @var array - */ - protected array $extraFields = [ - 'url' => 'REQUEST_URI', - 'ip' => 'REMOTE_ADDR', - 'http_method' => 'REQUEST_METHOD', - 'server' => 'SERVER_NAME', - 'referrer' => 'HTTP_REFERER', - 'user_agent' => 'HTTP_USER_AGENT', - ]; - - /** - * @param array|ArrayAccess|null $serverData Array or object w/ ArrayAccess that provides access to the $_SERVER data - * @param array|array|null $extraFields Field names and the related key inside $serverData to be added (or just a list of field names to use the default configured $serverData mapping). If not provided it defaults to: [url, ip, http_method, server, referrer] + unique_id if present in server data - */ - public function __construct(array|ArrayAccess|null $serverData = null, array|null $extraFields = null) - { - if (null === $serverData) { - $this->serverData = &$_SERVER; - } else { - $this->serverData = $serverData; - } - - $defaultEnabled = ['url', 'ip', 'http_method', 'server', 'referrer']; - if (isset($this->serverData['UNIQUE_ID'])) { - $this->extraFields['unique_id'] = 'UNIQUE_ID'; - $defaultEnabled[] = 'unique_id'; - } - - if (null === $extraFields) { - $extraFields = $defaultEnabled; - } - if (isset($extraFields[0])) { - foreach (array_keys($this->extraFields) as $fieldName) { - if (!in_array($fieldName, $extraFields, true)) { - unset($this->extraFields[$fieldName]); - } - } - } else { - $this->extraFields = $extraFields; - } - } - - /** - * @inheritDoc - */ - public function __invoke(LogRecord $record): LogRecord - { - // skip processing if for some reason request data - // is not present (CLI or wonky SAPIs) - if (!isset($this->serverData['REQUEST_URI'])) { - return $record; - } - - $record->extra = $this->appendExtraFields($record->extra); - - return $record; - } - - public function addExtraField(string $extraName, string $serverName): self - { - $this->extraFields[$extraName] = $serverName; - - return $this; - } - - /** - * @param mixed[] $extra - * @return mixed[] - */ - private function appendExtraFields(array $extra): array - { - foreach ($this->extraFields as $extraName => $serverName) { - $extra[$extraName] = $this->serverData[$serverName] ?? null; - } - - return $extra; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Registry.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Registry.php deleted file mode 100644 index 2ef2edceb4d3..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Registry.php +++ /dev/null @@ -1,133 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog; - -use InvalidArgumentException; - -/** - * Monolog log registry - * - * Allows to get `Logger` instances in the global scope - * via static method calls on this class. - * - * - * $application = new Monolog\Logger('application'); - * $api = new Monolog\Logger('api'); - * - * Monolog\Registry::addLogger($application); - * Monolog\Registry::addLogger($api); - * - * function testLogger() - * { - * Monolog\Registry::api()->error('Sent to $api Logger instance'); - * Monolog\Registry::application()->error('Sent to $application Logger instance'); - * } - * - * - * @author Tomas Tatarko - */ -class Registry -{ - /** - * List of all loggers in the registry (by named indexes) - * - * @var Logger[] - */ - private static array $loggers = []; - - /** - * Adds new logging channel to the registry - * - * @param Logger $logger Instance of the logging channel - * @param string|null $name Name of the logging channel ($logger->getName() by default) - * @param bool $overwrite Overwrite instance in the registry if the given name already exists? - * @throws \InvalidArgumentException If $overwrite set to false and named Logger instance already exists - */ - public static function addLogger(Logger $logger, ?string $name = null, bool $overwrite = false): void - { - $name = $name ?? $logger->getName(); - - if (isset(self::$loggers[$name]) && !$overwrite) { - throw new InvalidArgumentException('Logger with the given name already exists'); - } - - self::$loggers[$name] = $logger; - } - - /** - * Checks if such logging channel exists by name or instance - * - * @param string|Logger $logger Name or logger instance - */ - public static function hasLogger($logger): bool - { - if ($logger instanceof Logger) { - $index = array_search($logger, self::$loggers, true); - - return false !== $index; - } - - return isset(self::$loggers[$logger]); - } - - /** - * Removes instance from registry by name or instance - * - * @param string|Logger $logger Name or logger instance - */ - public static function removeLogger($logger): void - { - if ($logger instanceof Logger) { - if (false !== ($idx = array_search($logger, self::$loggers, true))) { - unset(self::$loggers[$idx]); - } - } else { - unset(self::$loggers[$logger]); - } - } - - /** - * Clears the registry - */ - public static function clear(): void - { - self::$loggers = []; - } - - /** - * Gets Logger instance from the registry - * - * @param string $name Name of the requested Logger instance - * @throws \InvalidArgumentException If named Logger instance is not in the registry - */ - public static function getInstance(string $name): Logger - { - if (!isset(self::$loggers[$name])) { - throw new InvalidArgumentException(sprintf('Requested "%s" logger instance is not in the registry', $name)); - } - - return self::$loggers[$name]; - } - - /** - * Gets Logger instance from the registry via static method call - * - * @param string $name Name of the requested Logger instance - * @param mixed[] $arguments Arguments passed to static method call - * @throws \InvalidArgumentException If named Logger instance is not in the registry - * @return Logger Requested instance of Logger - */ - public static function __callStatic(string $name, array $arguments): Logger - { - return self::getInstance($name); - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/ResettableInterface.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/ResettableInterface.php deleted file mode 100644 index 4983a6b3553a..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/ResettableInterface.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog; - -/** - * Handler or Processor implementing this interface will be reset when Logger::reset() is called. - * - * Resetting ends a log cycle gets them back to their initial state. - * - * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal - * state, and getting it back to a state in which it can receive log records again. - * - * This is useful in case you want to avoid logs leaking between two requests or jobs when you - * have a long running process like a worker or an application server serving multiple requests - * in one process. - * - * @author Grégoire Pineau - */ -interface ResettableInterface -{ - public function reset(): void; -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/SignalHandler.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/SignalHandler.php deleted file mode 100644 index b930ca4395a6..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/SignalHandler.php +++ /dev/null @@ -1,113 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog; - -use Psr\Log\LoggerInterface; -use Psr\Log\LogLevel; -use ReflectionExtension; - -/** - * Monolog POSIX signal handler - * - * @author Robert Gust-Bardon - */ -class SignalHandler -{ - private LoggerInterface $logger; - - /** @var array SIG_DFL, SIG_IGN or previous callable */ - private array $previousSignalHandler = []; - /** @var array */ - private array $signalLevelMap = []; - /** @var array */ - private array $signalRestartSyscalls = []; - - public function __construct(LoggerInterface $logger) - { - $this->logger = $logger; - } - - /** - * @param int|string|Level $level Level or level name - * @return $this - * - * @phpstan-param value-of|value-of|Level|LogLevel::* $level - */ - public function registerSignalHandler(int $signo, int|string|Level $level = LogLevel::CRITICAL, bool $callPrevious = true, bool $restartSyscalls = true, ?bool $async = true): self - { - if (!extension_loaded('pcntl') || !function_exists('pcntl_signal')) { - return $this; - } - - $level = Logger::toMonologLevel($level)->toPsrLogLevel(); - - if ($callPrevious) { - $handler = pcntl_signal_get_handler($signo); - $this->previousSignalHandler[$signo] = $handler; - } else { - unset($this->previousSignalHandler[$signo]); - } - $this->signalLevelMap[$signo] = $level; - $this->signalRestartSyscalls[$signo] = $restartSyscalls; - - if ($async !== null) { - pcntl_async_signals($async); - } - - pcntl_signal($signo, [$this, 'handleSignal'], $restartSyscalls); - - return $this; - } - - /** - * @param mixed $siginfo - */ - public function handleSignal(int $signo, $siginfo = null): void - { - /** @var array $signals */ - static $signals = []; - - if (\count($signals) === 0 && extension_loaded('pcntl')) { - $pcntl = new ReflectionExtension('pcntl'); - foreach ($pcntl->getConstants() as $name => $value) { - if (substr($name, 0, 3) === 'SIG' && $name[3] !== '_' && is_int($value)) { - $signals[$value] = $name; - } - } - } - - $level = $this->signalLevelMap[$signo] ?? LogLevel::CRITICAL; - $signal = $signals[$signo] ?? $signo; - $context = $siginfo ?? []; - $this->logger->log($level, sprintf('Program received signal %s', $signal), $context); - - if (!isset($this->previousSignalHandler[$signo])) { - return; - } - - if ($this->previousSignalHandler[$signo] === SIG_DFL) { - if (extension_loaded('pcntl') && function_exists('pcntl_signal') && function_exists('pcntl_sigprocmask') && function_exists('pcntl_signal_dispatch') - && extension_loaded('posix') && function_exists('posix_getpid') && function_exists('posix_kill') - ) { - $restartSyscalls = $this->signalRestartSyscalls[$signo] ?? true; - pcntl_signal($signo, SIG_DFL, $restartSyscalls); - pcntl_sigprocmask(SIG_UNBLOCK, [$signo], $oldset); - posix_kill(posix_getpid(), $signo); - pcntl_signal_dispatch(); - pcntl_sigprocmask(SIG_SETMASK, $oldset); - pcntl_signal($signo, [$this, 'handleSignal'], $restartSyscalls); - } - } elseif (is_callable($this->previousSignalHandler[$signo])) { - $this->previousSignalHandler[$signo]($signo, $siginfo); - } - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Test/TestCase.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Test/TestCase.php deleted file mode 100644 index 98204a95ca5f..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Test/TestCase.php +++ /dev/null @@ -1,82 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog\Test; - -use Monolog\Level; -use Monolog\Logger; -use Monolog\LogRecord; -use Monolog\DateTimeImmutable; -use Monolog\Formatter\FormatterInterface; -use Psr\Log\LogLevel; - -/** - * Lets you easily generate log records and a dummy formatter for testing purposes - * - * @author Jordi Boggiano - * - * @internal feel free to reuse this to test your own handlers, this is marked internal to avoid issues with PHPStorm https://github.com/Seldaek/monolog/issues/1677 - */ -class TestCase extends \PHPUnit\Framework\TestCase -{ - public function tearDown(): void - { - parent::tearDown(); - - if (isset($this->handler)) { - unset($this->handler); - } - } - - /** - * @param array $context - * @param array $extra - * - * @phpstan-param value-of|value-of|Level|LogLevel::* $level - */ - protected function getRecord(int|string|Level $level = Level::Warning, string|\Stringable $message = 'test', array $context = [], string $channel = 'test', \DateTimeImmutable $datetime = new DateTimeImmutable(true), array $extra = []): LogRecord - { - return new LogRecord( - message: (string) $message, - context: $context, - level: Logger::toMonologLevel($level), - channel: $channel, - datetime: $datetime, - extra: $extra, - ); - } - - /** - * @phpstan-return list - */ - protected function getMultipleRecords(): array - { - return [ - $this->getRecord(Level::Debug, 'debug message 1'), - $this->getRecord(Level::Debug, 'debug message 2'), - $this->getRecord(Level::Info, 'information'), - $this->getRecord(Level::Warning, 'warning'), - $this->getRecord(Level::Error, 'error'), - ]; - } - - protected function getIdentityFormatter(): FormatterInterface - { - $formatter = $this->createMock(FormatterInterface::class); - $formatter->expects(self::any()) - ->method('format') - ->will(self::returnCallback(function ($record) { - return $record->message; - })); - - return $formatter; - } -} diff --git a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Utils.php b/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Utils.php deleted file mode 100644 index 7848f0ecdda1..000000000000 --- a/apps/files_external/3rdparty/monolog/monolog/src/Monolog/Utils.php +++ /dev/null @@ -1,274 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Monolog; - -final class Utils -{ - const DEFAULT_JSON_FLAGS = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION | JSON_INVALID_UTF8_SUBSTITUTE | JSON_PARTIAL_OUTPUT_ON_ERROR; - - public static function getClass(object $object): string - { - $class = \get_class($object); - - if (false === ($pos = \strpos($class, "@anonymous\0"))) { - return $class; - } - - if (false === ($parent = \get_parent_class($class))) { - return \substr($class, 0, $pos + 10); - } - - return $parent . '@anonymous'; - } - - public static function substr(string $string, int $start, ?int $length = null): string - { - if (extension_loaded('mbstring')) { - return mb_strcut($string, $start, $length); - } - - return substr($string, $start, (null === $length) ? strlen($string) : $length); - } - - /** - * Makes sure if a relative path is passed in it is turned into an absolute path - * - * @param string $streamUrl stream URL or path without protocol - */ - public static function canonicalizePath(string $streamUrl): string - { - $prefix = ''; - if ('file://' === substr($streamUrl, 0, 7)) { - $streamUrl = substr($streamUrl, 7); - $prefix = 'file://'; - } - - // other type of stream, not supported - if (false !== strpos($streamUrl, '://')) { - return $streamUrl; - } - - // already absolute - if (substr($streamUrl, 0, 1) === '/' || substr($streamUrl, 1, 1) === ':' || substr($streamUrl, 0, 2) === '\\\\') { - return $prefix.$streamUrl; - } - - $streamUrl = getcwd() . '/' . $streamUrl; - - return $prefix.$streamUrl; - } - - /** - * Return the JSON representation of a value - * - * @param mixed $data - * @param int $encodeFlags flags to pass to json encode, defaults to DEFAULT_JSON_FLAGS - * @param bool $ignoreErrors whether to ignore encoding errors or to throw on error, when ignored and the encoding fails, "null" is returned which is valid json for null - * @throws \RuntimeException if encoding fails and errors are not ignored - * @return string when errors are ignored and the encoding fails, "null" is returned which is valid json for null - */ - public static function jsonEncode($data, ?int $encodeFlags = null, bool $ignoreErrors = false): string - { - if (null === $encodeFlags) { - $encodeFlags = self::DEFAULT_JSON_FLAGS; - } - - if ($ignoreErrors) { - $json = @json_encode($data, $encodeFlags); - if (false === $json) { - return 'null'; - } - - return $json; - } - - $json = json_encode($data, $encodeFlags); - if (false === $json) { - $json = self::handleJsonError(json_last_error(), $data); - } - - return $json; - } - - /** - * Handle a json_encode failure. - * - * If the failure is due to invalid string encoding, try to clean the - * input and encode again. If the second encoding attempt fails, the - * initial error is not encoding related or the input can't be cleaned then - * raise a descriptive exception. - * - * @param int $code return code of json_last_error function - * @param mixed $data data that was meant to be encoded - * @param int $encodeFlags flags to pass to json encode, defaults to JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION - * @throws \RuntimeException if failure can't be corrected - * @return string JSON encoded data after error correction - */ - public static function handleJsonError(int $code, $data, ?int $encodeFlags = null): string - { - if ($code !== JSON_ERROR_UTF8) { - self::throwEncodeError($code, $data); - } - - if (is_string($data)) { - self::detectAndCleanUtf8($data); - } elseif (is_array($data)) { - array_walk_recursive($data, ['Monolog\Utils', 'detectAndCleanUtf8']); - } else { - self::throwEncodeError($code, $data); - } - - if (null === $encodeFlags) { - $encodeFlags = self::DEFAULT_JSON_FLAGS; - } - - $json = json_encode($data, $encodeFlags); - - if ($json === false) { - self::throwEncodeError(json_last_error(), $data); - } - - return $json; - } - - /** - * @internal - */ - public static function pcreLastErrorMessage(int $code): string - { - if (PHP_VERSION_ID >= 80000) { - return preg_last_error_msg(); - } - - $constants = (get_defined_constants(true))['pcre']; - $constants = array_filter($constants, function ($key) { - return substr($key, -6) == '_ERROR'; - }, ARRAY_FILTER_USE_KEY); - - $constants = array_flip($constants); - - return $constants[$code] ?? 'UNDEFINED_ERROR'; - } - - /** - * Throws an exception according to a given code with a customized message - * - * @param int $code return code of json_last_error function - * @param mixed $data data that was meant to be encoded - * @throws \RuntimeException - */ - private static function throwEncodeError(int $code, $data): never - { - $msg = match ($code) { - JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', - JSON_ERROR_STATE_MISMATCH => 'Underflow or the modes mismatch', - JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', - JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded', - default => 'Unknown error', - }; - - throw new \RuntimeException('JSON encoding failed: '.$msg.'. Encoding: '.var_export($data, true)); - } - - /** - * Detect invalid UTF-8 string characters and convert to valid UTF-8. - * - * Valid UTF-8 input will be left unmodified, but strings containing - * invalid UTF-8 codepoints will be reencoded as UTF-8 with an assumed - * original encoding of ISO-8859-15. This conversion may result in - * incorrect output if the actual encoding was not ISO-8859-15, but it - * will be clean UTF-8 output and will not rely on expensive and fragile - * detection algorithms. - * - * Function converts the input in place in the passed variable so that it - * can be used as a callback for array_walk_recursive. - * - * @param mixed $data Input to check and convert if needed, passed by ref - */ - private static function detectAndCleanUtf8(&$data): void - { - if (is_string($data) && preg_match('//u', $data) !== 1) { - $data = preg_replace_callback( - '/[\x80-\xFF]+/', - function (array $m): string { - return function_exists('mb_convert_encoding') ? mb_convert_encoding($m[0], 'UTF-8', 'ISO-8859-1') : utf8_encode($m[0]); - }, - $data - ); - if (!is_string($data)) { - $pcreErrorCode = preg_last_error(); - - throw new \RuntimeException('Failed to preg_replace_callback: ' . $pcreErrorCode . ' / ' . self::pcreLastErrorMessage($pcreErrorCode)); - } - $data = str_replace( - ['¤', '¦', '¨', '´', '¸', '¼', '½', '¾'], - ['€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'], - $data - ); - } - } - - /** - * Converts a string with a valid 'memory_limit' format, to bytes. - * - * @param string|false $val - * @return int|false Returns an integer representing bytes. Returns FALSE in case of error. - */ - public static function expandIniShorthandBytes($val) - { - if (!is_string($val)) { - return false; - } - - // support -1 - if ((int) $val < 0) { - return (int) $val; - } - - if (preg_match('/^\s*(?\d+)(?:\.\d+)?\s*(?[gmk]?)\s*$/i', $val, $match) !== 1) { - return false; - } - - $val = (int) $match['val']; - switch (strtolower($match['unit'] ?? '')) { - case 'g': - $val *= 1024; - // no break - case 'm': - $val *= 1024; - // no break - case 'k': - $val *= 1024; - } - - return $val; - } - - public static function getRecordMessageForException(LogRecord $record): string - { - $context = ''; - $extra = ''; - - try { - if (\count($record->context) > 0) { - $context = "\nContext: " . json_encode($record->context, JSON_THROW_ON_ERROR); - } - if (\count($record->extra) > 0) { - $extra = "\nExtra: " . json_encode($record->extra, JSON_THROW_ON_ERROR); - } - } catch (\Throwable $e) { - // noop - } - - return "\nThe exception occurred while attempting to log: " . $record->message . $context . $extra; - } -} diff --git a/apps/files_external/3rdparty/psr/cache/README.md b/apps/files_external/3rdparty/psr/cache/README.md deleted file mode 100644 index 9855a318bd4b..000000000000 --- a/apps/files_external/3rdparty/psr/cache/README.md +++ /dev/null @@ -1,12 +0,0 @@ -Caching Interface -============== - -This repository holds all interfaces related to [PSR-6 (Caching Interface)][psr-url]. - -Note that this is not a Caching implementation of its own. It is merely interfaces that describe the components of a Caching mechanism. - -The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist. - -[psr-url]: https://www.php-fig.org/psr/psr-6/ -[package-url]: https://packagist.org/packages/psr/cache -[implementation-url]: https://packagist.org/providers/psr/cache-implementation diff --git a/apps/files_external/3rdparty/psr/cache/composer.json b/apps/files_external/3rdparty/psr/cache/composer.json deleted file mode 100644 index 4b687971e4cb..000000000000 --- a/apps/files_external/3rdparty/psr/cache/composer.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "psr/cache", - "description": "Common interface for caching libraries", - "keywords": ["psr", "psr-6", "cache"], - "license": "MIT", - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "require": { - "php": ">=8.0.0" - }, - "autoload": { - "psr-4": { - "Psr\\Cache\\": "src/" - } - }, - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - } -} diff --git a/apps/files_external/3rdparty/psr/cache/src/CacheException.php b/apps/files_external/3rdparty/psr/cache/src/CacheException.php deleted file mode 100644 index bb785f46cebc..000000000000 --- a/apps/files_external/3rdparty/psr/cache/src/CacheException.php +++ /dev/null @@ -1,10 +0,0 @@ -=8.0.0" - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - } -} diff --git a/apps/files_external/3rdparty/psr/log/src/AbstractLogger.php b/apps/files_external/3rdparty/psr/log/src/AbstractLogger.php deleted file mode 100644 index d60a091affae..000000000000 --- a/apps/files_external/3rdparty/psr/log/src/AbstractLogger.php +++ /dev/null @@ -1,15 +0,0 @@ -logger = $logger; - } -} diff --git a/apps/files_external/3rdparty/psr/log/src/LoggerInterface.php b/apps/files_external/3rdparty/psr/log/src/LoggerInterface.php deleted file mode 100644 index b3a24b5f7e9d..000000000000 --- a/apps/files_external/3rdparty/psr/log/src/LoggerInterface.php +++ /dev/null @@ -1,125 +0,0 @@ -log(LogLevel::EMERGENCY, $message, $context); - } - - /** - * Action must be taken immediately. - * - * Example: Entire website down, database unavailable, etc. This should - * trigger the SMS alerts and wake you up. - * - * @param string|\Stringable $message - * @param array $context - * - * @return void - */ - public function alert(string|\Stringable $message, array $context = []): void - { - $this->log(LogLevel::ALERT, $message, $context); - } - - /** - * Critical conditions. - * - * Example: Application component unavailable, unexpected exception. - * - * @param string|\Stringable $message - * @param array $context - * - * @return void - */ - public function critical(string|\Stringable $message, array $context = []): void - { - $this->log(LogLevel::CRITICAL, $message, $context); - } - - /** - * Runtime errors that do not require immediate action but should typically - * be logged and monitored. - * - * @param string|\Stringable $message - * @param array $context - * - * @return void - */ - public function error(string|\Stringable $message, array $context = []): void - { - $this->log(LogLevel::ERROR, $message, $context); - } - - /** - * Exceptional occurrences that are not errors. - * - * Example: Use of deprecated APIs, poor use of an API, undesirable things - * that are not necessarily wrong. - * - * @param string|\Stringable $message - * @param array $context - * - * @return void - */ - public function warning(string|\Stringable $message, array $context = []): void - { - $this->log(LogLevel::WARNING, $message, $context); - } - - /** - * Normal but significant events. - * - * @param string|\Stringable $message - * @param array $context - * - * @return void - */ - public function notice(string|\Stringable $message, array $context = []): void - { - $this->log(LogLevel::NOTICE, $message, $context); - } - - /** - * Interesting events. - * - * Example: User logs in, SQL logs. - * - * @param string|\Stringable $message - * @param array $context - * - * @return void - */ - public function info(string|\Stringable $message, array $context = []): void - { - $this->log(LogLevel::INFO, $message, $context); - } - - /** - * Detailed debug information. - * - * @param string|\Stringable $message - * @param array $context - * - * @return void - */ - public function debug(string|\Stringable $message, array $context = []): void - { - $this->log(LogLevel::DEBUG, $message, $context); - } - - /** - * Logs with an arbitrary level. - * - * @param mixed $level - * @param string|\Stringable $message - * @param array $context - * - * @return void - * - * @throws \Psr\Log\InvalidArgumentException - */ - abstract public function log($level, string|\Stringable $message, array $context = []): void; -} diff --git a/apps/files_external/3rdparty/psr/log/src/NullLogger.php b/apps/files_external/3rdparty/psr/log/src/NullLogger.php deleted file mode 100644 index c1cc3c0692a2..000000000000 --- a/apps/files_external/3rdparty/psr/log/src/NullLogger.php +++ /dev/null @@ -1,30 +0,0 @@ -logger) { }` - * blocks. - */ -class NullLogger extends AbstractLogger -{ - /** - * Logs with an arbitrary level. - * - * @param mixed $level - * @param string|\Stringable $message - * @param array $context - * - * @return void - * - * @throws \Psr\Log\InvalidArgumentException - */ - public function log($level, string|\Stringable $message, array $context = []): void - { - // noop - } -} diff --git a/lib/private/legacy/app.php b/lib/private/legacy/app.php index d855d5ba4a8e..ce29c6159cad 100644 --- a/lib/private/legacy/app.php +++ b/lib/private/legacy/app.php @@ -128,7 +128,7 @@ public static function loadApps($types = null) { \ob_end_clean(); // once all authentication apps are loaded we can validate the session - if ($types === null || \in_array('authentication', $types)) { + if ($types === null || \in_array('authentication', \is_array($types) ? $types : [$types], true)) { if (\OC::$server->getUserSession()) { $request = \OC::$server->getRequest(); $session = \OC::$server->getUserSession(); diff --git a/lib/private/legacy/image.php b/lib/private/legacy/image.php index 43ab84f9a17e..d08416585972 100644 --- a/lib/private/legacy/image.php +++ b/lib/private/legacy/image.php @@ -748,7 +748,7 @@ public function loadFromData($str) { * @param string $str A string base64 encoded string of image data. * @return bool An image resource or false on error */ - private function loadFromBase64($str) { + public function loadFromBase64(string $str): bool { if (!\is_string($str)) { return false; } From ab6a6502bcca92af42bb77d9d860d730177c27e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Tue, 17 Feb 2026 15:32:19 +0100 Subject: [PATCH 31/42] feat: php 8.3 --- .drone.star | 2 +- .github/workflows/lint-and-codestyle.yml | 2 +- lib/base.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.drone.star b/.drone.star index 55c09773e58c..c2dd1dd97407 100644 --- a/.drone.star +++ b/.drone.star @@ -26,7 +26,7 @@ SONARSOURCE_SONAR_SCANNER_CLI = "sonarsource/sonar-scanner-cli:5" TOOLHIPPIE_CALENS = "toolhippie/calens:latest" WEBHIPPIE_REDIS = "webhippie/redis:latest" -DEFAULT_PHP_VERSION = "8.2" +DEFAULT_PHP_VERSION = "8.3" DEFAULT_NODEJS_VERSION = "14" # minio mc environment variables diff --git a/.github/workflows/lint-and-codestyle.yml b/.github/workflows/lint-and-codestyle.yml index a9639e08ffe3..b5f820141815 100644 --- a/.github/workflows/lint-and-codestyle.yml +++ b/.github/workflows/lint-and-codestyle.yml @@ -24,7 +24,7 @@ jobs: strategy: fail-fast: false matrix: - php-version: ["7.4"] + php-version: ["8.3"] steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/lib/base.php b/lib/base.php index f7ec7c0a9654..b04d77a9bfe6 100644 --- a/lib/base.php +++ b/lib/base.php @@ -11,8 +11,8 @@ exit(1); } -if (PHP_VERSION_ID >= 80300) { - echo 'This version of ownCloud is not compatible with PHP 8.3' . $eol; +if (PHP_VERSION_ID >= 80600) { + echo 'This version of ownCloud is not compatible with PHP 8.6' . $eol; echo 'You are currently running PHP ' . PHP_VERSION . '.' . $eol; exit(1); } From 0eee9befadded63912738fd69e353bef4ab6c814 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Tue, 17 Feb 2026 15:55:26 +0100 Subject: [PATCH 32/42] fix: Query::jsonSerialize() return type --- lib/private/Diagnostics/Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/private/Diagnostics/Query.php b/lib/private/Diagnostics/Query.php index 7c6d2ce6a1ed..7b7b57d8f454 100644 --- a/lib/private/Diagnostics/Query.php +++ b/lib/private/Diagnostics/Query.php @@ -76,7 +76,7 @@ public function getDuration() { return $this->end - $this->start; } - public function jsonSerialize() { + public function jsonSerialize(): mixed { return [ 'query' => $this->sql, 'parameters' => $this->params, From f1a36bce100e77eaec64b856f88bda81016ac687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Tue, 17 Feb 2026 16:06:20 +0100 Subject: [PATCH 33/42] Revert "Merge pull request #40747 from owncloud/ci-ubuntu22" This reverts commit 605037a35b66a01f4bb5fb3733c0226d9ed5e6e6, reversing changes made to e9cb5fdec5487b9283e010c255cb68596b3c1799. --- .drone.star | 46 ++++++++++++---------------------------------- 1 file changed, 12 insertions(+), 34 deletions(-) diff --git a/.drone.star b/.drone.star index c2dd1dd97407..3a824b0bcb5f 100644 --- a/.drone.star +++ b/.drone.star @@ -85,18 +85,6 @@ config = { "oracle", ], }, - "ubuntu22": { - "phpVersions": [ - "7.4-ubuntu22.04", - ], - # These pipelines are run just to help avoid any obscure regression - # on Ubuntu 22.04. We do not need coverage for this. - "coverage": False, - "databases": [ - "mariadb:10.6", - "mariadb:10.11", - ], - }, "external-samba": { "phpVersions": [ DEFAULT_PHP_VERSION, @@ -251,10 +239,6 @@ config = { "testingRemoteSystem": False, }, "cliEncryption": { - "phpVersions": [ - DEFAULT_PHP_VERSION, - "7.4-ubuntu22.04", - ], "suites": [ "cliEncryption", ], @@ -420,18 +404,6 @@ config = { "runAllSuites": True, "numberOfParts": 8, }, - "apiUbuntu22": { - "phpVersions": [ - "7.4-ubuntu22.04", - ], - "suites": { - "apiUbuntu22": "apiUbuntu22", - }, - "useHttps": False, - "filterTags": "@smokeTest&&~@notifications-app-required&&~@local_storage&&~@files_external-app-required", - "runAllSuites": True, - "numberOfParts": 8, - }, "apiOnSqlite": { "suites": { "apiOnSqlite": "apiOnSqlite", @@ -1153,15 +1125,18 @@ def phpTests(ctx, testType, withCoverage): else: command = "unknown tbd" - # Shorten PHP docker tags that have longer names like 7.4-ubuntu22.04 - phpVersionString = phpVersion.replace("-ubuntu", "-u") + # Get the first 3 characters of the PHP version (7.4 or 8.0 etc) + # And use that for constructing the pipeline name + # That helps shorten pipeline names when using owncloud-ci images + # that have longer names like 7.4-ubuntu20.04 + phpMinorVersion = phpVersion[0:3] for db in params["databases"]: for externalType in params["externalTypes"]: keyString = "-" + category if params["includeKeyInMatrixName"] else "" filesExternalType = externalType if externalType != "none" else "" externalNameString = "-" + externalType if externalType != "none" else "" - name = "%s%s-php%s-%s%s" % (testType, keyString, phpVersionString, getShortDbNameAndVersion(db), externalNameString) + name = "%s%s-php%s-%s%s" % (testType, keyString, phpMinorVersion, getShortDbNameAndVersion(db), externalNameString) maxLength = 50 nameLength = len(name) if nameLength > maxLength: @@ -1426,8 +1401,11 @@ def acceptance(ctx): for federatedServerVersion in params["federatedServerVersions"]: for browser in params["browsers"]: for phpVersion in params["phpVersions"]: - # Shorten PHP docker tags that have longer names like 7.4-ubuntu22.04 - phpVersionString = phpVersion.replace("-ubuntu", "-u") + # Get the first 3 characters of the PHP version (7.4 or 8.0 etc) + # And use that for constructing the pipeline name + # That helps shorten pipeline names when using owncloud-ci images + # that have longer names like 7.4-ubuntu20.04 + phpMinorVersion = phpVersion[0:3] for db in params["databases"]: for runPart in range(1, params["numberOfParts"] + 1): debugPartsEnabled = (len(params["skipExceptParts"]) != 0) @@ -1449,7 +1427,7 @@ def acceptance(ctx): keyString = "-" + category if params["includeKeyInMatrixName"] else "" partString = "" if params["numberOfParts"] == 1 else "-%d-%d" % (params["numberOfParts"], runPart) federatedServerVersionString = "-" + federatedServerVersion.replace("daily-", "").replace("-qa", "") if (federatedServerVersion != "") else "" - name = "%s%s%s%s%s-%s-php%s" % (alternateSuiteName, keyString, partString, federatedServerVersionString, browserString, getShortDbNameAndVersion(db), phpVersionString) + name = "%s%s%s%s%s-%s-php%s" % (alternateSuiteName, keyString, partString, federatedServerVersionString, browserString, getShortDbNameAndVersion(db), phpMinorVersion) maxLength = 50 nameLength = len(name) if nameLength > maxLength: From 274e0b2d7fc4ede2a68e1cc55b4674bbc306fc61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Tue, 17 Feb 2026 16:11:44 +0100 Subject: [PATCH 34/42] fix: php 8.3 is min version --- lib/base.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/base.php b/lib/base.php index b04d77a9bfe6..0705862347aa 100644 --- a/lib/base.php +++ b/lib/base.php @@ -5,8 +5,8 @@ if (\defined('OC_CONSOLE')) { $eol = PHP_EOL; } -if (PHP_VERSION_ID < 80200) { - echo 'This version of ownCloud requires at least PHP 8.2.0'.$eol; +if (PHP_VERSION_ID < 80300) { + echo 'This version of ownCloud requires at least PHP 8.3.0'.$eol; echo 'You are currently running PHP ' . PHP_VERSION . '. Please update your PHP version.'.$eol; exit(1); } From c4e98fb6a8727e3c742d2bd644a8dc73bcb25470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Tue, 17 Feb 2026 16:12:15 +0100 Subject: [PATCH 35/42] fix: QueryLogger::getMicrotime() returns int --- lib/private/Diagnostics/QueryLogger.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/private/Diagnostics/QueryLogger.php b/lib/private/Diagnostics/QueryLogger.php index 1d2254222e6d..decb3edc6023 100644 --- a/lib/private/Diagnostics/QueryLogger.php +++ b/lib/private/Diagnostics/QueryLogger.php @@ -82,10 +82,10 @@ public function flush(): void { $this->queries = []; } - private function getMicrotime(): float { + private function getMicrotime(): int { if ($this->testNow) { - return $this->testNow; + return (int)$this->testNow; } - return microtime(true); + return (int)microtime(true); } } From f3b0ca44f11457ebbf0365404e0b6224850706fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Thu, 26 Feb 2026 12:43:16 +0100 Subject: [PATCH 36/42] fix: MySQLPlatform casing and MapperTest --- lib/private/DB/MDB2SchemaManager.php | 2 +- lib/private/DB/Migrator.php | 2 +- tests/lib/AppFramework/Db/MapperTestUtility.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/private/DB/MDB2SchemaManager.php b/lib/private/DB/MDB2SchemaManager.php index 27c7d3d2a6f7..3f02101d1700 100644 --- a/lib/private/DB/MDB2SchemaManager.php +++ b/lib/private/DB/MDB2SchemaManager.php @@ -136,7 +136,7 @@ public function removeDBStructure($file) { * @return bool */ private function executeSchemaChange($schema) { - if (!$this->conn->getDatabasePlatform() instanceof MySqlPlatform) { + if (!$this->conn->getDatabasePlatform() instanceof MySQLPlatform) { $this->conn->beginTransaction(); } foreach ($schema->toSql($this->conn->getDatabasePlatform()) as $sql) { diff --git a/lib/private/DB/Migrator.php b/lib/private/DB/Migrator.php index 001b89237a7f..fbf0ecaf4da9 100644 --- a/lib/private/DB/Migrator.php +++ b/lib/private/DB/Migrator.php @@ -174,7 +174,7 @@ protected function applySchema(Schema $targetSchema, \Doctrine\DBAL\Connection $ $schemaDiff = $this->getDiff($targetSchema, $connection); - if (!$connection->getDatabasePlatform() instanceof MySqlPlatform) { + if (!$connection->getDatabasePlatform() instanceof MySQLPlatform) { $connection->beginTransaction(); } $sqls = $schemaDiff->toSql($connection->getDatabasePlatform()); diff --git a/tests/lib/AppFramework/Db/MapperTestUtility.php b/tests/lib/AppFramework/Db/MapperTestUtility.php index cdf316df6735..849d5cfb818e 100644 --- a/tests/lib/AppFramework/Db/MapperTestUtility.php +++ b/tests/lib/AppFramework/Db/MapperTestUtility.php @@ -59,7 +59,7 @@ protected function setUp(): void { $this->iterators = []; $this->fetchAt = 0; - $this->query->method('execute')->willReturn(true); + $this->query->method('execute')->willReturn($this->result); } /** From 42270e3617aad6b251d818fabd57f47c8a6216c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Thu, 26 Feb 2026 12:51:48 +0100 Subject: [PATCH 37/42] fix: composer update + remove roave/security-advisories for the time being - firebase/php-jwt requires major update --- composer.json | 3 +- composer.lock | 1585 +++++++++---------------------------------------- 2 files changed, 286 insertions(+), 1302 deletions(-) diff --git a/composer.json b/composer.json index 34cc19a45ed3..1c5591c85cb5 100644 --- a/composer.json +++ b/composer.json @@ -41,8 +41,7 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8", "mikey179/vfsstream": "^1.6", - "phpunit/phpunit": "^9.5", - "roave/security-advisories": "dev-latest" + "phpunit/phpunit": "^9.5" }, "require": { "php": ">=8.2", diff --git a/composer.lock b/composer.lock index 8eae6e0c5f2b..77f333d692da 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1d84a0f8b8dd3428e4a98a523b191464", + "content-hash": "d5a7c6738f14546ca1f30741b6cdaf3e", "packages": [ { "name": "bantu/ini-get-wrapper", @@ -407,29 +407,29 @@ }, { "name": "doctrine/deprecations", - "version": "1.1.5", + "version": "1.1.6", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", - "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "conflict": { - "phpunit/phpunit": "<=7.5 || >=13" + "phpunit/phpunit": "<=7.5 || >=14" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^12 || ^13", - "phpstan/phpstan": "1.4.10 || 2.1.11", + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", "phpstan/phpstan-phpunit": "^1.0 || ^2", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", "psr/log": "^1 || ^2 || ^3" }, "suggest": { @@ -449,36 +449,35 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.5" + "source": "https://github.com/doctrine/deprecations/tree/1.1.6" }, - "time": "2025-04-07T20:06:18+00:00" + "time": "2026-02-07T07:09:04+00:00" }, { "name": "doctrine/event-manager", - "version": "1.2.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/doctrine/event-manager.git", - "reference": "95aa4cb529f1e96576f3fda9f5705ada4056a520" + "reference": "dda33921b198841ca8dbad2eaa5d4d34769d18cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/95aa4cb529f1e96576f3fda9f5705ada4056a520", - "reference": "95aa4cb529f1e96576f3fda9f5705ada4056a520", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/dda33921b198841ca8dbad2eaa5d4d34769d18cf", + "reference": "dda33921b198841ca8dbad2eaa5d4d34769d18cf", "shasum": "" }, "require": { - "doctrine/deprecations": "^0.5.3 || ^1", - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "conflict": { "doctrine/common": "<2.9" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^10", - "phpstan/phpstan": "~1.4.10 || ^1.8.8", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.24" + "doctrine/coding-standard": "^14", + "phpdocumentor/guides-cli": "^1.4", + "phpstan/phpstan": "^2.1.32", + "phpunit/phpunit": "^10.5.58" }, "type": "library", "autoload": { @@ -527,7 +526,7 @@ ], "support": { "issues": "https://github.com/doctrine/event-manager/issues", - "source": "https://github.com/doctrine/event-manager/tree/1.2.0" + "source": "https://github.com/doctrine/event-manager/tree/2.1.1" }, "funding": [ { @@ -543,7 +542,7 @@ "type": "tidelift" } ], - "time": "2022-10-12T20:51:15+00:00" + "time": "2026-01-29T07:11:08+00:00" }, { "name": "doctrine/lexer", @@ -691,26 +690,26 @@ }, { "name": "firebase/php-jwt", - "version": "v6.10.0", + "version": "v6.11.1", "source": { "type": "git", "url": "https://github.com/firebase/php-jwt.git", - "reference": "a49db6f0a5033aef5143295342f1c95521b075ff" + "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/a49db6f0a5033aef5143295342f1c95521b075ff", - "reference": "a49db6f0a5033aef5143295342f1c95521b075ff", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", + "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", "shasum": "" }, "require": { - "php": "^7.4||^8.0" + "php": "^8.0" }, "require-dev": { - "guzzlehttp/guzzle": "^6.5||^7.4", + "guzzlehttp/guzzle": "^7.4", "phpspec/prophecy-phpunit": "^2.0", "phpunit/phpunit": "^9.5", - "psr/cache": "^1.0||^2.0", + "psr/cache": "^2.0||^3.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0" }, @@ -748,40 +747,40 @@ ], "support": { "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v6.10.0" + "source": "https://github.com/firebase/php-jwt/tree/v6.11.1" }, - "time": "2023-12-01T16:26:39+00:00" + "time": "2025-04-09T20:32:01+00:00" }, { "name": "google/apiclient", - "version": "v2.16.1", + "version": "v2.19.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-api-php-client.git", - "reference": "d7066f3a205aaeb83cce439613770e69394a43c8" + "reference": "b18fa8aed7b2b2dd4bcce74e2c7d267e16007ea9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-api-php-client/zipball/d7066f3a205aaeb83cce439613770e69394a43c8", - "reference": "d7066f3a205aaeb83cce439613770e69394a43c8", + "url": "https://api.github.com/repos/googleapis/google-api-php-client/zipball/b18fa8aed7b2b2dd4bcce74e2c7d267e16007ea9", + "reference": "b18fa8aed7b2b2dd4bcce74e2c7d267e16007ea9", "shasum": "" }, "require": { - "firebase/php-jwt": "~6.0", + "firebase/php-jwt": "^6.0||^7.0", "google/apiclient-services": "~0.350", "google/auth": "^1.37", - "guzzlehttp/guzzle": "^6.5.8||^7.4.5", - "guzzlehttp/psr7": "^1.9.1||^2.2.1", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.6", "monolog/monolog": "^2.9||^3.0", - "php": "^7.4|^8.0", + "php": "^8.1", "phpseclib/phpseclib": "^3.0.36" }, "require-dev": { "cache/filesystem-adapter": "^1.1", "composer/composer": "^1.10.23", "phpcompatibility/php-compatibility": "^9.2", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.5", + "phpspec/prophecy-phpunit": "^2.1", + "phpunit/phpunit": "^9.6", "squizlabs/php_codesniffer": "^3.8", "symfony/css-selector": "~2.1", "symfony/dom-crawler": "~2.1" @@ -817,29 +816,29 @@ ], "support": { "issues": "https://github.com/googleapis/google-api-php-client/issues", - "source": "https://github.com/googleapis/google-api-php-client/tree/v2.16.1" + "source": "https://github.com/googleapis/google-api-php-client/tree/v2.19.0" }, - "time": "2024-12-12T17:34:46+00:00" + "time": "2026-01-09T19:59:47+00:00" }, { "name": "google/apiclient-services", - "version": "v0.355.0", + "version": "v0.434.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-api-php-client-services.git", - "reference": "235e6a45ecafd77accc102b5ab6d529aab54da23" + "reference": "65550d5fd5c468badd75db9ec73c2c187470a00d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/235e6a45ecafd77accc102b5ab6d529aab54da23", - "reference": "235e6a45ecafd77accc102b5ab6d529aab54da23", + "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/65550d5fd5c468badd75db9ec73c2c187470a00d", + "reference": "65550d5fd5c468badd75db9ec73c2c187470a00d", "shasum": "" }, "require": { - "php": "^7.4||^8.0" + "php": "^8.1" }, "require-dev": { - "phpunit/phpunit": "^5.7||^8.5.13" + "phpunit/phpunit": "^9.6" }, "type": "library", "autoload": { @@ -861,40 +860,42 @@ ], "support": { "issues": "https://github.com/googleapis/google-api-php-client-services/issues", - "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.355.0" + "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.434.0" }, - "time": "2024-05-11T01:02:11+00:00" + "time": "2026-02-18T01:00:35+00:00" }, { "name": "google/auth", - "version": "v1.37.2", + "version": "v1.44.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-auth-library-php.git", - "reference": "25eed0045d8cf107424a8b9010c9fdcc0734ceb0" + "reference": "5670e56307d7a2eac931f677c0e59a4f8abb2e43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/25eed0045d8cf107424a8b9010c9fdcc0734ceb0", - "reference": "25eed0045d8cf107424a8b9010c9fdcc0734ceb0", + "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/5670e56307d7a2eac931f677c0e59a4f8abb2e43", + "reference": "5670e56307d7a2eac931f677c0e59a4f8abb2e43", "shasum": "" }, "require": { "firebase/php-jwt": "^6.0", - "guzzlehttp/guzzle": "^6.5.8||^7.4.5", + "guzzlehttp/guzzle": "^7.4.5", "guzzlehttp/psr7": "^2.4.5", - "php": "^7.4||^8.0", - "psr/cache": "^1.0||^2.0||^3.0", + "php": "^8.1", + "psr/cache": "^2.0||^3.0", "psr/http-message": "^1.1||^2.0" }, "require-dev": { "guzzlehttp/promises": "^2.0", "kelvinmo/simplejwt": "0.7.1", - "phpseclib/phpseclib": "^3.0", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.0.0", + "phpseclib/phpseclib": "^3.0.35", + "phpspec/prophecy-phpunit": "^2.1", + "phpunit/phpunit": "^9.6", "sebastian/comparator": ">=1.2.3", - "squizlabs/php_codesniffer": "^3.5" + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^6.0||^7.0", + "webmozart/assert": "^1.11" }, "suggest": { "phpseclib/phpseclib": "May be used in place of OpenSSL for signing strings or for token management. Please require version ^2." @@ -919,9 +920,9 @@ "support": { "docs": "https://googleapis.github.io/google-auth-library-php/main/", "issues": "https://github.com/googleapis/google-auth-library-php/issues", - "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.37.2" + "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.44.0" }, - "time": "2024-12-11T18:15:11+00:00" + "time": "2024-12-04T15:34:58+00:00" }, { "name": "guzzlehttp/guzzle", @@ -1250,21 +1251,21 @@ }, { "name": "icewind/smb", - "version": "v3.7.0", + "version": "3.8.1", "source": { "type": "git", "url": "https://codeberg.org/icewind/SMB", - "reference": "e6904cbe75f678335092f4861c60c656b1a99e84" + "reference": "97063a63b44edc6554966f6121679506b8d85103" }, "require": { "icewind/streams": ">=0.7.3", - "php": ">=7.2" + "php": ">=8.2" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.16", + "friendsofphp/php-cs-fixer": "v3.89.0", "phpstan/phpstan": "^0.12.57", - "phpunit/phpunit": "^8.5|^9.3.8", - "psalm/phar": "^4.3" + "phpunit/phpunit": "10.5.58", + "psalm/phar": "6.*" }, "type": "library", "autoload": { @@ -1279,11 +1280,11 @@ "authors": [ { "name": "Robin Appelman", - "email": "icewind@owncloud.com" + "email": "robin@icewind.nl" } ], "description": "php wrapper for smbclient and libsmbclient-php", - "time": "2024-11-11T14:08:34+00:00" + "time": "2025-11-13T16:17:19+00:00" }, { "name": "icewind/streams", @@ -1379,38 +1380,38 @@ }, { "name": "laminas/laminas-filter", - "version": "2.22.0", + "version": "2.42.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-filter.git", - "reference": "c48e8a392a81de7d211026c078dce0e8bc57e2e3" + "reference": "985d27bd42daf51b415ce1ee889e0978cc1e59ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-filter/zipball/c48e8a392a81de7d211026c078dce0e8bc57e2e3", - "reference": "c48e8a392a81de7d211026c078dce0e8bc57e2e3", + "url": "https://api.github.com/repos/laminas/laminas-filter/zipball/985d27bd42daf51b415ce1ee889e0978cc1e59ed", + "reference": "985d27bd42daf51b415ce1ee889e0978cc1e59ed", "shasum": "" }, "require": { "ext-mbstring": "*", - "laminas/laminas-servicemanager": "^3.14.0", - "laminas/laminas-stdlib": "^3.13.0", - "php": "^7.4 || ~8.0.0 || ~8.1.0" + "laminas/laminas-servicemanager": "^3.21.0", + "laminas/laminas-stdlib": "^3.19.0", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" }, "conflict": { "laminas/laminas-validator": "<2.10.1", "zendframework/zend-filter": "*" }, "require-dev": { - "laminas/laminas-coding-standard": "~2.4.0", - "laminas/laminas-crypt": "^3.5.1", - "laminas/laminas-uri": "^2.9.1", - "pear/archive_tar": "^1.4.14", - "phpspec/prophecy-phpunit": "^2.0.1", - "phpunit/phpunit": "^9.5.24", - "psalm/plugin-phpunit": "^0.17.0", - "psr/http-factory": "^1.0.1", - "vimeo/psalm": "^4.27.0" + "laminas/laminas-coding-standard": "^3.1", + "laminas/laminas-crypt": "^3.12", + "laminas/laminas-i18n": "^2.30.0", + "laminas/laminas-uri": "^2.13", + "pear/archive_tar": "^1.6.0", + "phpunit/phpunit": "^10.5.58", + "psalm/plugin-phpunit": "^0.19.0", + "psr/http-factory": "^1.1.0", + "vimeo/psalm": "^5.26.1" }, "suggest": { "laminas/laminas-crypt": "Laminas\\Crypt component, for encryption filters", @@ -1454,41 +1455,40 @@ "type": "community_bridge" } ], - "time": "2022-10-11T08:14:46+00:00" + "time": "2025-10-13T15:44:52+00:00" }, { "name": "laminas/laminas-inputfilter", - "version": "2.21.0", + "version": "2.35.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-inputfilter.git", - "reference": "8668227246d19564f339643f0f2aedcdff66612b" + "reference": "326d2dac38814f70902a3a9e0062f740d06f89c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-inputfilter/zipball/8668227246d19564f339643f0f2aedcdff66612b", - "reference": "8668227246d19564f339643f0f2aedcdff66612b", + "url": "https://api.github.com/repos/laminas/laminas-inputfilter/zipball/326d2dac38814f70902a3a9e0062f740d06f89c5", + "reference": "326d2dac38814f70902a3a9e0062f740d06f89c5", "shasum": "" }, "require": { - "laminas/laminas-filter": "^2.13", - "laminas/laminas-servicemanager": "^3.16.0", - "laminas/laminas-stdlib": "^3.0", - "laminas/laminas-validator": "^2.15", - "php": "^7.4 || ~8.0.0 || ~8.1.0" + "laminas/laminas-filter": "^2.19", + "laminas/laminas-servicemanager": "^3.21.0", + "laminas/laminas-stdlib": "^3.19", + "laminas/laminas-validator": "^2.60.0", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "psr/container": "^1.1 || ^2.0" }, "conflict": { "zendframework/zend-inputfilter": "*" }, "require-dev": { "ext-json": "*", - "laminas/laminas-coding-standard": "~2.4.0", - "laminas/laminas-db": "^2.15.0", - "phpunit/phpunit": "^9.5.24", - "psalm/plugin-phpunit": "^0.17.0", - "psr/http-message": "^1.0", - "vimeo/psalm": "^4.27.0", - "webmozart/assert": "^1.11" + "laminas/laminas-coding-standard": "^3.1.0", + "phpunit/phpunit": "^11.5.46", + "psalm/plugin-phpunit": "^0.19.5", + "psr/http-message": "^2.0", + "vimeo/psalm": "^6.14.3" }, "suggest": { "psr/http-message-implementation": "PSR-7 is required if you wish to validate PSR-7 UploadedFileInterface payloads" @@ -1529,30 +1529,30 @@ "type": "community_bridge" } ], - "time": "2022-09-20T10:03:09+00:00" + "time": "2026-01-10T15:07:43+00:00" }, { "name": "laminas/laminas-servicemanager", - "version": "3.17.0", + "version": "3.24.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-servicemanager.git", - "reference": "360be5f16955dd1edbcce1cfaa98ed82a17f02ec" + "reference": "b172a0df568bf37ebdfb3658263156eefe3c1e8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-servicemanager/zipball/360be5f16955dd1edbcce1cfaa98ed82a17f02ec", - "reference": "360be5f16955dd1edbcce1cfaa98ed82a17f02ec", + "url": "https://api.github.com/repos/laminas/laminas-servicemanager/zipball/b172a0df568bf37ebdfb3658263156eefe3c1e8c", + "reference": "b172a0df568bf37ebdfb3658263156eefe3c1e8c", "shasum": "" }, "require": { - "laminas/laminas-stdlib": "^3.2.1", - "php": "~7.4.0 || ~8.0.0 || ~8.1.0", + "laminas/laminas-stdlib": "^3.19", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", "psr/container": "^1.0" }, "conflict": { "ext-psr": "*", - "laminas/laminas-code": "<3.3.1", + "laminas/laminas-code": "<4.10.0", "zendframework/zend-code": "<3.3.1", "zendframework/zend-servicemanager": "*" }, @@ -1563,20 +1563,19 @@ "container-interop/container-interop": "^1.2.0" }, "require-dev": { - "composer/package-versions-deprecated": "^1.0", - "laminas/laminas-coding-standard": "~2.4.0", - "laminas/laminas-container-config-test": "^0.7", - "laminas/laminas-dependency-plugin": "^2.1.2", - "mikey179/vfsstream": "^1.6.10@alpha", - "ocramius/proxy-manager": "^2.11", - "phpbench/phpbench": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.5.5", - "psalm/plugin-phpunit": "^0.17.0", - "vimeo/psalm": "^4.8" + "composer/package-versions-deprecated": "^1.11.99.5", + "friendsofphp/proxy-manager-lts": "^1.0.18", + "laminas/laminas-code": "^4.16.0", + "laminas/laminas-coding-standard": "~2.5.0", + "laminas/laminas-container-config-test": "^0.8", + "mikey179/vfsstream": "^1.6.12", + "phpbench/phpbench": "^1.4.1", + "phpunit/phpunit": "^10.5.58", + "psalm/plugin-phpunit": "^0.18.4", + "vimeo/psalm": "^5.26.1" }, "suggest": { - "ocramius/proxy-manager": "ProxyManager ^2.1.1 to handle lazy initialization of services" + "friendsofphp/proxy-manager-lts": "ProxyManager ^2.1.1 to handle lazy initialization of services" }, "bin": [ "bin/generate-deps-for-config-factory", @@ -1620,35 +1619,34 @@ "type": "community_bridge" } ], - "time": "2022-09-22T11:33:46+00:00" + "time": "2025-10-14T09:03:51+00:00" }, { "name": "laminas/laminas-stdlib", - "version": "3.13.0", + "version": "3.21.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-stdlib.git", - "reference": "66a6d03c381f6c9f1dd988bf8244f9afb9380d76" + "reference": "b1c81514cfe158aadf724c42b34d3d0a8164c096" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/66a6d03c381f6c9f1dd988bf8244f9afb9380d76", - "reference": "66a6d03c381f6c9f1dd988bf8244f9afb9380d76", + "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/b1c81514cfe158aadf724c42b34d3d0a8164c096", + "reference": "b1c81514cfe158aadf724c42b34d3d0a8164c096", "shasum": "" }, "require": { - "php": "^7.4 || ~8.0.0 || ~8.1.0" + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" }, "conflict": { "zendframework/zend-stdlib": "*" }, "require-dev": { - "laminas/laminas-coding-standard": "~2.3.0", - "phpbench/phpbench": "^1.2.6", - "phpstan/phpdoc-parser": "^0.5.4", - "phpunit/phpunit": "^9.5.23", - "psalm/plugin-phpunit": "^0.17.0", - "vimeo/psalm": "^4.26" + "laminas/laminas-coding-standard": "^3.1.0", + "phpbench/phpbench": "^1.4.1", + "phpunit/phpunit": "^11.5.42", + "psalm/plugin-phpunit": "^0.19.5", + "vimeo/psalm": "^6.13.1" }, "type": "library", "autoload": { @@ -1680,45 +1678,43 @@ "type": "community_bridge" } ], - "time": "2022-08-24T13:56:50+00:00" + "time": "2025-10-11T18:13:12+00:00" }, { "name": "laminas/laminas-validator", - "version": "2.25.0", + "version": "2.65.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-validator.git", - "reference": "42de39b78e73b321db7d948cf8530a2764f8b9aa" + "reference": "f0767ca83e0dd91a6f8ccdd4f0887eb132c0ea49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/42de39b78e73b321db7d948cf8530a2764f8b9aa", - "reference": "42de39b78e73b321db7d948cf8530a2764f8b9aa", + "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/f0767ca83e0dd91a6f8ccdd4f0887eb132c0ea49", + "reference": "f0767ca83e0dd91a6f8ccdd4f0887eb132c0ea49", "shasum": "" }, "require": { - "laminas/laminas-servicemanager": "^3.12.0", - "laminas/laminas-stdlib": "^3.13", - "php": "^7.4 || ~8.0.0 || ~8.1.0" + "laminas/laminas-servicemanager": "^3.21.0", + "laminas/laminas-stdlib": "^3.19", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "psr/http-message": "^1.0.1 || ^2.0.0" }, "conflict": { "zendframework/zend-validator": "*" }, "require-dev": { - "laminas/laminas-coding-standard": "^2.4.0", - "laminas/laminas-db": "^2.15.0", - "laminas/laminas-filter": "^2.18.0", - "laminas/laminas-http": "^2.16.0", - "laminas/laminas-i18n": "^2.17.0", - "laminas/laminas-session": "^2.13.0", - "laminas/laminas-uri": "^2.9.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.5.24", - "psalm/plugin-phpunit": "^0.17.0", - "psr/http-client": "^1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", - "vimeo/psalm": "^4.27.0" + "laminas/laminas-coding-standard": "^2.5", + "laminas/laminas-db": "^2.20", + "laminas/laminas-filter": "^2.41.0", + "laminas/laminas-i18n": "^2.30.0", + "laminas/laminas-session": "^2.25.1", + "laminas/laminas-uri": "^2.13.0", + "phpunit/phpunit": "^10.5.58", + "psalm/plugin-phpunit": "^0.19.0", + "psr/http-client": "^1.0.3", + "psr/http-factory": "^1.1.0", + "vimeo/psalm": "^5.26.1" }, "suggest": { "laminas/laminas-db": "Laminas\\Db component, required by the (No)RecordExists validator", @@ -1766,7 +1762,7 @@ "type": "community_bridge" } ], - "time": "2022-09-20T11:33:19+00:00" + "time": "2025-10-13T14:40:30+00:00" }, { "name": "laravel/serializable-closure", @@ -1981,42 +1977,43 @@ }, { "name": "monolog/monolog", - "version": "2.11.0", + "version": "3.10.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "37308608e599f34a1a4845b16440047ec98a172a" + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/37308608e599f34a1a4845b16440047ec98a172a", - "reference": "37308608e599f34a1a4845b16440047ec98a172a", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0", "shasum": "" }, "require": { - "php": ">=7.2", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" }, "provide": { - "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" + "psr/log-implementation": "3.0.0" }, "require-dev": { - "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "aws/aws-sdk-php": "^3.0", "doctrine/couchdb": "~1.0@dev", "elasticsearch/elasticsearch": "^7 || ^8", "ext-json": "*", - "graylog2/gelf-php": "^1.4.2 || ^2@dev", - "guzzlehttp/guzzle": "^7.4", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", "guzzlehttp/psr7": "^2.2", "mongodb/mongodb": "^1.8 || ^2.0", "php-amqplib/php-amqplib": "~2.4 || ^3", - "phpspec/prophecy": "^1.15", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^8.5.38 || ^9.6.19", - "predis/predis": "^1.1 || ^2.0", - "rollbar/rollbar": "^1.3 || ^2 || ^3", - "ruflin/elastica": "^7", - "swiftmailer/swiftmailer": "^5.3|^6.0", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", + "predis/predis": "^1.1 || ^2", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", "symfony/mailer": "^5.4 || ^6", "symfony/mime": "^5.4 || ^6" }, @@ -2039,7 +2036,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.x-dev" + "dev-main": "3.x-dev" } }, "autoload": { @@ -2067,7 +2064,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/2.11.0" + "source": "https://github.com/Seldaek/monolog/tree/3.10.0" }, "funding": [ { @@ -2079,7 +2076,7 @@ "type": "tidelift" } ], - "time": "2026-01-01T13:05:00+00:00" + "time": "2026-01-02T08:56:05+00:00" }, { "name": "nikic/php-parser", @@ -2193,24 +2190,26 @@ }, { "name": "paragonie/constant_time_encoding", - "version": "v2.8.2", + "version": "v3.1.3", "source": { "type": "git", "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "e30811f7bc69e4b5b6d5783e712c06c8eabf0226" + "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/e30811f7bc69e4b5b6d5783e712c06c8eabf0226", - "reference": "e30811f7bc69e4b5b6d5783e712c06c8eabf0226", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", + "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", "shasum": "" }, "require": { - "php": "^7|^8" + "php": "^8" }, "require-dev": { - "phpunit/phpunit": "^6|^7|^8|^9", - "vimeo/psalm": "^1|^2|^3|^4" + "infection/infection": "^0", + "nikic/php-fuzzer": "^0", + "phpunit/phpunit": "^9|^10|^11", + "vimeo/psalm": "^4|^5|^6" }, "type": "library", "autoload": { @@ -2256,7 +2255,7 @@ "issues": "https://github.com/paragonie/constant_time_encoding/issues", "source": "https://github.com/paragonie/constant_time_encoding" }, - "time": "2025-09-24T15:12:37+00:00" + "time": "2025-09-24T15:06:41+00:00" }, { "name": "paragonie/random_compat", @@ -2427,7 +2426,6 @@ "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Console_Getopt", "source": "https://github.com/pear/Console_Getopt" }, - "abandoned": true, "time": "2019-11-20T18:27:48+00:00" }, { @@ -2650,16 +2648,16 @@ }, { "name": "pimple/pimple", - "version": "v3.6.1", + "version": "v3.6.2", "source": { "type": "git", "url": "https://github.com/silexphp/Pimple.git", - "reference": "020029849cc55ad360066844df8ed880fb20c5bb" + "reference": "8cfe7f74ac22a433d303914eba9ea4c2a834edce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/silexphp/Pimple/zipball/020029849cc55ad360066844df8ed880fb20c5bb", - "reference": "020029849cc55ad360066844df8ed880fb20c5bb", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/8cfe7f74ac22a433d303914eba9ea4c2a834edce", + "reference": "8cfe7f74ac22a433d303914eba9ea4c2a834edce", "shasum": "" }, "require": { @@ -2672,7 +2670,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.4.x-dev" + "dev-master": "3.x-dev" } }, "autoload": { @@ -2697,26 +2695,26 @@ "dependency injection" ], "support": { - "source": "https://github.com/silexphp/Pimple/tree/v3.6.1" + "source": "https://github.com/silexphp/Pimple/tree/v3.6.2" }, - "time": "2025-12-31T08:24:29+00:00" + "time": "2026-02-26T08:23:44+00:00" }, { "name": "psr/cache", - "version": "1.0.1", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/cache.git", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { @@ -2736,7 +2734,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for caching libraries", @@ -2746,9 +2744,9 @@ "psr-6" ], "support": { - "source": "https://github.com/php-fig/cache/tree/master" + "source": "https://github.com/php-fig/cache/tree/3.0.0" }, - "time": "2016-08-06T20:24:11+00:00" + "time": "2021-02-03T23:26:27+00:00" }, { "name": "psr/container", @@ -3010,30 +3008,30 @@ }, { "name": "psr/log", - "version": "1.1.4", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "url": "https://api.github.com/repos/php-fig/log/zipball/ef29f6d262798707a9edd554e2b82517ef3a9376", + "reference": "ef29f6d262798707a9edd554e2b82517ef3a9376", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "Psr\\Log\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -3054,9 +3052,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" + "source": "https://github.com/php-fig/log/tree/2.0.0" }, - "time": "2021-05-03T11:20:27+00:00" + "time": "2021-07-14T16:41:46+00:00" }, { "name": "punic/punic", @@ -3781,20 +3779,20 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v2.5.4", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918" + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/605389f2a7e5625f273b53960dc46aeaf9c62918", - "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.1" }, "type": "library", "extra": { @@ -3803,7 +3801,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -3828,7 +3826,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.4" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -3844,7 +3842,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:11:13+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/event-dispatcher", @@ -3933,25 +3931,22 @@ }, { "name": "symfony/event-dispatcher-contracts", - "version": "v2.5.4", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f" + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f", - "reference": "e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1", "psr/event-dispatcher": "^1" }, - "suggest": { - "symfony/event-dispatcher-implementation": "" - }, "type": "library", "extra": { "thanks": { @@ -3959,7 +3954,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -3992,7 +3987,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.4" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" }, "funding": [ { @@ -4008,7 +4003,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:11:13+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/mailer", @@ -4088,16 +4083,16 @@ }, { "name": "symfony/mime", - "version": "v6.4.32", + "version": "v6.4.34", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "7409686879ca36c09fc970a5fa8ff6e93504dba4" + "reference": "2b32fbbe10b36a8379efab6e702ad8b917151839" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/7409686879ca36c09fc970a5fa8ff6e93504dba4", - "reference": "7409686879ca36c09fc970a5fa8ff6e93504dba4", + "url": "https://api.github.com/repos/symfony/mime/zipball/2b32fbbe10b36a8379efab6e702ad8b917151839", + "reference": "2b32fbbe10b36a8379efab6e702ad8b917151839", "shasum": "" }, "require": { @@ -4153,7 +4148,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.4.32" + "source": "https://github.com/symfony/mime/tree/v6.4.34" }, "funding": [ { @@ -4173,7 +4168,7 @@ "type": "tidelift" } ], - "time": "2026-01-04T11:53:14+00:00" + "time": "2026-02-02T17:01:23+00:00" }, { "name": "symfony/polyfill-php80", @@ -4417,29 +4412,26 @@ }, { "name": "symfony/service-contracts", - "version": "v2.5.4", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "f37b419f7aea2e9abf10abd261832cace12e3300" + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f37b419f7aea2e9abf10abd261832cace12e3300", - "reference": "f37b419f7aea2e9abf10abd261832cace12e3300", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/container": "^1.1", - "symfony/deprecation-contracts": "^2.1|^3" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { "ext-psr": "<1.1|>=2" }, - "suggest": { - "symfony/service-implementation": "" - }, "type": "library", "extra": { "thanks": { @@ -4447,13 +4439,16 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { "psr-4": { "Symfony\\Contracts\\Service\\": "" - } + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4480,7 +4475,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.4" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, "funding": [ { @@ -4491,43 +4486,46 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:11:13+00:00" + "time": "2025-07-15T11:30:57+00:00" }, { "name": "symfony/string", - "version": "v5.4.47", + "version": "v6.4.34", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "136ca7d72f72b599f2631aca474a4f8e26719799" + "reference": "2adaf4106f2ef4c67271971bde6d3fe0a6936432" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/136ca7d72f72b599f2631aca474a4f8e26719799", - "reference": "136ca7d72f72b599f2631aca474a4f8e26719799", + "url": "https://api.github.com/repos/symfony/string/zipball/2adaf4106f2ef4c67271971bde6d3fe0a6936432", + "reference": "2adaf4106f2ef4c67271971bde6d3fe0a6936432", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "~1.15" + "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/translation-contracts": ">=3.0" + "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/translation-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0|^6.0" + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/intl": "^6.2|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -4566,7 +4564,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.47" + "source": "https://github.com/symfony/string/tree/v6.4.34" }, "funding": [ { @@ -4577,12 +4575,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-11-10T20:33:58+00:00" + "time": "2026-02-08T20:44:54+00:00" }, { "name": "symfony/translation", @@ -4820,30 +4822,30 @@ }, { "name": "doctrine/instantiator", - "version": "1.5.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^11", + "doctrine/coding-standard": "^11", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.30 || ^5.4" + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" }, "type": "library", "autoload": { @@ -4870,7 +4872,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.5.0" + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" }, "funding": [ { @@ -4886,7 +4888,7 @@ "type": "tidelift" } ], - "time": "2022-12-30T00:15:36+00:00" + "time": "2022-12-30T00:23:10+00:00" }, { "name": "mikey179/vfsstream", @@ -5548,1022 +5550,6 @@ ], "time": "2026-01-27T05:45:00+00:00" }, - { - "name": "roave/security-advisories", - "version": "dev-latest", - "source": { - "type": "git", - "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "8457f2008fc6396be788162c4e04228028306534" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/8457f2008fc6396be788162c4e04228028306534", - "reference": "8457f2008fc6396be788162c4e04228028306534", - "shasum": "" - }, - "conflict": { - "3f/pygmentize": "<1.2", - "adaptcms/adaptcms": "<=1.3", - "admidio/admidio": "<=4.3.16", - "adodb/adodb-php": "<=5.22.9", - "aheinze/cockpit": "<2.2", - "aimeos/ai-admin-graphql": ">=2022.04.1,<2022.10.10|>=2023.04.1,<2023.10.6|>=2024.04.1,<2024.07.2", - "aimeos/ai-admin-jsonadm": "<2020.10.13|>=2021.04.1,<2021.10.6|>=2022.04.1,<2022.10.3|>=2023.04.1,<2023.10.4|==2024.04.1", - "aimeos/ai-client-html": ">=2020.04.1,<2020.10.27|>=2021.04.1,<2021.10.22|>=2022.04.1,<2022.10.13|>=2023.04.1,<2023.10.15|>=2024.04.1,<2024.04.7", - "aimeos/ai-cms-grapesjs": ">=2021.04.1,<2021.10.8|>=2022.04.1,<2022.10.9|>=2023.04.1,<2023.10.15|>=2024.04.1,<2024.10.8|>=2025.04.1,<2025.10.2", - "aimeos/ai-controller-frontend": "<2020.10.15|>=2021.04.1,<2021.10.8|>=2022.04.1,<2022.10.8|>=2023.04.1,<2023.10.9|==2024.04.1", - "aimeos/aimeos-core": ">=2022.04.1,<2022.10.17|>=2023.04.1,<2023.10.17|>=2024.04.1,<2024.04.7", - "aimeos/aimeos-laravel": "==2021.10", - "aimeos/aimeos-typo3": "<19.10.12|>=20,<20.10.5", - "airesvsg/acf-to-rest-api": "<=3.1", - "akaunting/akaunting": "<2.1.13", - "akeneo/pim-community-dev": "<5.0.119|>=6,<6.0.53", - "alextselegidis/easyappointments": "<=1.5.2", - "alexusmai/laravel-file-manager": "<=3.3.1", - "algolia/algoliasearch-magento-2": "<=3.16.1|>=3.17.0.0-beta1,<=3.17.1", - "alt-design/alt-redirect": "<1.6.4", - "altcha-org/altcha": "<1.3.1", - "alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1", - "amazing/media2click": ">=1,<1.3.3", - "ameos/ameos_tarteaucitron": "<1.2.23", - "amphp/artax": "<1.0.6|>=2,<2.0.6", - "amphp/http": "<=1.7.2|>=2,<=2.1", - "amphp/http-client": ">=4,<4.4", - "anchorcms/anchor-cms": "<=0.12.7", - "andreapollastri/cipi": "<=3.1.15", - "andrewhaine/silverstripe-form-capture": ">=0.2,<=0.2.3|>=1,<1.0.2|>=2,<2.2.5", - "aoe/restler": "<1.7.1", - "apache-solr-for-typo3/solr": "<2.8.3", - "apereo/phpcas": "<1.6", - "api-platform/core": "<3.4.17|>=4,<4.0.22|>=4.1,<4.1.5", - "api-platform/graphql": "<3.4.17|>=4,<4.0.22|>=4.1,<4.1.5", - "appwrite/server-ce": "<=1.2.1", - "arc/web": "<3", - "area17/twill": "<1.2.5|>=2,<2.5.3", - "artesaos/seotools": "<0.17.2", - "asymmetricrypt/asymmetricrypt": "<9.9.99", - "athlon1600/php-proxy": "<=5.1", - "athlon1600/php-proxy-app": "<=3", - "athlon1600/youtube-downloader": "<=4", - "austintoddj/canvas": "<=3.4.2", - "auth0/auth0-php": ">=3.3,<8.18", - "auth0/login": "<7.20", - "auth0/symfony": "<=5.5", - "auth0/wordpress": "<=5.4", - "automad/automad": "<2.0.0.0-alpha5", - "automattic/jetpack": "<9.8", - "awesome-support/awesome-support": "<=6.0.7", - "aws/aws-sdk-php": "<3.368", - "azuracast/azuracast": "<=0.23.1", - "b13/seo_basics": "<0.8.2", - "backdrop/backdrop": "<=1.32", - "backpack/crud": "<3.4.9", - "backpack/filemanager": "<2.0.2|>=3,<3.0.9", - "bacula-web/bacula-web": "<9.7.1", - "badaso/core": "<=2.9.11", - "bagisto/bagisto": "<2.3.10", - "barrelstrength/sprout-base-email": "<1.2.7", - "barrelstrength/sprout-forms": "<3.9", - "barryvdh/laravel-translation-manager": "<0.6.8", - "barzahlen/barzahlen-php": "<2.0.1", - "baserproject/basercms": "<=5.1.1", - "bassjobsen/bootstrap-3-typeahead": ">4.0.2", - "bbpress/bbpress": "<2.6.5", - "bcit-ci/codeigniter": "<3.1.3", - "bcosca/fatfree": "<3.7.2", - "bedita/bedita": "<4", - "bednee/cooluri": "<1.0.30", - "bigfork/silverstripe-form-capture": ">=3,<3.1.1", - "billz/raspap-webgui": "<3.3.6", - "binarytorch/larecipe": "<2.8.1", - "bk2k/bootstrap-package": ">=7.1,<7.1.2|>=8,<8.0.8|>=9,<9.0.4|>=9.1,<9.1.3|>=10,<10.0.10|>=11,<11.0.3", - "blueimp/jquery-file-upload": "==6.4.4", - "bmarshall511/wordpress_zero_spam": "<5.2.13", - "bolt/bolt": "<3.7.2", - "bolt/core": "<=4.2", - "born05/craft-twofactorauthentication": "<3.3.4", - "bottelet/flarepoint": "<2.2.1", - "bref/bref": "<2.1.17", - "brightlocal/phpwhois": "<=4.2.5", - "brotkrueml/codehighlight": "<2.7", - "brotkrueml/schema": "<1.13.1|>=2,<2.5.1", - "brotkrueml/typo3-matomo-integration": "<1.3.2", - "buddypress/buddypress": "<7.2.1", - "bugsnag/bugsnag-laravel": ">=2,<2.0.2", - "bvbmedia/multishop": "<2.0.39", - "bytefury/crater": "<6.0.2", - "cachethq/cachet": "<2.5.1", - "cadmium-org/cadmium-cms": "<=0.4.9", - "cakephp/cakephp": "<3.10.3|>=4,<4.0.10|>=4.1,<4.1.4|>=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10|>=5.2.10,<5.2.12|==5.3", - "cakephp/database": ">=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10", - "cardgate/magento2": "<2.0.33", - "cardgate/woocommerce": "<=3.1.15", - "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4", - "cart2quote/module-quotation-encoded": ">=4.1.6,<=4.4.5|>=5,<5.4.4", - "cartalyst/sentry": "<=2.1.6", - "catfan/medoo": "<1.7.5", - "causal/oidc": "<4", - "cecil/cecil": "<7.47.1", - "centreon/centreon": "<22.10.15", - "cesnet/simplesamlphp-module-proxystatistics": "<3.1", - "chriskacerguis/codeigniter-restserver": "<=2.7.1", - "chrome-php/chrome": "<1.14", - "civicrm/civicrm-core": ">=4.2,<4.2.9|>=4.3,<4.3.3", - "ckeditor/ckeditor": "<4.25", - "clickstorm/cs-seo": ">=6,<6.8|>=7,<7.5|>=8,<8.4|>=9,<9.3", - "co-stack/fal_sftp": "<0.2.6", - "cockpit-hq/cockpit": "<2.11.4", - "code16/sharp": "<9.11.1", - "codeception/codeception": "<3.1.3|>=4,<4.1.22", - "codeigniter/framework": "<3.1.10", - "codeigniter4/framework": "<4.6.2", - "codeigniter4/shield": "<1.0.0.0-beta8", - "codiad/codiad": "<=2.8.4", - "codingms/additional-tca": ">=1.7,<1.15.17|>=1.16,<1.16.9", - "codingms/modules": "<4.3.11|>=5,<5.7.4|>=6,<6.4.2|>=7,<7.5.5", - "commerceteam/commerce": ">=0.9.6,<0.9.9", - "components/jquery": ">=1.0.3,<3.5", - "composer/composer": "<1.10.27|>=2,<2.2.26|>=2.3,<2.9.3", - "concrete5/concrete5": "<9.4.3", - "concrete5/core": "<8.5.8|>=9,<9.1", - "contao-components/mediaelement": ">=2.14.2,<2.21.1", - "contao/comments-bundle": ">=2,<4.13.40|>=5.0.0.0-RC1-dev,<5.3.4", - "contao/contao": ">=3,<3.5.37|>=4,<4.4.56|>=4.5,<4.13.56|>=5,<5.3.38|>=5.4.0.0-RC1-dev,<5.6.1", - "contao/core": "<3.5.39", - "contao/core-bundle": "<4.13.57|>=5,<5.3.42|>=5.4,<5.6.5", - "contao/listing-bundle": ">=3,<=3.5.30|>=4,<4.4.8", - "contao/managed-edition": "<=1.5", - "coreshop/core-shop": "<4.1.9", - "corveda/phpsandbox": "<1.3.5", - "cosenary/instagram": "<=2.3", - "couleurcitron/tarteaucitron-wp": "<0.3", - "cpsit/typo3-mailqueue": "<0.4.3|>=0.5,<0.5.1", - "craftcms/cms": "<=4.16.16|>=5,<=5.8.20", - "croogo/croogo": "<=4.0.7", - "cuyz/valinor": "<0.12", - "czim/file-handling": "<1.5|>=2,<2.3", - "czproject/git-php": "<4.0.3", - "damienharper/auditor-bundle": "<5.2.6", - "dapphp/securimage": "<3.6.6", - "darylldoyle/safe-svg": "<1.9.10", - "datadog/dd-trace": ">=0.30,<0.30.2", - "datahihi1/tiny-env": "<1.0.3|>=1.0.9,<1.0.11", - "datatables/datatables": "<1.10.10", - "david-garcia/phpwhois": "<=4.3.1", - "dbrisinajumi/d2files": "<1", - "dcat/laravel-admin": "<=2.1.3|==2.2.0.0-beta|==2.2.2.0-beta", - "derhansen/fe_change_pwd": "<2.0.5|>=3,<3.0.3", - "derhansen/sf_event_mgt": "<4.3.1|>=5,<5.1.1|>=7,<7.4", - "desperado/xml-bundle": "<=0.1.7", - "dev-lancer/minecraft-motd-parser": "<=1.0.5", - "devcode-it/openstamanager": "<=2.9.4", - "devgroup/dotplant": "<2020.09.14-dev", - "digimix/wp-svg-upload": "<=1", - "directmailteam/direct-mail": "<6.0.3|>=7,<7.0.3|>=8,<9.5.2", - "dl/yag": "<3.0.1", - "dmk/webkitpdf": "<1.1.4", - "dnadesign/silverstripe-elemental": "<5.3.12", - "doctrine/annotations": "<1.2.7", - "doctrine/cache": ">=1,<1.3.2|>=1.4,<1.4.2", - "doctrine/common": "<2.4.3|>=2.5,<2.5.1", - "doctrine/dbal": ">=2,<2.0.8|>=2.1,<2.1.2|>=3,<3.1.4", - "doctrine/doctrine-bundle": "<1.5.2", - "doctrine/doctrine-module": "<0.7.2", - "doctrine/mongodb-odm": "<1.0.2", - "doctrine/mongodb-odm-bundle": "<3.0.1", - "doctrine/orm": ">=1,<1.2.4|>=2,<2.4.8|>=2.5,<2.5.1|>=2.8.3,<2.8.4", - "dolibarr/dolibarr": "<21.0.3", - "dompdf/dompdf": "<2.0.4", - "doublethreedigital/guest-entries": "<3.1.2", - "drupal-pattern-lab/unified-twig-extensions": "<=0.1", - "drupal/access_code": "<2.0.5", - "drupal/acquia_dam": "<1.1.5", - "drupal/admin_audit_trail": "<1.0.5", - "drupal/ai": "<1.0.5", - "drupal/alogin": "<2.0.6", - "drupal/cache_utility": "<1.2.1", - "drupal/civictheme": "<1.12", - "drupal/commerce_alphabank_redirect": "<1.0.3", - "drupal/commerce_eurobank_redirect": "<2.1.1", - "drupal/config_split": "<1.10|>=2,<2.0.2", - "drupal/core": ">=6,<6.38|>=7,<7.103|>=8,<10.4.9|>=10.5,<10.5.6|>=11,<11.1.9|>=11.2,<11.2.8", - "drupal/core-recommended": ">=7,<7.102|>=8,<10.2.11|>=10.3,<10.3.9|>=11,<11.0.8", - "drupal/currency": "<3.5", - "drupal/drupal": ">=5,<5.11|>=6,<6.38|>=7,<7.102|>=8,<10.2.11|>=10.3,<10.3.9|>=11,<11.0.8", - "drupal/email_tfa": "<2.0.6", - "drupal/formatter_suite": "<2.1", - "drupal/gdpr": "<3.0.1|>=3.1,<3.1.2", - "drupal/google_tag": "<1.8|>=2,<2.0.8", - "drupal/ignition": "<1.0.4", - "drupal/json_field": "<1.5", - "drupal/lightgallery": "<1.6", - "drupal/link_field_display_mode_formatter": "<1.6", - "drupal/matomo": "<1.24", - "drupal/oauth2_client": "<4.1.3", - "drupal/oauth2_server": "<2.1", - "drupal/obfuscate": "<2.0.1", - "drupal/plausible_tracking": "<1.0.2", - "drupal/quick_node_block": "<2", - "drupal/rapidoc_elements_field_formatter": "<1.0.1", - "drupal/reverse_proxy_header": "<1.1.2", - "drupal/simple_multistep": "<2", - "drupal/simple_oauth": ">=6,<6.0.7", - "drupal/spamspan": "<3.2.1", - "drupal/tfa": "<1.10", - "drupal/umami_analytics": "<1.0.1", - "duncanmcclean/guest-entries": "<3.1.2", - "dweeves/magmi": "<=0.7.24", - "ec-cube/ec-cube": "<2.4.4|>=2.11,<=2.17.1|>=3,<=3.0.18.0-patch4|>=4,<=4.1.2", - "ecodev/newsletter": "<=4", - "ectouch/ectouch": "<=2.7.2", - "egroupware/egroupware": "<23.1.20260113|>=26.0.20251208,<26.0.20260113", - "elefant/cms": "<2.0.7", - "elgg/elgg": "<3.3.24|>=4,<4.0.5", - "elijaa/phpmemcacheadmin": "<=1.3", - "elmsln/haxcms": "<11.0.14", - "encore/laravel-admin": "<=1.8.19", - "endroid/qr-code-bundle": "<3.4.2", - "enhavo/enhavo-app": "<=0.13.1", - "enshrined/svg-sanitize": "<0.22", - "erusev/parsedown": "<1.7.2", - "ether/logs": "<3.0.4", - "evolutioncms/evolution": "<=3.2.3", - "exceedone/exment": "<4.4.3|>=5,<5.0.3", - "exceedone/laravel-admin": "<2.2.3|==3", - "ezsystems/demobundle": ">=5.4,<5.4.6.1-dev", - "ezsystems/ez-support-tools": ">=2.2,<2.2.3", - "ezsystems/ezdemo-ls-extension": ">=5.4,<5.4.2.1-dev", - "ezsystems/ezfind-ls": ">=5.3,<5.3.6.1-dev|>=5.4,<5.4.11.1-dev|>=2017.12,<2017.12.0.1-dev", - "ezsystems/ezplatform": "<=1.13.6|>=2,<=2.5.24", - "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6|>=1.5,<1.5.29|>=2.3,<2.3.39|>=3.3,<3.3.39", - "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2.1|>=5,<5.0.1|>=5.1,<5.1.1|>=5.3.0.0-beta1,<5.3.5", - "ezsystems/ezplatform-graphql": ">=1.0.0.0-RC1-dev,<1.0.13|>=2.0.0.0-beta1,<2.3.12", - "ezsystems/ezplatform-http-cache": "<2.3.16", - "ezsystems/ezplatform-kernel": "<1.2.5.1-dev|>=1.3,<1.3.35", - "ezsystems/ezplatform-rest": ">=1.2,<=1.2.2|>=1.3,<1.3.8", - "ezsystems/ezplatform-richtext": ">=2.3,<2.3.26|>=3.3,<3.3.40", - "ezsystems/ezplatform-solr-search-engine": ">=1.7,<1.7.12|>=2,<2.0.2|>=3.3,<3.3.15", - "ezsystems/ezplatform-user": ">=1,<1.0.1", - "ezsystems/ezpublish-kernel": "<6.13.8.2-dev|>=7,<7.5.31", - "ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.6,<=2019.03.5.1", - "ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3", - "ezsystems/repository-forms": ">=2.3,<2.3.2.1-dev|>=2.5,<2.5.15", - "ezyang/htmlpurifier": "<=4.2", - "facade/ignition": "<1.16.15|>=2,<2.4.2|>=2.5,<2.5.2", - "facturascripts/facturascripts": "<=2025.4|==2025.11|==2025.41|==2025.43", - "fastly/magento2": "<1.2.26", - "feehi/cms": "<=2.1.1", - "feehi/feehicms": "<=2.1.1", - "fenom/fenom": "<=2.12.1", - "filament/actions": ">=3.2,<3.2.123", - "filament/filament": ">=4,<4.3.1", - "filament/infolists": ">=3,<3.2.115", - "filament/tables": ">=3,<3.2.115", - "filegator/filegator": "<7.8", - "filp/whoops": "<2.1.13", - "fineuploader/php-traditional-server": "<=1.2.2", - "firebase/php-jwt": "<6", - "fisharebest/webtrees": "<=2.1.18", - "fixpunkt/fp-masterquiz": "<2.2.1|>=3,<3.5.2", - "fixpunkt/fp-newsletter": "<1.1.1|>=1.2,<2.1.2|>=2.2,<3.2.6", - "flarum/core": "<1.8.10", - "flarum/flarum": "<0.1.0.0-beta8", - "flarum/framework": "<1.8.10", - "flarum/mentions": "<1.6.3", - "flarum/sticky": ">=0.1.0.0-beta14,<=0.1.0.0-beta15", - "flarum/tags": "<=0.1.0.0-beta13", - "floriangaerber/magnesium": "<0.3.1", - "fluidtypo3/vhs": "<5.1.1", - "fof/byobu": ">=0.3.0.0-beta2,<1.1.7", - "fof/pretty-mail": "<=1.1.2", - "fof/upload": "<1.2.3", - "foodcoopshop/foodcoopshop": ">=3.2,<3.6.1", - "fooman/tcpdf": "<6.2.22", - "forkcms/forkcms": "<5.11.1", - "fossar/tcpdf-parser": "<6.2.22", - "francoisjacquet/rosariosis": "<=11.5.1", - "frappant/frp-form-answers": "<3.1.2|>=4,<4.0.2", - "friendsofsymfony/oauth2-php": "<1.3", - "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2", - "friendsofsymfony/user-bundle": ">=1,<1.3.5", - "friendsofsymfony1/swiftmailer": ">=4,<5.4.13|>=6,<6.2.5", - "friendsofsymfony1/symfony1": ">=1.1,<1.5.19", - "friendsoftypo3/mediace": ">=7.6.2,<7.6.5", - "friendsoftypo3/openid": ">=4.5,<4.5.31|>=4.7,<4.7.16|>=6,<6.0.11|>=6.1,<6.1.6", - "froala/wysiwyg-editor": "<=4.3", - "froxlor/froxlor": "<=2.2.5", - "frozennode/administrator": "<=5.0.12", - "fuel/core": "<1.8.1", - "funadmin/funadmin": "<=5.0.2", - "gaoming13/wechat-php-sdk": "<=1.10.2", - "genix/cms": "<=1.1.11", - "georgringer/news": "<1.3.3", - "geshi/geshi": "<=1.0.9.1", - "getformwork/formwork": "<2.2", - "getgrav/grav": "<1.11.0.0-beta1", - "getkirby/cms": "<3.9.8.3-dev|>=3.10,<3.10.1.2-dev|>=4,<4.7.1|>=5,<=5.2.1", - "getkirby/kirby": "<3.9.8.3-dev|>=3.10,<3.10.1.2-dev|>=4,<4.7.1", - "getkirby/panel": "<2.5.14", - "getkirby/starterkit": "<=3.7.0.2", - "gilacms/gila": "<=1.15.4", - "gleez/cms": "<=1.3|==2", - "globalpayments/php-sdk": "<2", - "goalgorilla/open_social": "<12.3.11|>=12.4,<12.4.10|>=13.0.0.0-alpha1,<13.0.0.0-alpha11", - "gogentooss/samlbase": "<1.2.7", - "google/protobuf": "<3.4", - "gos/web-socket-bundle": "<1.10.4|>=2,<2.6.1|>=3,<3.3", - "gp247/core": "<1.1.24", - "gree/jose": "<2.2.1", - "gregwar/rst": "<1.0.3", - "grumpydictator/firefly-iii": "<6.1.17", - "gugoan/economizzer": "<=0.9.0.0-beta1", - "guzzlehttp/guzzle": "<6.5.8|>=7,<7.4.5", - "guzzlehttp/oauth-subscriber": "<0.8.1", - "guzzlehttp/psr7": "<1.9.1|>=2,<2.4.5", - "haffner/jh_captcha": "<=2.1.3|>=3,<=3.0.2", - "handcraftedinthealps/goodby-csv": "<1.4.3", - "harvesthq/chosen": "<1.8.7", - "helloxz/imgurl": "<=2.31", - "hhxsv5/laravel-s": "<3.7.36", - "hillelcoren/invoice-ninja": "<5.3.35", - "himiklab/yii2-jqgrid-widget": "<1.0.8", - "hjue/justwriting": "<=1", - "hov/jobfair": "<1.0.13|>=2,<2.0.2", - "httpsoft/http-message": "<1.0.12", - "hyn/multi-tenant": ">=5.6,<5.7.2", - "ibexa/admin-ui": ">=4.2,<4.2.3|>=4.6,<4.6.25|>=5,<5.0.3", - "ibexa/admin-ui-assets": ">=4.6.0.0-alpha1,<4.6.21", - "ibexa/core": ">=4,<4.0.7|>=4.1,<4.1.4|>=4.2,<4.2.3|>=4.5,<4.5.6|>=4.6,<4.6.2", - "ibexa/fieldtype-richtext": ">=4.6,<4.6.25|>=5,<5.0.3", - "ibexa/graphql": ">=2.5,<2.5.31|>=3.3,<3.3.28|>=4.2,<4.2.3", - "ibexa/http-cache": ">=4.6,<4.6.14", - "ibexa/post-install": "<1.0.16|>=4.6,<4.6.14", - "ibexa/solr": ">=4.5,<4.5.4", - "ibexa/user": ">=4,<4.4.3|>=5,<5.0.4", - "icecoder/icecoder": "<=8.1", - "idno/known": "<=1.3.1", - "ilicmiljan/secure-props": ">=1.2,<1.2.2", - "illuminate/auth": "<5.5.10", - "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<6.18.31|>=7,<7.22.4", - "illuminate/database": "<6.20.26|>=7,<7.30.5|>=8,<8.40", - "illuminate/encryption": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.40|>=5.6,<5.6.15", - "illuminate/view": "<6.20.42|>=7,<7.30.6|>=8,<8.75", - "imdbphp/imdbphp": "<=5.1.1", - "impresscms/impresscms": "<=1.4.5", - "impresspages/impresspages": "<1.0.13", - "in2code/femanager": "<6.4.2|>=7,<7.5.3|>=8,<8.3.1", - "in2code/ipandlanguageredirect": "<5.1.2", - "in2code/lux": "<17.6.1|>=18,<24.0.2", - "in2code/powermail": "<7.5.1|>=8,<8.5.1|>=9,<10.9.1|>=11,<12.5.3|==13", - "innologi/typo3-appointments": "<2.0.6", - "intelliants/subrion": "<4.2.2", - "inter-mediator/inter-mediator": "==5.5", - "ipl/web": "<0.10.1", - "islandora/crayfish": "<4.1", - "islandora/islandora": ">=2,<2.4.1", - "ivankristianto/phpwhois": "<=4.3", - "jackalope/jackalope-doctrine-dbal": "<1.7.4", - "jambagecom/div2007": "<0.10.2", - "james-heinrich/getid3": "<1.9.21", - "james-heinrich/phpthumb": "<=1.7.23", - "jasig/phpcas": "<1.3.3", - "jbartels/wec-map": "<3.0.3", - "jcbrand/converse.js": "<3.3.3", - "joelbutcher/socialstream": "<5.6|>=6,<6.2", - "johnbillion/wp-crontrol": "<1.16.2|>=1.17,<1.19.2", - "joomla/application": "<1.0.13", - "joomla/archive": "<1.1.12|>=2,<2.0.1", - "joomla/database": ">=1,<2.2|>=3,<3.4", - "joomla/filesystem": "<1.6.2|>=2,<2.0.1", - "joomla/filter": "<2.0.6|>=3,<3.0.5|==4", - "joomla/framework": "<1.5.7|>=2.5.4,<=3.8.12", - "joomla/input": ">=2,<2.0.2", - "joomla/joomla-cms": "<3.9.12|>=4,<4.4.13|>=5,<5.2.6", - "joomla/joomla-platform": "<1.5.4", - "joomla/session": "<1.3.1", - "joyqi/hyper-down": "<=2.4.27", - "jsdecena/laracom": "<2.0.9", - "jsmitty12/phpwhois": "<5.1", - "juzaweb/cms": "<=3.4.2", - "jweiland/events2": "<8.3.8|>=9,<9.0.6", - "jweiland/kk-downloader": "<1.2.2", - "kazist/phpwhois": "<=4.2.6", - "kelvinmo/simplexrd": "<3.1.1", - "kevinpapst/kimai2": "<1.16.7", - "khodakhah/nodcms": "<=3", - "kimai/kimai": "<2.46", - "kitodo/presentation": "<3.2.3|>=3.3,<3.3.4", - "klaviyo/magento2-extension": ">=1,<3", - "knplabs/knp-snappy": "<=1.4.2", - "kohana/core": "<3.3.3", - "koillection/koillection": "<1.6.12", - "krayin/laravel-crm": "<=1.3", - "kreait/firebase-php": ">=3.2,<3.8.1", - "kumbiaphp/kumbiapp": "<=1.1.1", - "la-haute-societe/tcpdf": "<6.2.22", - "laminas/laminas-diactoros": "<2.18.1|==2.19|==2.20|==2.21|==2.22|==2.23|>=2.24,<2.24.2|>=2.25,<2.25.2", - "laminas/laminas-form": "<2.17.1|>=3,<3.0.2|>=3.1,<3.1.1", - "laminas/laminas-http": "<2.14.2", - "lara-zeus/artemis": ">=1,<=1.0.6", - "lara-zeus/dynamic-dashboard": ">=3,<=3.0.1", - "laravel/fortify": "<1.11.1", - "laravel/framework": "<10.48.29|>=11,<11.44.1|>=12,<12.1.1", - "laravel/laravel": ">=5.4,<5.4.22", - "laravel/pulse": "<1.3.1", - "laravel/reverb": "<1.7", - "laravel/socialite": ">=1,<2.0.10", - "latte/latte": "<2.10.8", - "lavalite/cms": "<=10.1", - "lavitto/typo3-form-to-database": "<2.2.5|>=3,<3.2.2|>=4,<4.2.3|>=5,<5.0.2", - "lcobucci/jwt": ">=3.4,<3.4.6|>=4,<4.0.4|>=4.1,<4.1.5", - "league/commonmark": "<2.7", - "league/flysystem": "<1.1.4|>=2,<2.1.1", - "league/oauth2-server": ">=8.3.2,<8.4.2|>=8.5,<8.5.3", - "leantime/leantime": "<3.3", - "lexik/jwt-authentication-bundle": "<2.10.7|>=2.11,<2.11.3", - "libreform/libreform": ">=2,<=2.0.8", - "librenms/librenms": "<25.12", - "liftkit/database": "<2.13.2", - "lightsaml/lightsaml": "<1.3.5", - "limesurvey/limesurvey": "<6.5.12", - "livehelperchat/livehelperchat": "<=3.91", - "livewire-filemanager/filemanager": "<=1.0.4", - "livewire/livewire": "<2.12.7|>=3.0.0.0-beta1,<3.6.4", - "livewire/volt": "<1.7", - "lms/routes": "<2.1.1", - "localizationteam/l10nmgr": "<7.4|>=8,<8.7|>=9,<9.2", - "lomkit/laravel-rest-api": "<2.13", - "luracast/restler": "<3.1", - "luyadev/yii-helpers": "<1.2.1", - "macropay-solutions/laravel-crud-wizard-free": "<3.4.17", - "maestroerror/php-heic-to-jpg": "<1.0.5", - "magento/community-edition": "<2.4.6.0-patch13|>=2.4.7.0-beta1,<2.4.7.0-patch8|>=2.4.8.0-beta1,<2.4.8.0-patch3|>=2.4.9.0-alpha1,<2.4.9.0-alpha3|==2.4.9", - "magento/core": "<=1.9.4.5", - "magento/magento1ce": "<1.9.4.3-dev", - "magento/magento1ee": ">=1,<1.14.4.3-dev", - "magento/product-community-edition": "<2.4.4.0-patch9|>=2.4.5,<2.4.5.0-patch8|>=2.4.6,<2.4.6.0-patch6|>=2.4.7,<2.4.7.0-patch1", - "magento/project-community-edition": "<=2.0.2", - "magneto/core": "<1.9.4.4-dev", - "mahocommerce/maho": "<25.9", - "maikuolan/phpmussel": ">=1,<1.6", - "mainwp/mainwp": "<=4.4.3.3", - "manogi/nova-tiptap": "<=3.2.6", - "mantisbt/mantisbt": "<2.27.2", - "marcwillmann/turn": "<0.3.3", - "marshmallow/nova-tiptap": "<5.7", - "matomo/matomo": "<1.11", - "matyhtf/framework": "<3.0.6", - "mautic/core": "<5.2.9|>=6,<6.0.7", - "mautic/core-lib": ">=1.0.0.0-beta,<4.4.13|>=5.0.0.0-alpha,<5.1.1", - "mautic/grapes-js-builder-bundle": ">=4,<4.4.18|>=5,<5.2.9|>=6,<6.0.7", - "maximebf/debugbar": "<1.19", - "mdanter/ecc": "<2", - "mediawiki/abuse-filter": "<1.39.9|>=1.40,<1.41.3|>=1.42,<1.42.2", - "mediawiki/cargo": "<3.8.3", - "mediawiki/core": "<1.39.5|==1.40", - "mediawiki/data-transfer": ">=1.39,<1.39.11|>=1.41,<1.41.3|>=1.42,<1.42.2", - "mediawiki/matomo": "<2.4.3", - "mediawiki/semantic-media-wiki": "<4.0.2", - "mehrwert/phpmyadmin": "<3.2", - "melisplatform/melis-asset-manager": "<5.0.1", - "melisplatform/melis-cms": "<5.3.4", - "melisplatform/melis-cms-slider": "<5.3.1", - "melisplatform/melis-core": "<5.3.11", - "melisplatform/melis-front": "<5.0.1", - "mezzio/mezzio-swoole": "<3.7|>=4,<4.3", - "mgallegos/laravel-jqgrid": "<=1.3", - "microsoft/microsoft-graph": ">=1.16,<1.109.1|>=2,<2.0.1", - "microsoft/microsoft-graph-beta": "<2.0.1", - "microsoft/microsoft-graph-core": "<2.0.2", - "microweber/microweber": "<=2.0.19", - "mikehaertl/php-shellcommand": "<1.6.1", - "mineadmin/mineadmin": "<=3.0.9", - "miniorange/miniorange-saml": "<1.4.3", - "mittwald/typo3_forum": "<1.2.1", - "mobiledetect/mobiledetectlib": "<2.8.32", - "modx/revolution": "<=3.1", - "mojo42/jirafeau": "<4.4", - "mongodb/mongodb": ">=1,<1.9.2", - "mongodb/mongodb-extension": "<1.21.2", - "monolog/monolog": ">=1.8,<1.12", - "moodle/moodle": "<4.4.12|>=4.5.0.0-beta,<4.5.8|>=5.0.0.0-beta,<5.0.4|>=5.1.0.0-beta,<5.1.1", - "moonshine/moonshine": "<=3.12.5", - "mos/cimage": "<0.7.19", - "movim/moxl": ">=0.8,<=0.10", - "movingbytes/social-network": "<=1.2.1", - "mpdf/mpdf": "<=7.1.7", - "munkireport/comment": "<4", - "munkireport/managedinstalls": "<2.6", - "munkireport/munki_facts": "<1.5", - "munkireport/reportdata": "<3.5", - "munkireport/softwareupdate": "<1.6", - "mustache/mustache": ">=2,<2.14.1", - "mwdelaney/wp-enable-svg": "<=0.2", - "namshi/jose": "<2.2", - "nasirkhan/laravel-starter": "<11.11", - "nategood/httpful": "<1", - "neoan3-apps/template": "<1.1.1", - "neorazorx/facturascripts": "<2022.04", - "neos/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", - "neos/form": ">=1.2,<4.3.3|>=5,<5.0.9|>=5.1,<5.1.3", - "neos/media-browser": "<7.3.19|>=8,<8.0.16|>=8.1,<8.1.11|>=8.2,<8.2.11|>=8.3,<8.3.9", - "neos/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<5.3.10|>=7,<7.0.9|>=7.1,<7.1.7|>=7.2,<7.2.6|>=7.3,<7.3.4|>=8,<8.0.2", - "neos/swiftmailer": "<5.4.5", - "nesbot/carbon": "<2.72.6|>=3,<3.8.4", - "netcarver/textile": "<=4.1.2", - "netgen/tagsbundle": ">=3.4,<3.4.11|>=4,<4.0.15", - "nette/application": ">=2,<2.0.19|>=2.1,<2.1.13|>=2.2,<2.2.10|>=2.3,<2.3.14|>=2.4,<2.4.16|>=3,<3.0.6", - "nette/nette": ">=2,<2.0.19|>=2.1,<2.1.13", - "neuron-core/neuron-ai": "<=2.8.11", - "nilsteampassnet/teampass": "<3.1.3.1-dev", - "nitsan/ns-backup": "<13.0.1", - "nonfiction/nterchange": "<4.1.1", - "notrinos/notrinos-erp": "<=0.7", - "noumo/easyii": "<=0.9", - "novaksolutions/infusionsoft-php-sdk": "<1", - "novosga/novosga": "<=2.2.12", - "nukeviet/nukeviet": "<4.5.02", - "nyholm/psr7": "<1.6.1", - "nystudio107/craft-seomatic": "<3.4.12", - "nzedb/nzedb": "<0.8", - "nzo/url-encryptor-bundle": ">=4,<4.3.2|>=5,<5.0.1", - "october/backend": "<1.1.2", - "october/cms": "<1.0.469|==1.0.469|==1.0.471|==1.1.1", - "october/october": "<3.7.5", - "october/rain": "<1.0.472|>=1.1,<1.1.2", - "october/system": "<=3.7.12|>=4,<=4.0.11", - "oliverklee/phpunit": "<3.5.15", - "omeka/omeka-s": "<4.0.3", - "onelogin/php-saml": "<2.21.1|>=3,<3.8.1|>=4,<4.3.1", - "oneup/uploader-bundle": ">=1,<1.9.3|>=2,<2.1.5", - "open-web-analytics/open-web-analytics": "<1.8.1", - "opencart/opencart": ">=0", - "openid/php-openid": "<2.3", - "openmage/magento-lts": "<20.16", - "opensolutions/vimbadmin": "<=3.0.15", - "opensource-workshop/connect-cms": "<1.8.7|>=2,<2.4.7", - "orchid/platform": ">=8,<14.43", - "oro/calendar-bundle": ">=4.2,<=4.2.6|>=5,<=5.0.6|>=5.1,<5.1.1", - "oro/commerce": ">=4.1,<5.0.11|>=5.1,<5.1.1", - "oro/crm": ">=1.7,<1.7.4|>=3.1,<4.1.17|>=4.2,<4.2.7", - "oro/crm-call-bundle": ">=4.2,<=4.2.5|>=5,<5.0.4|>=5.1,<5.1.1", - "oro/customer-portal": ">=4.1,<=4.1.13|>=4.2,<=4.2.10|>=5,<=5.0.11|>=5.1,<=5.1.3", - "oro/platform": ">=1.7,<1.7.4|>=3.1,<3.1.29|>=4.1,<4.1.17|>=4.2,<=4.2.10|>=5,<=5.0.12|>=5.1,<=5.1.3", - "oveleon/contao-cookiebar": "<1.16.3|>=2,<2.1.3", - "oxid-esales/oxideshop-ce": "<=7.0.5", - "oxid-esales/paymorrow-module": ">=1,<1.0.2|>=2,<2.0.1", - "packbackbooks/lti-1-3-php-library": "<5", - "padraic/humbug_get_contents": "<1.1.2", - "pagarme/pagarme-php": "<3", - "pagekit/pagekit": "<=1.0.18", - "paragonie/ecc": "<2.0.1", - "paragonie/random_compat": "<2", - "paragonie/sodium_compat": "<1.24|>=2,<2.5", - "passbolt/passbolt_api": "<4.6.2", - "paypal/adaptivepayments-sdk-php": "<=3.9.2", - "paypal/invoice-sdk-php": "<=3.9", - "paypal/merchant-sdk-php": "<3.12", - "paypal/permissions-sdk-php": "<=3.9.1", - "pear/archive_tar": "<1.4.14", - "pear/auth": "<1.2.4", - "pear/crypt_gpg": "<1.6.7", - "pear/http_request2": "<2.7", - "pear/pear": "<=1.10.1", - "pegasus/google-for-jobs": "<1.5.1|>=2,<2.1.1", - "personnummer/personnummer": "<3.0.2", - "ph7software/ph7builder": "<=17.9.1", - "phanan/koel": "<5.1.4", - "phenx/php-svg-lib": "<0.5.2", - "php-censor/php-censor": "<2.0.13|>=2.1,<2.1.5", - "php-mod/curl": "<2.3.2", - "phpbb/phpbb": "<3.3.11", - "phpems/phpems": ">=6,<=6.1.3", - "phpfastcache/phpfastcache": "<6.1.5|>=7,<7.1.2|>=8,<8.0.7", - "phpmailer/phpmailer": "<6.5", - "phpmussel/phpmussel": ">=1,<1.6", - "phpmyadmin/phpmyadmin": "<5.2.2", - "phpmyfaq/phpmyfaq": "<=4.0.16", - "phpoffice/common": "<0.2.9", - "phpoffice/math": "<=0.2", - "phpoffice/phpexcel": "<=1.8.2", - "phpoffice/phpspreadsheet": "<1.30|>=2,<2.1.12|>=2.2,<2.4|>=3,<3.10|>=4,<5", - "phppgadmin/phppgadmin": "<=7.13", - "phpseclib/phpseclib": "<2.0.47|>=3,<3.0.36", - "phpservermon/phpservermon": "<3.6", - "phpsysinfo/phpsysinfo": "<3.4.3", - "phpunit/phpunit": "<8.5.52|>=9,<9.6.33|>=10,<10.5.62|>=11,<11.5.50|>=12,<12.5.8", - "phpwhois/phpwhois": "<=4.2.5", - "phpxmlrpc/extras": "<0.6.1", - "phpxmlrpc/phpxmlrpc": "<4.9.2", - "pi/pi": "<=2.5", - "pimcore/admin-ui-classic-bundle": "<=1.7.15|>=2.0.0.0-RC1-dev,<=2.2.2", - "pimcore/customer-management-framework-bundle": "<4.2.1", - "pimcore/data-hub": "<1.2.4", - "pimcore/data-importer": "<1.8.9|>=1.9,<1.9.3", - "pimcore/demo": "<10.3", - "pimcore/ecommerce-framework-bundle": "<1.0.10", - "pimcore/perspective-editor": "<1.5.1", - "pimcore/pimcore": "<=11.5.13|>=12.0.0.0-RC1-dev,<12.3.1", - "pimcore/web2print-tools-bundle": "<=5.2.1|>=6.0.0.0-RC1-dev,<=6.1", - "piwik/piwik": "<1.11", - "pixelfed/pixelfed": "<0.12.5", - "plotly/plotly.js": "<2.25.2", - "pocketmine/bedrock-protocol": "<8.0.2", - "pocketmine/pocketmine-mp": "<5.32.1", - "pocketmine/raklib": ">=0.14,<0.14.6|>=0.15,<0.15.1", - "pressbooks/pressbooks": "<5.18", - "prestashop/autoupgrade": ">=4,<4.10.1", - "prestashop/blockreassurance": "<=5.1.3", - "prestashop/blockwishlist": ">=2,<2.1.1", - "prestashop/contactform": ">=1.0.1,<4.3", - "prestashop/gamification": "<2.3.2", - "prestashop/prestashop": "<8.2.3", - "prestashop/productcomments": "<5.0.2", - "prestashop/ps_checkout": "<4.4.1|>=5,<5.0.5", - "prestashop/ps_contactinfo": "<=3.3.2", - "prestashop/ps_emailsubscription": "<2.6.1", - "prestashop/ps_facetedsearch": "<3.4.1", - "prestashop/ps_linklist": "<3.1", - "privatebin/privatebin": "<1.4|>=1.5,<1.7.4|>=1.7.7,<2.0.3", - "processwire/processwire": "<=3.0.246", - "propel/propel": ">=2.0.0.0-alpha1,<=2.0.0.0-alpha7", - "propel/propel1": ">=1,<=1.7.1", - "psy/psysh": "<=0.11.22|>=0.12,<=0.12.18", - "pterodactyl/panel": "<1.12", - "ptheofan/yii2-statemachine": ">=2.0.0.0-RC1-dev,<=2", - "ptrofimov/beanstalk_console": "<1.7.14", - "pubnub/pubnub": "<6.1", - "punktde/pt_extbase": "<1.5.1", - "pusher/pusher-php-server": "<2.2.1", - "pwweb/laravel-core": "<=0.3.6.0-beta", - "pxlrbt/filament-excel": "<1.1.14|>=2.0.0.0-alpha,<2.3.3", - "pyrocms/pyrocms": "<=3.9.1", - "qcubed/qcubed": "<=3.1.1", - "quickapps/cms": "<=2.0.0.0-beta2", - "rainlab/blog-plugin": "<1.4.1", - "rainlab/debugbar-plugin": "<3.1", - "rainlab/user-plugin": "<=1.4.5", - "rankmath/seo-by-rank-math": "<=1.0.95", - "rap2hpoutre/laravel-log-viewer": "<0.13", - "react/http": ">=0.7,<1.9", - "really-simple-plugins/complianz-gdpr": "<6.4.2", - "redaxo/source": "<=5.20.1", - "remdex/livehelperchat": "<4.29", - "renolit/reint-downloadmanager": "<4.0.2|>=5,<5.0.1", - "reportico-web/reportico": "<=8.1", - "rhukster/dom-sanitizer": "<1.0.7", - "rmccue/requests": ">=1.6,<1.8", - "robrichards/xmlseclibs": "<=3.1.3", - "roots/soil": "<4.1", - "roundcube/roundcubemail": "<1.5.10|>=1.6,<1.6.11", - "rudloff/alltube": "<3.0.3", - "rudloff/rtmpdump-bin": "<=2.3.1", - "s-cart/core": "<=9.0.5", - "s-cart/s-cart": "<6.9", - "sabberworm/php-css-parser": ">=1,<1.0.1|>=2,<2.0.1|>=3,<3.0.1|>=4,<4.0.1|>=5,<5.0.9|>=5.1,<5.1.3|>=5.2,<5.2.1|>=6,<6.0.2|>=7,<7.0.4|>=8,<8.0.1|>=8.1,<8.1.1|>=8.2,<8.2.1|>=8.3,<8.3.1", - "sabre/dav": ">=1.6,<1.7.11|>=1.8,<1.8.9", - "samwilson/unlinked-wikibase": "<1.42", - "scheb/two-factor-bundle": "<3.26|>=4,<4.11", - "sensiolabs/connect": "<4.2.3", - "serluck/phpwhois": "<=4.2.6", - "setasign/fpdi": "<2.6.4", - "sfroemken/url_redirect": "<=1.2.1", - "sheng/yiicms": "<1.2.1", - "shopware/core": "<6.6.10.9-dev|>=6.7,<6.7.6.1-dev", - "shopware/platform": "<6.6.10.7-dev|>=6.7,<6.7.3.1-dev", - "shopware/production": "<=6.3.5.2", - "shopware/shopware": "<=5.7.17|>=6.4.6,<6.6.10.10-dev|>=6.7,<6.7.6.1-dev", - "shopware/storefront": "<6.6.10.10-dev|>=6.7,<6.7.5.1-dev", - "shopxo/shopxo": "<=6.4", - "showdoc/showdoc": "<2.10.4", - "shuchkin/simplexlsx": ">=1.0.12,<1.1.13", - "silverstripe-australia/advancedreports": ">=1,<=2", - "silverstripe/admin": "<1.13.19|>=2,<2.1.8", - "silverstripe/assets": ">=1,<1.11.1", - "silverstripe/cms": "<4.11.3", - "silverstripe/comments": ">=1.3,<3.1.1", - "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", - "silverstripe/framework": "<5.3.23", - "silverstripe/graphql": ">=2,<2.0.5|>=3,<3.8.2|>=4,<4.3.7|>=5,<5.1.3", - "silverstripe/hybridsessions": ">=1,<2.4.1|>=2.5,<2.5.1", - "silverstripe/recipe-cms": ">=4.5,<4.5.3", - "silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1", - "silverstripe/reports": "<5.2.3", - "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4|>=2.1,<2.1.2", - "silverstripe/silverstripe-omnipay": "<2.5.2|>=3,<3.0.2|>=3.1,<3.1.4|>=3.2,<3.2.1", - "silverstripe/subsites": ">=2,<2.6.1", - "silverstripe/taxonomy": ">=1.3,<1.3.1|>=2,<2.0.1", - "silverstripe/userforms": "<3|>=5,<5.4.2", - "silverstripe/versioned-admin": ">=1,<1.11.1", - "simogeo/filemanager": "<=2.5", - "simple-updates/phpwhois": "<=1", - "simplesamlphp/saml2": "<=4.16.15|>=5.0.0.0-alpha1,<=5.0.0.0-alpha19", - "simplesamlphp/saml2-legacy": "<=4.16.15", - "simplesamlphp/simplesamlphp": "<1.18.6", - "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", - "simplesamlphp/simplesamlphp-module-openid": "<1", - "simplesamlphp/simplesamlphp-module-openidprovider": "<0.9", - "simplesamlphp/xml-common": "<1.20", - "simplesamlphp/xml-security": "==1.6.11", - "simplito/elliptic-php": "<1.0.6", - "sitegeist/fluid-components": "<3.5", - "sjbr/sr-feuser-register": "<2.6.2|>=5.1,<12.5", - "sjbr/sr-freecap": "<2.4.6|>=2.5,<2.5.3", - "sjbr/static-info-tables": "<2.3.1", - "slim/psr7": "<1.4.1|>=1.5,<1.5.1|>=1.6,<1.6.1", - "slim/slim": "<2.6", - "slub/slub-events": "<3.0.3", - "smarty/smarty": "<4.5.3|>=5,<5.1.1", - "snipe/snipe-it": "<=8.3.4", - "socalnick/scn-social-auth": "<1.15.2", - "socialiteproviders/steam": "<1.1", - "solspace/craft-freeform": "<4.1.29|>=5,<=5.14.6", - "soosyze/soosyze": "<=2", - "spatie/browsershot": "<5.0.5", - "spatie/image-optimizer": "<1.7.3", - "spencer14420/sp-php-email-handler": "<1", - "spipu/html2pdf": "<5.2.8", - "spiral/roadrunner": "<2025.1", - "spoon/library": "<1.4.1", - "spoonity/tcpdf": "<6.2.22", - "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", - "ssddanbrown/bookstack": "<24.05.1", - "starcitizentools/citizen-skin": ">=1.9.4,<3.9", - "starcitizentools/short-description": ">=4,<4.0.1", - "starcitizentools/tabber-neue": ">=1.9.1,<2.7.2|>=3,<3.1.1", - "starcitizenwiki/embedvideo": "<=4", - "statamic/cms": "<=5.22", - "stormpath/sdk": "<9.9.99", - "studio-42/elfinder": "<=2.1.64", - "studiomitte/friendlycaptcha": "<0.1.4", - "subhh/libconnect": "<7.0.8|>=8,<8.1", - "sukohi/surpass": "<1", - "sulu/form-bundle": ">=2,<2.5.3", - "sulu/sulu": "<1.6.44|>=2,<2.5.25|>=2.6,<2.6.9|>=3.0.0.0-alpha1,<3.0.0.0-alpha3", - "sumocoders/framework-user-bundle": "<1.4", - "superbig/craft-audit": "<3.0.2", - "svewap/a21glossary": "<=0.4.10", - "swag/paypal": "<5.4.4", - "swiftmailer/swiftmailer": "<6.2.5", - "swiftyedit/swiftyedit": "<1.2", - "sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2", - "sylius/grid": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", - "sylius/grid-bundle": "<1.10.1", - "sylius/paypal-plugin": "<1.6.2|>=1.7,<1.7.2|>=2,<2.0.2", - "sylius/resource-bundle": ">=1,<1.3.14|>=1.4,<1.4.7|>=1.5,<1.5.2|>=1.6,<1.6.4", - "sylius/sylius": "<1.12.19|>=1.13.0.0-alpha1,<1.13.4", - "symbiote/silverstripe-multivaluefield": ">=3,<3.1", - "symbiote/silverstripe-queuedjobs": ">=3,<3.0.2|>=3.1,<3.1.4|>=4,<4.0.7|>=4.1,<4.1.2|>=4.2,<4.2.4|>=4.3,<4.3.3|>=4.4,<4.4.3|>=4.5,<4.5.1|>=4.6,<4.6.4", - "symbiote/silverstripe-seed": "<6.0.3", - "symbiote/silverstripe-versionedfiles": "<=2.0.3", - "symfont/process": ">=0", - "symfony/cache": ">=3.1,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", - "symfony/dependency-injection": ">=2,<2.0.17|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", - "symfony/error-handler": ">=4.4,<4.4.4|>=5,<5.0.4", - "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1", - "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=5.3.14,<5.3.15|>=5.4.3,<5.4.4|>=6.0.3,<6.0.4", - "symfony/http-client": ">=4.3,<5.4.47|>=6,<6.4.15|>=7,<7.1.8", - "symfony/http-foundation": "<5.4.50|>=6,<6.4.29|>=7,<7.3.7", - "symfony/http-kernel": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", - "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", - "symfony/maker-bundle": ">=1.27,<1.29.2|>=1.30,<1.31.1", - "symfony/mime": ">=4.3,<4.3.8", - "symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", - "symfony/polyfill": ">=1,<1.10", - "symfony/polyfill-php55": ">=1,<1.10", - "symfony/process": "<5.4.51|>=6,<6.4.33|>=7,<7.1.7|>=7.3,<7.3.11|>=7.4,<7.4.5|>=8,<8.0.5", - "symfony/proxy-manager-bridge": ">=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", - "symfony/routing": ">=2,<2.0.19", - "symfony/runtime": ">=5.3,<5.4.46|>=6,<6.4.14|>=7,<7.1.7", - "symfony/security": ">=2,<2.7.51|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.8", - "symfony/security-bundle": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.4.10|>=7,<7.0.10|>=7.1,<7.1.3", - "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.9", - "symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", - "symfony/security-guard": ">=2.8,<3.4.48|>=4,<4.4.23|>=5,<5.2.8", - "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.4.47|>=6,<6.4.15|>=7,<7.1.8", - "symfony/serializer": ">=2,<2.0.11|>=4.1,<4.4.35|>=5,<5.3.12", - "symfony/symfony": "<5.4.51|>=6,<6.4.33|>=7,<7.3.11|>=7.4,<7.4.5|>=8,<8.0.5", - "symfony/translation": ">=2,<2.0.17", - "symfony/twig-bridge": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8", - "symfony/ux-autocomplete": "<2.11.2", - "symfony/ux-live-component": "<2.25.1", - "symfony/ux-twig-component": "<2.25.1", - "symfony/validator": "<5.4.43|>=6,<6.4.11|>=7,<7.1.4", - "symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8", - "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", - "symfony/webhook": ">=6.3,<6.3.8", - "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7|>=2.2.0.0-beta1,<2.2.0.0-beta2", - "symphonycms/symphony-2": "<2.6.4", - "t3/dce": "<0.11.5|>=2.2,<2.6.2", - "t3g/svg-sanitizer": "<1.0.3", - "t3s/content-consent": "<1.0.3|>=2,<2.0.2", - "tastyigniter/tastyigniter": "<4", - "tcg/voyager": "<=1.8", - "tecnickcom/tc-lib-pdf-font": "<2.6.4", - "tecnickcom/tcpdf": "<6.8", - "terminal42/contao-tablelookupwizard": "<3.3.5", - "thelia/backoffice-default-template": ">=2.1,<2.1.2", - "thelia/thelia": ">=2.1,<2.1.3", - "theonedemon/phpwhois": "<=4.2.5", - "thinkcmf/thinkcmf": "<6.0.8", - "thorsten/phpmyfaq": "<=4.0.16|>=4.1.0.0-alpha,<=4.1.0.0-beta2", - "tikiwiki/tiki-manager": "<=17.1", - "timber/timber": ">=0.16.6,<1.23.1|>=1.24,<1.24.1|>=2,<2.1", - "tinymce/tinymce": "<7.2", - "tinymighty/wiki-seo": "<1.2.2", - "titon/framework": "<9.9.99", - "tltneon/lgsl": "<7", - "tobiasbg/tablepress": "<=2.0.0.0-RC1", - "topthink/framework": "<6.0.17|>=6.1,<=8.0.4", - "topthink/think": "<=6.1.1", - "topthink/thinkphp": "<=3.2.3|>=6.1.3,<=8.0.4", - "torrentpier/torrentpier": "<=2.8.8", - "tpwd/ke_search": "<4.0.3|>=4.1,<4.6.6|>=5,<5.0.2", - "tribalsystems/zenario": "<=9.7.61188", - "truckersmp/phpwhois": "<=4.3.1", - "ttskch/pagination-service-provider": "<1", - "twbs/bootstrap": "<3.4.1|>=4,<4.3.1", - "twig/twig": "<3.11.2|>=3.12,<3.14.1|>=3.16,<3.19", - "typo3/cms": "<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2", - "typo3/cms-backend": "<4.1.14|>=4.2,<4.2.15|>=4.3,<4.3.7|>=4.4,<4.4.4|>=7,<=7.6.50|>=8,<=8.7.39|>=9,<9.5.55|>=10,<=10.4.54|>=11,<=11.5.48|>=12,<=12.4.40|>=13,<=13.4.22|>=14,<=14.0.1", - "typo3/cms-belog": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", - "typo3/cms-beuser": ">=9,<9.5.55|>=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18", - "typo3/cms-core": "<=8.7.56|>=9,<9.5.55|>=10,<=10.4.54|>=11,<=11.5.48|>=12,<=12.4.40|>=13,<=13.4.22|>=14,<=14.0.1", - "typo3/cms-dashboard": ">=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18", - "typo3/cms-extbase": "<6.2.24|>=7,<7.6.8|==8.1.1", - "typo3/cms-extensionmanager": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", - "typo3/cms-felogin": ">=4.2,<4.2.3", - "typo3/cms-fluid": "<4.3.4|>=4.4,<4.4.1", - "typo3/cms-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", - "typo3/cms-frontend": "<4.3.9|>=4.4,<4.4.5", - "typo3/cms-indexed-search": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", - "typo3/cms-install": "<4.1.14|>=4.2,<4.2.16|>=4.3,<4.3.9|>=4.4,<4.4.5|>=12.2,<12.4.8|==13.4.2", - "typo3/cms-lowlevel": ">=11,<=11.5.41", - "typo3/cms-recordlist": ">=11,<11.5.48", - "typo3/cms-recycler": ">=9,<9.5.55|>=10,<=10.4.54|>=11,<=11.5.48|>=12,<=12.4.40|>=13,<=13.4.22|>=14,<=14.0.1", - "typo3/cms-redirects": ">=10,<=10.4.54|>=11,<=11.5.48|>=12,<=12.4.40|>=13,<=13.4.22|>=14,<=14.0.1", - "typo3/cms-rte-ckeditor": ">=9.5,<9.5.42|>=10,<10.4.39|>=11,<11.5.30", - "typo3/cms-scheduler": ">=11,<=11.5.41", - "typo3/cms-setup": ">=9,<=9.5.50|>=10,<=10.4.49|>=11,<=11.5.43|>=12,<=12.4.30|>=13,<=13.4.11", - "typo3/cms-webhooks": ">=12,<=12.4.30|>=13,<=13.4.11", - "typo3/cms-workspaces": ">=9,<9.5.55|>=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18", - "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", - "typo3/html-sanitizer": ">=1,<=1.5.2|>=2,<=2.1.3", - "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.3.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<3.3.23|>=4,<4.0.17|>=4.1,<4.1.16|>=4.2,<4.2.12|>=4.3,<4.3.3", - "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1", - "typo3/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5", - "typo3fluid/fluid": ">=2,<2.0.8|>=2.1,<2.1.7|>=2.2,<2.2.4|>=2.3,<2.3.7|>=2.4,<2.4.4|>=2.5,<2.5.11|>=2.6,<2.6.10", - "ua-parser/uap-php": "<3.8", - "uasoft-indonesia/badaso": "<=2.9.7", - "unisharp/laravel-filemanager": "<2.9.1", - "universal-omega/dynamic-page-list3": "<3.6.4", - "unopim/unopim": "<=0.3", - "userfrosting/userfrosting": ">=0.3.1,<4.6.3", - "usmanhalalit/pixie": "<1.0.3|>=2,<2.0.2", - "uvdesk/community-skeleton": "<=1.1.1", - "uvdesk/core-framework": "<=1.1.1", - "vanilla/safecurl": "<0.9.2", - "verbb/comments": "<1.5.5", - "verbb/formie": "<=2.1.43", - "verbb/image-resizer": "<2.0.9", - "verbb/knock-knock": "<1.2.8", - "verot/class.upload.php": "<=2.1.6", - "vertexvaar/falsftp": "<0.2.6", - "villagedefrance/opencart-overclocked": "<=1.11.1", - "vova07/yii2-fileapi-widget": "<0.1.9", - "vrana/adminer": "<=4.8.1", - "vufind/vufind": ">=2,<9.1.1", - "waldhacker/hcaptcha": "<2.1.2", - "wallabag/tcpdf": "<6.2.22", - "wallabag/wallabag": "<2.6.11", - "wanglelecc/laracms": "<=1.0.3", - "wapplersystems/a21glossary": "<=0.4.10", - "web-auth/webauthn-framework": ">=3.3,<3.3.4|>=4.5,<4.9", - "web-auth/webauthn-lib": ">=4.5,<4.9", - "web-feet/coastercms": "==5.5", - "web-tp3/wec_map": "<3.0.3", - "webbuilders-group/silverstripe-kapost-bridge": "<0.4", - "webcoast/deferred-image-processing": "<1.0.2", - "webklex/laravel-imap": "<5.3", - "webklex/php-imap": "<5.3", - "webpa/webpa": "<3.1.2", - "webreinvent/vaahcms": "<=2.3.1", - "wikibase/wikibase": "<=1.39.3", - "wikimedia/parsoid": "<0.12.2", - "willdurand/js-translation-bundle": "<2.1.1", - "winter/wn-backend-module": "<1.2.4", - "winter/wn-cms-module": "<1.0.476|>=1.1,<1.1.11|>=1.2,<1.2.7", - "winter/wn-dusk-plugin": "<2.1", - "winter/wn-system-module": "<1.2.4", - "wintercms/winter": "<=1.2.3", - "wireui/wireui": "<1.19.3|>=2,<2.1.3", - "woocommerce/woocommerce": "<6.6|>=8.8,<8.8.5|>=8.9,<8.9.3", - "wp-cli/wp-cli": ">=0.12,<2.5", - "wp-graphql/wp-graphql": "<=1.14.5", - "wp-premium/gravityforms": "<2.4.21", - "wpanel/wpanel4-cms": "<=4.3.1", - "wpcloud/wp-stateless": "<3.2", - "wpglobus/wpglobus": "<=1.9.6", - "wwbn/avideo": "<14.3", - "xataface/xataface": "<3", - "xpressengine/xpressengine": "<3.0.15", - "yab/quarx": "<2.4.5", - "yeswiki/yeswiki": "<=4.5.4", - "yetiforce/yetiforce-crm": "<6.5", - "yidashi/yii2cmf": "<=2", - "yii2mod/yii2-cms": "<1.9.2", - "yiisoft/yii": "<1.1.31", - "yiisoft/yii2": "<2.0.52", - "yiisoft/yii2-authclient": "<2.2.15", - "yiisoft/yii2-bootstrap": "<2.0.4", - "yiisoft/yii2-dev": "<=2.0.45", - "yiisoft/yii2-elasticsearch": "<2.0.5", - "yiisoft/yii2-gii": "<=2.2.4", - "yiisoft/yii2-jui": "<2.0.4", - "yiisoft/yii2-redis": "<2.0.20", - "yikesinc/yikes-inc-easy-mailchimp-extender": "<6.8.6", - "yoast-seo-for-typo3/yoast_seo": "<7.2.3", - "yourls/yourls": "<=1.10.2", - "yuan1994/tpadmin": "<=1.3.12", - "yungifez/skuul": "<=2.6.5", - "z-push/z-push-dev": "<2.7.6", - "zencart/zencart": "<=1.5.7.0-beta", - "zendesk/zendesk_api_client_php": "<2.2.11", - "zendframework/zend-cache": ">=2.4,<2.4.8|>=2.5,<2.5.3", - "zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2", - "zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2", - "zendframework/zend-db": "<2.2.10|>=2.3,<2.3.5", - "zendframework/zend-developer-tools": ">=1.2.2,<1.2.3", - "zendframework/zend-diactoros": "<1.8.4", - "zendframework/zend-feed": "<2.10.3", - "zendframework/zend-form": ">=2,<2.2.7|>=2.3,<2.3.1", - "zendframework/zend-http": "<2.8.1", - "zendframework/zend-json": ">=2.1,<2.1.6|>=2.2,<2.2.6", - "zendframework/zend-ldap": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.8|>=2.3,<2.3.3", - "zendframework/zend-mail": "<2.4.11|>=2.5,<2.7.2", - "zendframework/zend-navigation": ">=2,<2.2.7|>=2.3,<2.3.1", - "zendframework/zend-session": ">=2,<2.2.9|>=2.3,<2.3.4", - "zendframework/zend-validator": ">=2.3,<2.3.6", - "zendframework/zend-view": ">=2,<2.2.7|>=2.3,<2.3.1", - "zendframework/zend-xmlrpc": ">=2.1,<2.1.6|>=2.2,<2.2.6", - "zendframework/zendframework": "<=3", - "zendframework/zendframework1": "<1.12.20", - "zendframework/zendopenid": "<2.0.2", - "zendframework/zendrest": "<2.0.2", - "zendframework/zendservice-amazon": "<2.0.3", - "zendframework/zendservice-api": "<1", - "zendframework/zendservice-audioscrobbler": "<2.0.2", - "zendframework/zendservice-nirvanix": "<2.0.2", - "zendframework/zendservice-slideshare": "<2.0.2", - "zendframework/zendservice-technorati": "<2.0.2", - "zendframework/zendservice-windowsazure": "<2.0.2", - "zendframework/zendxml": ">=1,<1.0.1", - "zenstruck/collection": "<0.2.1", - "zetacomponents/mail": "<1.8.2", - "zf-commons/zfc-user": "<1.2.2", - "zfcampus/zf-apigility-doctrine": ">=1,<1.0.3", - "zfr/zfr-oauth2-server-module": "<0.1.2", - "zoujingli/thinkadmin": "<=6.1.53" - }, - "default-branch": true, - "type": "metapackage", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "role": "maintainer" - }, - { - "name": "Ilya Tribusean", - "email": "slash3b@gmail.com", - "role": "maintainer" - } - ], - "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", - "keywords": [ - "dev" - ], - "support": { - "issues": "https://github.com/Roave/SecurityAdvisories/issues", - "source": "https://github.com/Roave/SecurityAdvisories/tree/latest" - }, - "funding": [ - { - "url": "https://github.com/Ocramius", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/roave/security-advisories", - "type": "tidelift" - } - ], - "time": "2026-01-30T22:06:58+00:00" - }, { "name": "sebastian/cli-parser", "version": "1.0.2", @@ -7629,13 +6615,12 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { - "rhukster/dom-sanitizer": 20, - "roave/security-advisories": 20 + "rhukster/dom-sanitizer": 20 }, "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=7.4", + "php": ">=8.2", "ext-apcu": "*", "ext-ctype": "*", "ext-curl": "*", @@ -7656,7 +6641,7 @@ }, "platform-dev": [], "platform-overrides": { - "php": "7.4" + "php": "8.2" }, "plugin-api-version": "2.6.0" } From f7f951712212b659b0f7179c05bfa414b7d556b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Thu, 26 Feb 2026 12:59:13 +0100 Subject: [PATCH 38/42] ci: use php 8.3 in GH workflows as well --- .github/workflows/ci.yml | 2 +- .github/workflows/php-unit.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54dc8b108015..d45250bef066 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,4 +26,4 @@ jobs: name: PHP Unit uses: ./.github/workflows/php-unit.yml with: - php-versions: '["7.4"]' + php-versions: '["8.3"]' diff --git a/.github/workflows/php-unit.yml b/.github/workflows/php-unit.yml index 38f51a2f0693..bef5b42dd716 100644 --- a/.github/workflows/php-unit.yml +++ b/.github/workflows/php-unit.yml @@ -19,9 +19,9 @@ jobs: php: ${{ fromJSON(inputs.php-versions) }} database: [sqlite] include: - - php: "7.4" + - php: "8.3" database: "mysql:8.0" - - php: "7.4" + - php: "8.3" database: "postgres:10.21" services: From 2835079918a42f9bbf49c3b75b97e306074f826b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Thu, 26 Feb 2026 15:12:26 +0100 Subject: [PATCH 39/42] fix: SVG preview generation --- lib/private/Preview/SVG.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/private/Preview/SVG.php b/lib/private/Preview/SVG.php index d7d385809366..00e474a47421 100644 --- a/lib/private/Preview/SVG.php +++ b/lib/private/Preview/SVG.php @@ -64,7 +64,7 @@ public function getThumbnail(File $file, $maxX, $maxY, $scalingUp) { //new image object $image = new \OC_Image(); - $image->loadFromData($imagick); + $image->loadFromData($imagick->getImageBlob()); //check if image object is valid if ($image->valid()) { $image->scaleDownToFit($maxX, $maxY); From b884476768aeb4a51bc2a917e1dfb8ce74e76eca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Thu, 26 Feb 2026 15:28:33 +0100 Subject: [PATCH 40/42] fix: Office preview generation + fixing CryptoSessionDataTest --- lib/private/Preview/Office.php | 2 +- tests/lib/Session/CryptoSessionDataTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/private/Preview/Office.php b/lib/private/Preview/Office.php index 479211ae5bd2..d8f038279b23 100644 --- a/lib/private/Preview/Office.php +++ b/lib/private/Preview/Office.php @@ -77,7 +77,7 @@ public function getThumbnail(File $file, $maxX, $maxY, $scalingUp) { } $image = new \OC_Image(); - $image->loadFromData($imagick); + $image->loadFromData($imagick->getImageBlob()); \unlink($pdfPreview); diff --git a/tests/lib/Session/CryptoSessionDataTest.php b/tests/lib/Session/CryptoSessionDataTest.php index c294d8f25058..04595a715584 100644 --- a/tests/lib/Session/CryptoSessionDataTest.php +++ b/tests/lib/Session/CryptoSessionDataTest.php @@ -65,7 +65,7 @@ public function testDestructExceptionCatching() { $instance = new CryptoSessionData($session, $this->crypto, 'PASS'); $instance->set('test', 'test'); $e = new SessionNotAvailableException(); - $session->expects($this->exactly(2))->method('set')->willThrowException($e); + $session->expects($this->atLeastOnce())->method('set')->willThrowException($e); $instance->__destruct(); } } From 9161d3ee2cca61e56d212d5e3c25a6ca09b44eb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Thu, 26 Feb 2026 16:46:23 +0100 Subject: [PATCH 41/42] fix: deprecations in ViewTest --- tests/lib/Files/ViewTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/lib/Files/ViewTest.php b/tests/lib/Files/ViewTest.php index bf526d0c0218..d840969f78ad 100644 --- a/tests/lib/Files/ViewTest.php +++ b/tests/lib/Files/ViewTest.php @@ -1615,10 +1615,10 @@ public function testHookPaths($root, $path, $shouldEmit) { $defaultRootValue->setAccessible(true); $oldRoot = $defaultRootValue->getValue(); $defaultView = new View('/foo/files'); - $defaultRootValue->setValue($defaultView); + $defaultRootValue->setValue(null, $defaultView); $view = new View($root); $result = static::invokePrivate($view, 'shouldEmitHooks', [$path]); - $defaultRootValue->setValue($oldRoot); + $defaultRootValue->setValue(null, $oldRoot); $this->assertEquals($shouldEmit, $result); } From 3c762f1c7705a632e999a5a54c93eeb67a9e490c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <1005065+DeepDiver1975@users.noreply.github.com> Date: Thu, 26 Feb 2026 21:05:37 +0100 Subject: [PATCH 42/42] fix: mysql setup --- lib/private/Setup/MySQL.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/private/Setup/MySQL.php b/lib/private/Setup/MySQL.php index 3ccda3e8f0e4..ddcef24c03e5 100644 --- a/lib/private/Setup/MySQL.php +++ b/lib/private/Setup/MySQL.php @@ -54,7 +54,8 @@ public function setupDatabase($username) { $query='select count(*) from information_schema.tables where table_schema=? AND table_name = ?'; $result = $connection->executeQuery($query, [$this->dbName, $this->tablePrefix.'users']); $row = $result->fetchAssociative(); - if (!$row or $row['count(*)'] === '0') { + $result->free(); + if (!$row || $row['count(*)'] === 0) { (new \OC\DB\MDB2SchemaManager(\OC::$server->getDatabaseConnection()))->createDbFromStructure($this->dbDefinitionFile); } }