Skip to content

Commit

Permalink
[FEATURE] Allow dictionaries with variable length an multibyte chars
Browse files Browse the repository at this point in the history
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
  • Loading branch information
astehlik committed May 12, 2023
1 parent 3efd846 commit 38302b1
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 25 deletions.
22 changes: 15 additions & 7 deletions Classes/UrlKeyGenerator/Base62UrlKeyGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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;
}

/**
Expand Down
45 changes: 27 additions & 18 deletions Tests/Unit/UrlKeyGenerator/Base62UrlKeyGeneratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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');
Expand All @@ -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')
Expand All @@ -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);
}

Expand All @@ -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);
}
}

0 comments on commit 38302b1

Please sign in to comment.