From 85b17aef8c82d640ae29ae7cb796e8f1d7452d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Sun, 8 Jan 2023 10:02:49 +0100 Subject: [PATCH 01/32] Attributes --- src/Translatable/TranslatableEntityTrait.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Translatable/TranslatableEntityTrait.php b/src/Translatable/TranslatableEntityTrait.php index 526f214..31e6d32 100644 --- a/src/Translatable/TranslatableEntityTrait.php +++ b/src/Translatable/TranslatableEntityTrait.php @@ -2,6 +2,8 @@ namespace ADT\Utils\Translatable; +use Gedmo\Mapping\Annotation\Locale; + trait TranslatableEntityTrait { /** @@ -9,6 +11,7 @@ trait TranslatableEntityTrait * Used locale to override Translation listener`s locale * this is not a mapped field of entity metadata, just a simple property */ + #[Locale] private $locale; public function setTranslatableLocale($locale) From 2c3dc46ef61700e02100e7e724a70dd953b841df Mon Sep 17 00:00:00 2001 From: Tomas Kudelka Date: Mon, 7 Aug 2023 16:57:28 +0200 Subject: [PATCH 02/32] createDirAtomically --- src/FileSystem.php | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/FileSystem.php diff --git a/src/FileSystem.php b/src/FileSystem.php new file mode 100644 index 0000000..679460c --- /dev/null +++ b/src/FileSystem.php @@ -0,0 +1,28 @@ + Date: Mon, 7 Aug 2023 17:30:34 +0200 Subject: [PATCH 03/32] remove command lock --- src/CommandLock.php | 128 -------------------------------- src/CommandLockPathProvider.php | 44 ----------- 2 files changed, 172 deletions(-) delete mode 100644 src/CommandLock.php delete mode 100644 src/CommandLockPathProvider.php diff --git a/src/CommandLock.php b/src/CommandLock.php deleted file mode 100644 index 9d74db4..0000000 --- a/src/CommandLock.php +++ /dev/null @@ -1,128 +0,0 @@ -getHelper('container'); - * $this->commandLockPathProvider = $containerHelper->getByType(\ADT\Utils\CommandLockPathProvider::class); - * } - * - * protected function execute(InputInterface $input, OutputInterface $output) { - * $this->tryLock(); - * // Execute command... - * $this->tryUnlock(); - * } - */ - - /** - * Tries to create a lock file with its process id. - * If file exists but process id in it doesn't belong to any running process, overrides the lock. - * If file exists and process id in it belongs to a running process, fails. - * @param bool $strict If true, failure throws an exception - * @param string $identifier If set, adds a "-$identifier" suffix to the name of the lock file. - * @return bool true in case of success, false otherwise - * @throws \Exception - */ - protected function tryLock(bool $strict = true, $identifier = null) { - $folderName = $this->getFolder($identifier); - if (!file_exists($folderName)) { - mkdir($folderName, 0777, true); - } - $pathName = $this->getPath($identifier); - $stream = fopen($pathName, 'a+'); - fseek($stream, 0); - $line = fgets($stream); - // If the file has no characters, it means it either did not exist - // or wasn't owned by any process - // If pgid can't be retrieved, the process that owned the lock - // doesn't exist anymore - if (strlen($line) === 0 || posix_getpgid(intval($line)) === false) { - fseek($stream, 0); - ftruncate($stream, 0); - fwrite($stream, getmypid()); - fclose($stream); - return true; - } - if ($strict) { - throw new \Exception('Error locking: Command already locked by a running process'); - } - else { - return false; - } - } - - /** - * Tries to unlock a lock. - * Can fail if the lock file has id of another running process. - * @param bool $strict If true, failure throws an exception - * @param string $identifier If set, adds a "-$identifier" suffix to the name of the lock file - * @return bool true in case of success, false otherwise - * @throws \Exception - */ - protected function tryUnlock(bool $strict = false, $identifier = null) { - $folderName = $this->getFolder($identifier); - if (!file_exists($folderName)) { - mkdir($folderName, 0777, true); - } - $pathName = $this->getPath($identifier); - $stream = fopen($pathName, 'a+'); - fseek($stream, 0); - // If the file has no characters, it means it either did not exist - // or wasn't owned by any process - // If process id in file matches the current process' id, it is owned - // by the current process and can be safely removed - if ($stream === false || strlen(($line = fgets($stream))) === 0 || intval($line) === getmypid()) { - // Between fclose and unlink calls, the lock is still practically owned by the current process - // and atomicity can still be guaranteed - fclose($stream); - unlink($pathName); - return true; - } - if ($strict) { - throw new \Exception('Error unlocking: Command already locked by a running process'); - } - else { - return false; - } - } - - protected function getPath($identifier = null) { - $fullName = static::getFullName($this->getName(), $identifier); - if (in_array('nette.safe', stream_get_wrappers())) { - return 'nette.safe://' . $this->commandLockPathProvider->getPath($fullName); - } - else { - return $this->commandLockPathProvider->getPath($fullName); - } - } - - protected function getFolder($identifier = null) { - $fullName = static::getFullName($this->getName(), $identifier); - return $this->commandLockPathProvider->getFolder($fullName); - } - - public static function getFullName($name, $identifier) - { - return $name . (is_string($identifier) ? "-$identifier" : ''); - } -} diff --git a/src/CommandLockPathProvider.php b/src/CommandLockPathProvider.php deleted file mode 100644 index 0b17705..0000000 --- a/src/CommandLockPathProvider.php +++ /dev/null @@ -1,44 +0,0 @@ -createPathString($path, $format); - } - - protected function createPathString($path, $format) { - if ($path[strlen($path) - 1] !== '/' && $path[strlen($path) - 1] !== '\\') { - $path .= '/'; - } - if (strpos($path, '$cmd$') !== false) { - throw new \Exception("Path can't include \$cmd\$"); - } - if (strpos($format, '$cmd$') === false) { - $this->lockPath = $path . '.$cmd$.lock'; - } - else { - $this->lockPath = $path . $format; - } - } - - public function getPath(string $commandName = '') { - $commandName = preg_replace('/[^-a-zA-Z0-9]/', '-', $commandName); - return str_replace('$cmd$', $commandName, $this->lockPath); - } - - public function getFolder(string $commandName = '') { - $path = $this->getPath($commandName); - return substr($path, 0, strripos($path, '/') + 1); - } - -} \ No newline at end of file From b4998b6e97e7b31181f02c0b7cb6995267d43351 Mon Sep 17 00:00:00 2001 From: Tomas Kudelka Date: Sat, 2 Sep 2023 07:57:23 +0200 Subject: [PATCH 04/32] handleGuzzleException, procisteni --- src/BackgroundQueue.php | 47 ---------------------------------- src/BadRequestException.php | 10 -------- src/Guzzle.php | 51 ++++++++++--------------------------- 3 files changed, 14 insertions(+), 94 deletions(-) delete mode 100644 src/BackgroundQueue.php delete mode 100644 src/BadRequestException.php diff --git a/src/BackgroundQueue.php b/src/BackgroundQueue.php deleted file mode 100644 index 904081a..0000000 --- a/src/BackgroundQueue.php +++ /dev/null @@ -1,47 +0,0 @@ -getRequest()) . ($e instanceof BadResponseException ? Message::toString($e->getResponse()) : $e->getMessage()); - - if ($e instanceof ServerException || $e instanceof ConnectException) { - throw new TemporaryErrorException($message); - } - - throw new BadRequestException($message); - } - - throw $e; - } - - /** - * @throws BadRequestException - * @throws TemporaryErrorException - */ - public static function handleGuzzleResult(Closure $closure) - { - try { - return $closure(); - } catch (Exception $e) { - self::handleException($e); - } - } -} \ No newline at end of file diff --git a/src/BadRequestException.php b/src/BadRequestException.php deleted file mode 100644 index 627722f..0000000 --- a/src/BadRequestException.php +++ /dev/null @@ -1,10 +0,0 @@ -getRequest()) . ($e instanceof BadResponseException ? Message::toString($e->getResponse()) : $e->getMessage())) - : $e - ); + if ($e instanceof GuzzleException) { + $message = ''; + if ($e instanceof ConnectException || $e instanceof RequestException) { + $message = "REQUEST: " . Message::toString($e->getRequest()) . "\n RESPONSE: "; + } + $message .= ($e instanceof RequestException ? Message::toString($e->getResponse()) : $e->getMessage()); + + throw new Exception($message); + } + + throw $e; } } From f0e71a926b5096881f2d52fcc16512eb09828580 Mon Sep 17 00:00:00 2001 From: Tomas Kudelka Date: Sat, 2 Sep 2023 08:33:45 +0200 Subject: [PATCH 05/32] fix --- src/Guzzle.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Guzzle.php b/src/Guzzle.php index 6fa861f..e205a3f 100644 --- a/src/Guzzle.php +++ b/src/Guzzle.php @@ -14,7 +14,7 @@ public static function handleException(Exception $e): ?Exception if ($e instanceof GuzzleException) { $message = ''; if ($e instanceof ConnectException || $e instanceof RequestException) { - $message = "REQUEST: " . Message::toString($e->getRequest()) . "\n RESPONSE: "; + $message = "--- REQUEST ---\n" . Message::toString($e->getRequest()) . "\n --- RESPONSE ---\n"; } $message .= ($e instanceof RequestException ? Message::toString($e->getResponse()) : $e->getMessage()); From 1ffdf4a9c8949cf1455465aea0f234f034a4d360 Mon Sep 17 00:00:00 2001 From: Tomas Kudelka Date: Mon, 4 Sep 2023 20:37:09 +0200 Subject: [PATCH 06/32] fix --- src/Guzzle.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Guzzle.php b/src/Guzzle.php index e205a3f..0cad373 100644 --- a/src/Guzzle.php +++ b/src/Guzzle.php @@ -6,10 +6,14 @@ use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Psr7\Message; +use Throwable; class Guzzle { - public static function handleException(Exception $e): ?Exception + /** + * @throws Throwable + */ + public static function handleException(Throwable $e): ?Exception { if ($e instanceof GuzzleException) { $message = ''; From f6a91673760c24245c9d82bed1bf2f9c2f94c472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Fri, 1 Mar 2024 09:09:52 +0100 Subject: [PATCH 07/32] Update Guzzle.php --- src/Guzzle.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Guzzle.php b/src/Guzzle.php index 0cad373..ada18ab 100644 --- a/src/Guzzle.php +++ b/src/Guzzle.php @@ -20,7 +20,7 @@ public static function handleException(Throwable $e): ?Exception if ($e instanceof ConnectException || $e instanceof RequestException) { $message = "--- REQUEST ---\n" . Message::toString($e->getRequest()) . "\n --- RESPONSE ---\n"; } - $message .= ($e instanceof RequestException ? Message::toString($e->getResponse()) : $e->getMessage()); + $message .= ($e instanceof RequestException && $e->getResponse() ? Message::toString($e->getResponse()) : $e->getMessage()); throw new Exception($message); } From 119c830a37ab5e34115941c586fc2e2693006a41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Fri, 7 Jun 2024 10:00:52 +0200 Subject: [PATCH 08/32] add removeDiacritics --- src/Strings.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Strings.php b/src/Strings.php index bab2206..aa6d260 100644 --- a/src/Strings.php +++ b/src/Strings.php @@ -40,6 +40,12 @@ public static function containsCharactersLargerThen(string $s, int $code, string return false; } + + public static function removeDiacritics(string $s): string + { + $transliterator = Transliterator::createFromRules(':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: NFC;', Transliterator::FORWARD); + return $transliterator->transliterate($s); + } public static function validateFullName(string $fullName): bool { From d562b41e44c40c3d0655915e06f4b4415b8427c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Fri, 7 Jun 2024 10:39:24 +0200 Subject: [PATCH 09/32] Update Strings.php --- src/Strings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Strings.php b/src/Strings.php index aa6d260..8dc7ff6 100644 --- a/src/Strings.php +++ b/src/Strings.php @@ -43,7 +43,7 @@ public static function containsCharactersLargerThen(string $s, int $code, string public static function removeDiacritics(string $s): string { - $transliterator = Transliterator::createFromRules(':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: NFC;', Transliterator::FORWARD); + $transliterator = \Transliterator::createFromRules(':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: NFC;', Transliterator::FORWARD); return $transliterator->transliterate($s); } From d4eb82ee6f2b576e189eb850ce83e9e36021190d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Fri, 7 Jun 2024 11:17:56 +0200 Subject: [PATCH 10/32] Update Strings.php --- src/Strings.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Strings.php b/src/Strings.php index 8dc7ff6..f098a4d 100644 --- a/src/Strings.php +++ b/src/Strings.php @@ -2,6 +2,8 @@ namespace ADT\Utils; +use Transliterator; + class Strings { /** @@ -43,7 +45,7 @@ public static function containsCharactersLargerThen(string $s, int $code, string public static function removeDiacritics(string $s): string { - $transliterator = \Transliterator::createFromRules(':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: NFC;', Transliterator::FORWARD); + $transliterator = Transliterator::createFromRules(':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: NFC;', Transliterator::FORWARD); return $transliterator->transliterate($s); } From 16bf429927767a0c0a7e8374f430e1b3c0de50d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Lohnisk=C3=BD?= Date: Thu, 5 Sep 2024 18:25:19 +0200 Subject: [PATCH 11/32] Update readme.md See 5cb686685ffe4276ad600be701bd23ad571bef21 --- readme.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/readme.md b/readme.md index fa39d27..0c6ea90 100644 --- a/readme.md +++ b/readme.md @@ -1,15 +1,3 @@ -## CommandLock - -CommandLock is a helper Trait for preventing a command from being ran multiple times at once by creating a lock file for each command. - -The lock files also contain process id of the command which created the lock. -If there is an attempt to run a command that is locked but the process id saved in the lock file doesn't belong to a running process, the lock is reset. - -Class using this Trait also needs to have a property commandLockPathProvider of type CommandLockPathProvider accessible by the Trait. - -CommandLock only runs on Unix and has no required dependencies. However only if Nette SafeStream is registered (either automatically or manually), -atomicity and thread safety of lock and unlock operations can be guaranteed. - ## Installation ```composer require adt/utils``` From 3ca0cd440dbdf857de0dc1248d872cfd329f701b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Sat, 7 Sep 2024 00:36:50 +0200 Subject: [PATCH 12/32] convertToType --- src/Strings.php | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/Strings.php b/src/Strings.php index f098a4d..e509dd5 100644 --- a/src/Strings.php +++ b/src/Strings.php @@ -78,4 +78,37 @@ public static function validateFullName(string $fullName): bool $ /mx", $fullName); } + + public static function convertToType($str) + { + if (!is_string($str)) { + return $str; + } + + // Pokud řetězec obsahuje pouze číslice + if (ctype_digit($str)) { + // když začíná 0 a má víc jak 1 znak, vratíme string + if (strpos($str, '0') === 0 && strlen($str) > 1) { + return $str; + } + return (int)$str; // jinak vratíme integer + } + + // Pokud řetězec obsahuje číslice a případně jednu desetinnou tečku, vratíme float + if (preg_match('/^\d+\.\d+$/', $str)) { + return (float)$str; + } + + // Pro "true" a "false" vrátíme boolean + if (strtolower($str) === 'true') { + return true; + } + + if (strtolower($str) === 'false') { + return false; + } + + // V opačném případě vratíme původní řetězec + return $str; + } } From cb121b55f1c9caf2bd1f176c304ddb584f6ce8b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Sun, 15 Sep 2024 19:32:51 +0200 Subject: [PATCH 13/32] filter image --- composer.json | 3 ++ src/Filters/Image.php | 118 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 src/Filters/Image.php diff --git a/composer.json b/composer.json index abe235d..fac40cf 100644 --- a/composer.json +++ b/composer.json @@ -5,6 +5,9 @@ "src/" ] }, + "require": { + "nette/utils": "^2.0|^3.0|^4.0" + }, "require-dev": { "guzzlehttp/guzzle": "^6.3|^7.0", "nette/tester": "^2.4" diff --git a/src/Filters/Image.php b/src/Filters/Image.php new file mode 100644 index 0000000..378d2b4 --- /dev/null +++ b/src/Filters/Image.php @@ -0,0 +1,118 @@ +path = $path; + $this->dir = $dir; + $this->multiplier = $multiplier; + } + + const FormatToExtensions = [ + IMAGETYPE_WEBP => 'webp' + ]; + + /** + * @throws ImageException + * @throws UnknownImageFileException + * @throws Exception + */ + public function format(string $url, int $width, int $height, int $mode = \Nette\Utils\Image::FIT, int $format = IMAGETYPE_WEBP): string + { + $isRemoteUrl = $this->isRemoteUrl($url); + + // original file does not exist + if ($isRemoteUrl) { + $contents = @file_get_contents($url); + if (!$contents) { + return $url; + } + + list($urlWithoutExtension,) = $this->splitUrlOnLastDot($this->removeProtocol($url)); + + } else { + $url = trim($url, '/'); + + if (!file_exists($this->path . '/' . $url)) { + return $url; + } + + list($urlWithoutExtension,) = $this->splitUrlOnLastDot($url); + } + + + + $width = $width * $this->multiplier; + $height = $height * $this->multiplier; + $newPath = $this->path . '/' . $this->dir; + $newFileName = $urlWithoutExtension . '_' . $width . '_' . $height . '_' . $mode . '.' . self::FormatToExtensions[$format]; + + // thumbnail already exists + if (file_exists($newPath . '/' . $newFileName)) { + goto end; + } + + FileSystem::createDirAtomically(dirname($newPath . '/' . $newFileName)); + + $prevErrorHandler = set_error_handler(function ($errno, $errstr) use (&$prevErrorHandler) { + if ($errno === E_USER_WARNING && $errstr === 'Nette\Utils\Image::fromFile(): gd-png: libpng warning: iCCP: known incorrect sRGB profile') { + return true; + } + return $prevErrorHandler ? $prevErrorHandler(...func_get_args()) : false; + }); + if ($isRemoteUrl) { + $image = \Nette\Utils\Image::fromString($contents); + } else { + $image = \Nette\Utils\Image::fromFile($this->path . '/' . $url); + } + set_error_handler($prevErrorHandler); + $image->resize($width, $height, $mode); + $image->save($newPath . '/' . $newFileName, 100, $format); + + end: + + return '/' . $this->dir . '/' . $newFileName; + } + + private function isRemoteUrl(string $url): bool + { + $parsedUrl = parse_url($url); + + if (isset($parsedUrl['scheme'])) { + return in_array($parsedUrl['scheme'], ['http', 'https']); + } + + return false; + } + + private function splitUrlOnLastDot(string $url): array + { + $lastDotPos = strrpos($url, '.'); + + if ($lastDotPos !== false) { + $part1 = substr($url, 0, $lastDotPos); + $part2 = substr($url, $lastDotPos + 1); + return [$part1, $part2]; + } + + return [$url, '']; + } + + private function removeProtocol(string $url): string + { + return preg_replace("/^https?:\/\//", "", $url); + } +} From 9e339810b3c7569274d8f207a56f6f3a67d916f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Sun, 15 Sep 2024 19:34:43 +0200 Subject: [PATCH 14/32] fix --- src/Filters/Image.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Filters/Image.php b/src/Filters/Image.php index 378d2b4..8b5d9e7 100644 --- a/src/Filters/Image.php +++ b/src/Filters/Image.php @@ -1,7 +1,7 @@ Date: Sun, 26 Jan 2025 14:09:27 +0100 Subject: [PATCH 15/32] Update CssModule.php --- src/CssModule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CssModule.php b/src/CssModule.php index b533502..6414a6b 100644 --- a/src/CssModule.php +++ b/src/CssModule.php @@ -23,7 +23,7 @@ public function injectCssModule() * @param string|null $dir * @return array */ - private function load(string $moduleName = 'index', string $dir = NULL): array + private function load(string $moduleName = 'index', ?string $dir = NULL): array { $dir = $dir ?? dirname($this->getReflection()->getFileName()); $filePath = $dir . "/$moduleName.module.scss.json"; From acea2e201d115e91ca5341376a821360860bf541 Mon Sep 17 00:00:00 2001 From: Lukas Karlovsky Date: Tue, 18 Mar 2025 13:28:30 +0100 Subject: [PATCH 16/32] Change parameters to many parameter. --- src/Translatable/TranslatableControlTrait.php | 8 +++----- src/Translatable/TranslatableSluggableFormTrait.php | 12 +++++------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/Translatable/TranslatableControlTrait.php b/src/Translatable/TranslatableControlTrait.php index 678bb15..d36670a 100644 --- a/src/Translatable/TranslatableControlTrait.php +++ b/src/Translatable/TranslatableControlTrait.php @@ -43,11 +43,9 @@ public function addTranslation(Form $form, $name, $containerFactory) ->where('e.objectClass = :objectClass') ->andWhere('e.locale NOT IN (:locales)') ->andWhere('e.foreignKey = :foreignKey') - ->setParameters([ - 'objectClass' => get_class($entity), - 'locales' => $locales, - 'foreignKey' => $entity->getId() - ]) + ->setParameter('objectClass', get_class($entity)) + ->setParameter('locales', $locales) + ->setParameter('foreignKey', $entity->getId()) ->delete() ->getQuery() ->execute(); diff --git a/src/Translatable/TranslatableSluggableFormTrait.php b/src/Translatable/TranslatableSluggableFormTrait.php index 1f17c35..656f6a3 100644 --- a/src/Translatable/TranslatableSluggableFormTrait.php +++ b/src/Translatable/TranslatableSluggableFormTrait.php @@ -50,13 +50,11 @@ private function generateSlug(TranslatableEntityInterface $entity) ->andWhere('e.content = :content') ->andWhere('e.foreignKey != :foreignKey') ->andWhere('e.field = :field') - ->setParameters([ - 'locale' => $_locale, - 'objectClass' => get_class($entity), - 'content' => $slug, - 'foreignKey' => $entity->getId(), - 'field' => $slugField, - ]) + ->setParameter('locale', $_locale) + ->setParameter('objectClass', get_class($entity)) + ->setParameter('content', $slug) + ->setParameter('foreignKey', $entity->getId()) + ->setParameter('field', $slugField) ->getQuery() ->getSingleResult(); From f1c9c02cefe0ed44392151a59f9534f27312e463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Wed, 30 Apr 2025 09:33:48 +0200 Subject: [PATCH 17/32] improvements --- src/Filters/Image.php | 91 +++++++++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 33 deletions(-) diff --git a/src/Filters/Image.php b/src/Filters/Image.php index 8b5d9e7..3aeb78e 100644 --- a/src/Filters/Image.php +++ b/src/Filters/Image.php @@ -6,7 +6,6 @@ use ADT\Utils\FileSystem; use Exception; use Nette\Utils\ImageException; -use Nette\Utils\UnknownImageFileException; class Image { @@ -25,12 +24,14 @@ public function __construct(string $path, string $dir = 'thumbnails', int $multi IMAGETYPE_WEBP => 'webp' ]; + const ExtensionsToFormat = [ + 'webp' => IMAGETYPE_WEBP + ]; + /** - * @throws ImageException - * @throws UnknownImageFileException * @throws Exception */ - public function format(string $url, int $width, int $height, int $mode = \Nette\Utils\Image::FIT, int $format = IMAGETYPE_WEBP): string + public function format(string $url, int $width, int $height, int $mode = \Nette\Utils\Image::OrSmaller, int $format = IMAGETYPE_WEBP): string { $isRemoteUrl = $this->isRemoteUrl($url); @@ -45,46 +46,22 @@ public function format(string $url, int $width, int $height, int $mode = \Nette\ } else { $url = trim($url, '/'); - if (!file_exists($this->path . '/' . $url)) { return $url; } + $contents = file_get_contents($this->path . '/' . $url); list($urlWithoutExtension,) = $this->splitUrlOnLastDot($url); } - - $width = $width * $this->multiplier; $height = $height * $this->multiplier; - $newPath = $this->path . '/' . $this->dir; - $newFileName = $urlWithoutExtension . '_' . $width . '_' . $height . '_' . $mode . '.' . self::FormatToExtensions[$format]; - - // thumbnail already exists - if (file_exists($newPath . '/' . $newFileName)) { - goto end; - } - - FileSystem::createDirAtomically(dirname($newPath . '/' . $newFileName)); - - $prevErrorHandler = set_error_handler(function ($errno, $errstr) use (&$prevErrorHandler) { - if ($errno === E_USER_WARNING && $errstr === 'Nette\Utils\Image::fromFile(): gd-png: libpng warning: iCCP: known incorrect sRGB profile') { - return true; - } - return $prevErrorHandler ? $prevErrorHandler(...func_get_args()) : false; - }); - if ($isRemoteUrl) { - $image = \Nette\Utils\Image::fromString($contents); - } else { - $image = \Nette\Utils\Image::fromFile($this->path . '/' . $url); - } - set_error_handler($prevErrorHandler); - $image->resize($width, $height, $mode); - $image->save($newPath . '/' . $newFileName, 100, $format); + $ext = pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION); + $newFile = $this->dir . '/' . $urlWithoutExtension . '_' . $width . '_' . $height . '_' . $mode . '_' . $ext . '.' . self::FormatToExtensions[$format]; - end: + $this->createImage($contents, $width, $height, $mode, $format, $this->path . '/' . $newFile); - return '/' . $this->dir . '/' . $newFileName; + return $newFile; } private function isRemoteUrl(string $url): bool @@ -115,4 +92,52 @@ private function removeProtocol(string $url): string { return preg_replace("/^https?:\/\//", "", $url); } + + /** + * @throws ImageException + */ + public function createImageFromThumbnailUrl(string $url): void + { + $info = pathinfo($url); + $format = $info['extension']; + $fileinfo = pathinfo($info['filename']); + $segments = explode('_', $fileinfo['filename']); + $extension = array_pop($segments); + $mode = (int)array_pop($segments); + $height = (int)array_pop($segments); + $width = (int)array_pop($segments); + $originalFile = $info['dirname'] . '/' . implode('_', $segments) . '.' . $extension; + + $this->createImage(file_get_contents($this->path . '/' . $originalFile), $width, $height, $mode, static::ExtensionsToFormat[$format], $this->path . '/' . $this->dir . '/' . $url); + } + + /** + * @throws ImageException + * @throws Exception + */ + protected function createImage(string $contents, int $width, int $height, int $mode, int $format, string $newFile): void + { + // thumbnail already exists + if (file_exists($newFile)) { + return; + } + + FileSystem::createDirAtomically(dirname($newFile)); + + $prevErrorHandler = set_error_handler(function ($errno, $errstr) use (&$prevErrorHandler) { + if ($errno === E_USER_WARNING && $errstr === 'Nette\Utils\Image::fromString(): gd-png: libpng warning: iCCP: known incorrect sRGB profile') { + return true; + } + return $prevErrorHandler ? $prevErrorHandler(...func_get_args()) : false; + }); + $image = \Nette\Utils\Image::fromString($contents); + set_error_handler($prevErrorHandler); + $image->resize($width, $height, $mode); + $image->save($newFile, 100, $format); + } + + public function getPath(): string + { + return $this->path; + } } From c3aee2b5ffc08e9bb2f1282112ddc4740db2ed5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Wed, 30 Apr 2025 12:28:41 +0200 Subject: [PATCH 18/32] Update Image.php --- src/Filters/Image.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Filters/Image.php b/src/Filters/Image.php index 3aeb78e..c878e4e 100644 --- a/src/Filters/Image.php +++ b/src/Filters/Image.php @@ -61,7 +61,7 @@ public function format(string $url, int $width, int $height, int $mode = \Nette\ $this->createImage($contents, $width, $height, $mode, $format, $this->path . '/' . $newFile); - return $newFile; + return '/' . $newFile; } private function isRemoteUrl(string $url): bool From bca14eb64acb4cf565a089c995b03487cd0633f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Thu, 1 May 2025 09:50:55 +0200 Subject: [PATCH 19/32] Update Image.php --- src/Filters/Image.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Filters/Image.php b/src/Filters/Image.php index c878e4e..c437078 100644 --- a/src/Filters/Image.php +++ b/src/Filters/Image.php @@ -93,10 +93,7 @@ private function removeProtocol(string $url): string return preg_replace("/^https?:\/\//", "", $url); } - /** - * @throws ImageException - */ - public function createImageFromThumbnailUrl(string $url): void + public function createImageFromThumbnailUrl(string $url): bool { $info = pathinfo($url); $format = $info['extension']; @@ -108,7 +105,13 @@ public function createImageFromThumbnailUrl(string $url): void $width = (int)array_pop($segments); $originalFile = $info['dirname'] . '/' . implode('_', $segments) . '.' . $extension; + if (!file_exists($this->path . '/' . $originalFile)) { + return false; + } + $this->createImage(file_get_contents($this->path . '/' . $originalFile), $width, $height, $mode, static::ExtensionsToFormat[$format], $this->path . '/' . $this->dir . '/' . $url); + + return true; } /** From a3730f6b9622939af5bea049727cc3fe3cd36ea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Thu, 1 May 2025 09:51:48 +0200 Subject: [PATCH 20/32] Update Image.php --- src/Filters/Image.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Filters/Image.php b/src/Filters/Image.php index c437078..c670e4c 100644 --- a/src/Filters/Image.php +++ b/src/Filters/Image.php @@ -93,6 +93,9 @@ private function removeProtocol(string $url): string return preg_replace("/^https?:\/\//", "", $url); } + /** + * @throws ImageException + */ public function createImageFromThumbnailUrl(string $url): bool { $info = pathinfo($url); From b81c8a477a0febeaeee7fe8b378d8fb3f6212bcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Sat, 3 May 2025 12:01:36 +0200 Subject: [PATCH 21/32] Update Image.php --- src/Filters/Image.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Filters/Image.php b/src/Filters/Image.php index c670e4c..530af1e 100644 --- a/src/Filters/Image.php +++ b/src/Filters/Image.php @@ -146,4 +146,9 @@ public function getPath(): string { return $this->path; } + + public function getDir(): string + { + return $this->dir; + } } From 09b5a97ffea085b009232b825559cd59f3522580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Tue, 17 Jun 2025 08:06:40 +0200 Subject: [PATCH 22/32] Adds utility function to create DateTimeImmutable Adds a static method that attempts to create a DateTimeImmutable object from an array containing date and timezone information. Returns null if the input data is invalid or if an exception occurs during DateTimeImmutable creation. --- src/Utils.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/Utils.php diff --git a/src/Utils.php b/src/Utils.php new file mode 100644 index 0000000..41665b1 --- /dev/null +++ b/src/Utils.php @@ -0,0 +1,27 @@ + Date: Tue, 17 Jun 2025 08:08:50 +0200 Subject: [PATCH 23/32] Adds utility function for checking emptiness Adds a utility function that provides a more reliable way to determine if a variable is considered "empty". The native `empty()` function treats '0' and 0 as empty, which is often not the desired behavior. The new function `realEmpty()` addresses this by explicitly checking for these cases. --- src/Utils.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Utils.php b/src/Utils.php index 41665b1..4f00212 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -8,6 +8,11 @@ class Utils { + final static public function realEmpty($value): bool + { + return empty($value) && $value !== 0 && $value !== '0'; + } + final public static function getDateTimeFromArray(array|string $data): ?DateTimeImmutable { if (!isset($data['date'], $data['timezone'], $data['timezone_type'])) { From f7f017213a61928e3de83711ac41539c8e7d689f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Tue, 26 Aug 2025 07:30:34 +0200 Subject: [PATCH 24/32] Update Utils.php --- src/Utils.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Utils.php b/src/Utils.php index 4f00212..ca2c655 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -13,20 +13,20 @@ final static public function realEmpty($value): bool return empty($value) && $value !== 0 && $value !== '0'; } - final public static function getDateTimeFromArray(array|string $data): ?DateTimeImmutable + final public static function getDateTimeFromArray(mixed $data, bool $returnOriginalOnError = false) { if (!isset($data['date'], $data['timezone'], $data['timezone_type'])) { - return null; + return $returnOriginalOnError ? $data : null; } if (!is_string($data['date']) || !is_string($data['timezone']) || !is_int($data['timezone_type'])) { - return null; + return $returnOriginalOnError ? $data : null; } try { return new DateTimeImmutable($data['date'], new DateTimeZone($data['timezone'])); } catch (Exception) { - return null; + return $returnOriginalOnError ? $data : null; } } -} \ No newline at end of file +} From f60948f34593b285f9a83335a57427e888d56ded Mon Sep 17 00:00:00 2001 From: Lukas Karlovsky Date: Tue, 23 Sep 2025 16:41:43 +0200 Subject: [PATCH 25/32] Disable format animated gif. --- src/Filters/Image.php | 45 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/Filters/Image.php b/src/Filters/Image.php index 530af1e..1ab37c1 100644 --- a/src/Filters/Image.php +++ b/src/Filters/Image.php @@ -51,13 +51,17 @@ public function format(string $url, int $width, int $height, int $mode = \Nette\ } $contents = file_get_contents($this->path . '/' . $url); + if ($this->isAnimatedGif($contents)) { + return $url; + } + list($urlWithoutExtension,) = $this->splitUrlOnLastDot($url); } $width = $width * $this->multiplier; $height = $height * $this->multiplier; $ext = pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION); - $newFile = $this->dir . '/' . $urlWithoutExtension . '_' . $width . '_' . $height . '_' . $mode . '_' . $ext . '.' . self::FormatToExtensions[$format]; + $newFile = $this->dir . '/' . $urlWithoutExtension . '_' . $width . '_' . $height . '_' . $mode . '_' . $ext . '.' . self::FormatToExtensions[$format]; $this->createImage($contents, $width, $height, $mode, $format, $this->path . '/' . $newFile); @@ -75,6 +79,45 @@ private function isRemoteUrl(string $url): bool return false; } + /** + * Thanks to ZeBadger for original example, and Davide Gualano for pointing me to it + * Original at http://it.php.net/manual/en/function.imagecreatefromgif.php#59787 + **/ + private function isAnimatedGif($fileContents): bool + { + $raw = $fileContents; + + $offset = 0; + $frames = 0; + while ($frames < 2) + { + $where1 = strpos($raw, "\x00\x21\xF9\x04", $offset); + if ( $where1 === false ) + { + break; + } + else + { + $offset = $where1 + 1; + $where2 = strpos( $raw, "\x00\x2C", $offset ); + if ( $where2 === false ) + { + break; + } + else + { + if ( $where1 + 8 == $where2 ) + { + $frames ++; + } + $offset = $where2 + 1; + } + } + } + + return $frames > 1; + } + private function splitUrlOnLastDot(string $url): array { $lastDotPos = strrpos($url, '.'); From 4080b736cbcf1d8b45fd3041f532daef53b7a0eb Mon Sep 17 00:00:00 2001 From: Lukas Karlovsky Date: Mon, 6 Oct 2025 13:19:57 +0200 Subject: [PATCH 26/32] Fix warnings. --- src/Filters/Image.php | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/Filters/Image.php b/src/Filters/Image.php index 1ab37c1..7a272aa 100644 --- a/src/Filters/Image.php +++ b/src/Filters/Image.php @@ -142,19 +142,32 @@ private function removeProtocol(string $url): string public function createImageFromThumbnailUrl(string $url): bool { $info = pathinfo($url); + if (empty($info['extension'])) { + return false; + } $format = $info['extension']; - $fileinfo = pathinfo($info['filename']); - $segments = explode('_', $fileinfo['filename']); + if (!array_key_exists($format, static::ExtensionsToFormat)) { + return false; + } + + if (empty($info['filename'])) { + return false; + } + $fileInfo = pathinfo($info['filename']); + + if (empty($fileInfo['filename'])) { + return false; + } + $segments = explode('_', $fileInfo['filename']); + $extension = array_pop($segments); $mode = (int)array_pop($segments); $height = (int)array_pop($segments); $width = (int)array_pop($segments); $originalFile = $info['dirname'] . '/' . implode('_', $segments) . '.' . $extension; - if (!file_exists($this->path . '/' . $originalFile)) { return false; } - $this->createImage(file_get_contents($this->path . '/' . $originalFile), $width, $height, $mode, static::ExtensionsToFormat[$format], $this->path . '/' . $this->dir . '/' . $url); return true; From 1a451b9184b986c58c852a7793ece723c914fb5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Fri, 5 Dec 2025 15:17:20 +0100 Subject: [PATCH 27/32] Create LICENSE --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d862e03 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Apps Dev Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 765a1754dbb58aa22a97a95e888ccaf9fdd1c45f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Fri, 5 Dec 2025 15:17:59 +0100 Subject: [PATCH 28/32] Add license field to composer.json --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index fac40cf..0207ae4 100644 --- a/composer.json +++ b/composer.json @@ -5,6 +5,7 @@ "src/" ] }, + "license": "MIT", "require": { "nette/utils": "^2.0|^3.0|^4.0" }, From 31c7dc74c77bf91d89a07a82687df3de36202216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Thu, 8 Jan 2026 18:55:45 +0100 Subject: [PATCH 29/32] Update Image.php --- src/Filters/Image.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Filters/Image.php b/src/Filters/Image.php index 7a272aa..7e6f9b0 100644 --- a/src/Filters/Image.php +++ b/src/Filters/Image.php @@ -61,6 +61,10 @@ public function format(string $url, int $width, int $height, int $mode = \Nette\ $width = $width * $this->multiplier; $height = $height * $this->multiplier; $ext = pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION); + if ($ext === 'svg') { + return $url; + } + $newFile = $this->dir . '/' . $urlWithoutExtension . '_' . $width . '_' . $height . '_' . $mode . '_' . $ext . '.' . self::FormatToExtensions[$format]; $this->createImage($contents, $width, $height, $mode, $format, $this->path . '/' . $newFile); From 508d7d9de3e4f369967d842e6170ef453726559e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Thu, 8 Jan 2026 19:07:35 +0100 Subject: [PATCH 30/32] Update Image.php --- src/Filters/Image.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Filters/Image.php b/src/Filters/Image.php index 7e6f9b0..556a82d 100644 --- a/src/Filters/Image.php +++ b/src/Filters/Image.php @@ -33,13 +33,15 @@ public function __construct(string $path, string $dir = 'thumbnails', int $multi */ public function format(string $url, int $width, int $height, int $mode = \Nette\Utils\Image::OrSmaller, int $format = IMAGETYPE_WEBP): string { + $originalUrl = $url; + $isRemoteUrl = $this->isRemoteUrl($url); // original file does not exist if ($isRemoteUrl) { $contents = @file_get_contents($url); if (!$contents) { - return $url; + return $originalUrl; } list($urlWithoutExtension,) = $this->splitUrlOnLastDot($this->removeProtocol($url)); @@ -47,12 +49,12 @@ public function format(string $url, int $width, int $height, int $mode = \Nette\ } else { $url = trim($url, '/'); if (!file_exists($this->path . '/' . $url)) { - return $url; + return $originalUrl; } $contents = file_get_contents($this->path . '/' . $url); if ($this->isAnimatedGif($contents)) { - return $url; + return $originalUrl; } list($urlWithoutExtension,) = $this->splitUrlOnLastDot($url); @@ -62,7 +64,7 @@ public function format(string $url, int $width, int $height, int $mode = \Nette\ $height = $height * $this->multiplier; $ext = pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION); if ($ext === 'svg') { - return $url; + return $originalUrl; } $newFile = $this->dir . '/' . $urlWithoutExtension . '_' . $width . '_' . $height . '_' . $mode . '_' . $ext . '.' . self::FormatToExtensions[$format]; From 23705dccc9770920d70d9106dcb05dbf753d6467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Konvi=C4=8Dka?= Date: Tue, 7 Apr 2026 12:55:32 +0200 Subject: [PATCH 31/32] add components method --- src/JsComponents.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/JsComponents.php b/src/JsComponents.php index 12b9686..2e49c73 100644 --- a/src/JsComponents.php +++ b/src/JsComponents.php @@ -2,17 +2,25 @@ namespace ADT\Utils; +use Nette\Utils\Json; + class JsComponents { protected array $components = []; public function generateConfig(): string { - return json_encode($this->components); + return Json::encode($this->components); } public function setRecaptcha(string $siteKey): string { return $this->components['recaptcha']['siteKey'] = $siteKey; } + + public function setComponents(array $components): self + { + $this->components = array_merge($this->components, $components); + return $this; + } } From e4a912023fb31231b5a434ac237e480021350c80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Tue, 28 Apr 2026 07:51:28 +0200 Subject: [PATCH 32/32] Sanitize and truncate Guzzle error messages Prevent binary data and excessively long request/response bodies from being included in exception messages to ensure logs remain readable. --- src/Guzzle.php | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/Guzzle.php b/src/Guzzle.php index ada18ab..f9d72c0 100644 --- a/src/Guzzle.php +++ b/src/Guzzle.php @@ -10,6 +10,8 @@ class Guzzle { + private const MAX_BODY_LENGTH = 10000; + /** * @throws Throwable */ @@ -18,13 +20,39 @@ public static function handleException(Throwable $e): ?Exception if ($e instanceof GuzzleException) { $message = ''; if ($e instanceof ConnectException || $e instanceof RequestException) { - $message = "--- REQUEST ---\n" . Message::toString($e->getRequest()) . "\n --- RESPONSE ---\n"; + $message = "--- REQUEST ---\n" . self::sanitizeMessage(Message::toString($e->getRequest())) . "\n --- RESPONSE ---\n"; } - $message .= ($e instanceof RequestException && $e->getResponse() ? Message::toString($e->getResponse()) : $e->getMessage()); + $message .= ($e instanceof RequestException && $e->getResponse() ? self::sanitizeMessage(Message::toString($e->getResponse())) : $e->getMessage()); throw new Exception($message); } throw $e; } + + private static function sanitizeMessage(string $message): string + { + // Odstraneni binarnich dat (null byty apod.) + if (preg_match('/[^\x20-\x7E\x0A\x0D\t]/u', $message)) { + // Najdeme konec hlavicek (prazdny radek) + $headerEnd = strpos($message, "\r\n\r\n"); + if ($headerEnd === false) { + $headerEnd = strpos($message, "\n\n"); + } + + if ($headerEnd !== false) { + $headers = substr($message, 0, $headerEnd); + return $headers . "\n\n[binary data removed]"; + } + + return '[binary data removed]'; + } + + // Oriznuti prilis dlouhych textovych odpovedi + if (strlen($message) > self::MAX_BODY_LENGTH) { + return substr($message, 0, self::MAX_BODY_LENGTH) . "\n\n... [truncated, total " . strlen($message) . " bytes]"; + } + + return $message; + } }