From 38302b18e755d3bedef0691a848264505ec3d3cb Mon Sep 17 00:00:00 2001 From: Alexander Stehlik Date: Fri, 12 May 2023 02:31:36 +0200 Subject: [PATCH] [FEATURE] Allow dictionaries with variable length an multibyte chars The Base62UrlKeyGenerator is now an BaseXUrlKeyGenerator depending on the length of the configured dictionary. There will be no more Uninitialized string offset warnings when using a dictionary with less than 62 chars. By using mb_strlen() and mb_substr() is should now also be possible to use a dictionary containing multiby characters. Thanks to @thomashohn for reporting the issue and providing the initial pull request. Closes: #29 --- .../UrlKeyGenerator/Base62UrlKeyGenerator.php | 22 ++++++--- .../Base62UrlKeyGeneratorTest.php | 45 +++++++++++-------- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/Classes/UrlKeyGenerator/Base62UrlKeyGenerator.php b/Classes/UrlKeyGenerator/Base62UrlKeyGenerator.php index eafe210..2af0b7e 100644 --- a/Classes/UrlKeyGenerator/Base62UrlKeyGenerator.php +++ b/Classes/UrlKeyGenerator/Base62UrlKeyGenerator.php @@ -20,7 +20,14 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; /** - * Contains utilities for creating tiny url keys and url hashes. + * Generates a key for a tinyurl using a configured dictionary. + * + * The dictionary is used as a base for encoding an integer (the UID of the tinyurl recordd) + * into a string. + * + * Opionally, a minimum length for the generated key can be configured. When the key generated from + * the dictionary is shorter than the configured minimum length, a random string is appended to the + * original key, separated by a dash. */ class Base62UrlKeyGenerator implements UrlKeyGenerator { @@ -85,21 +92,22 @@ public function injectGeneralUtility(GeneralUtilityWrapper $generalUtility): voi * Thanks to http://jeremygibbs.com/2012/01/16/how-to-make-a-url-shortener * * @param int $base10Integer The integer that will converted - * @param string $base62Dictionary the dictionary for generating the base62 integer + * @param string $baseXDictionary the dictionary for generating the baseX integer * * @return string A base62 encoded integer using a custom dictionary */ - protected function convertIntToBase62(int $base10Integer, string $base62Dictionary): string + protected function convertIntToBase62(int $base10Integer, string $baseXDictionary): string { - $base62Integer = ''; - $base = 62; + $baseXInteger = ''; + $base = mb_strlen($baseXDictionary); do { - $base62Integer = $base62Dictionary[$base10Integer % $base] . $base62Integer; + $dictionaryOffset = $base10Integer % $base; + $baseXInteger = mb_substr($baseXDictionary, $dictionaryOffset, 1) . $baseXInteger; $base10Integer = floor($base10Integer / $base); } while ($base10Integer > 0); - return $base62Integer; + return $baseXInteger; } /** diff --git a/Tests/Unit/UrlKeyGenerator/Base62UrlKeyGeneratorTest.php b/Tests/Unit/UrlKeyGenerator/Base62UrlKeyGeneratorTest.php index 73f756b..485dab6 100644 --- a/Tests/Unit/UrlKeyGenerator/Base62UrlKeyGeneratorTest.php +++ b/Tests/Unit/UrlKeyGenerator/Base62UrlKeyGeneratorTest.php @@ -26,20 +26,11 @@ */ class Base62UrlKeyGeneratorTest extends TestCase { - /** - * @var Base62UrlKeyGenerator - */ - protected $base62UrlKeyGenerator; + protected Base62UrlKeyGenerator $base62UrlKeyGenerator; - /** - * @var ExtensionConfiguration|MockObject - */ - protected $extensionConfigurationMock; + protected ExtensionConfiguration|MockObject $extensionConfigurationMock; - /** - * @var GeneralUtilityWrapper|MockObject - */ - protected $generalUtilityMock; + protected GeneralUtilityWrapper|MockObject $generalUtilityMock; protected function setUp(): void { @@ -51,7 +42,7 @@ protected function setUp(): void $this->base62UrlKeyGenerator->injectGeneralUtility($this->generalUtilityMock); } - public function testGenerateTinyurlKeyForUidEncodesIntegerIfNoMinimalLengthIsConfigured(): void + public function testGenerateTinyurlKeyForTinyUrlCreatesExpectedKey(): void { $this->extensionConfigurationMock->method('getBase62Dictionary') ->willReturn('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'); @@ -62,6 +53,17 @@ public function testGenerateTinyurlKeyForUidEncodesIntegerIfNoMinimalLengthIsCon self::assertSame('ud', $key); } + public function testGenerateTinyurlKeyForUidEncodesIntegerIfNoMinimalLengthIsConfigured(): void + { + $this->extensionConfigurationMock->method('getBase62Dictionary') + ->willReturn('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'); + + $tinyUrl = TinyUrl::createNew(); + $tinyUrl->persistPostProcessInsert(1243); + $key = $this->base62UrlKeyGenerator->generateTinyurlKeyForUid(1243); + self::assertSame('ud', $key); + } + public function testGenerateTinyurlKeyForUidFillsUpKeyUpToConfiguredMinimalLength(): void { $this->extensionConfigurationMock->method('getBase62Dictionary') @@ -74,9 +76,7 @@ public function testGenerateTinyurlKeyForUidFillsUpKeyUpToConfiguredMinimalLengt ->with(2) ->willReturn('ag'); - $tinyUrl = TinyUrl::createNew(); - $tinyUrl->persistPostProcessInsert(1243); - $key = $this->base62UrlKeyGenerator->generateTinyurlKeyForTinyUrl($tinyUrl); + $key = $this->base62UrlKeyGenerator->generateTinyurlKeyForUid(1243); self::assertSame('ud-ag', $key); } @@ -95,9 +95,18 @@ public function testGenerateTinyurlKeyForUidFillsUpKeyWithConfiguredMinimalRando ->with(2) ->willReturn('ag'); + $key = $this->base62UrlKeyGenerator->generateTinyurlKeyForUid(1243); + self::assertSame('ud-ag', $key); + } + + public function testGenerateTinyurlKeyForUidWorksWithShorterDictionary(): void + { + $this->extensionConfigurationMock->method('getBase62Dictionary') + ->willReturn('abcä'); + $tinyUrl = TinyUrl::createNew(); $tinyUrl->persistPostProcessInsert(1243); - $key = $this->base62UrlKeyGenerator->generateTinyurlKeyForTinyUrl($tinyUrl); - self::assertSame('ud-ag', $key); + $key = $this->base62UrlKeyGenerator->generateTinyurlKeyForUid(1243); + self::assertSame('baäbcä', $key); } }