From e47c90c645e22abeddef4bded42f3e80dbbc104a Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 9 Jan 2022 21:02:23 +0100 Subject: [PATCH] Simplified how the custom slugs are processed, allowing more characters in the process --- composer.json | 1 - config/constants.php | 1 - .../src/Util/CocurSymfonySluggerBridge.php | 22 ------------------- .../src/Validation/ShortUrlInputFilter.php | 13 +++++------ module/Core/test/Model/ShortUrlMetaTest.php | 14 +++++++++++- 5 files changed, 18 insertions(+), 33 deletions(-) delete mode 100644 module/Core/src/Util/CocurSymfonySluggerBridge.php diff --git a/composer.json b/composer.json index 2522f158..f12e41fb 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,6 @@ "ext-pdo": "*", "akrabat/ip-address-middleware": "^2.1", "cakephp/chronos": "^2.3", - "cocur/slugify": "^4.0", "doctrine/migrations": "^3.3", "doctrine/orm": "^2.10", "endroid/qr-code": "^4.4", diff --git a/config/constants.php b/config/constants.php index 8171cd66..978964c5 100644 --- a/config/constants.php +++ b/config/constants.php @@ -12,7 +12,6 @@ const MIN_SHORT_CODES_LENGTH = 4; const DEFAULT_REDIRECT_STATUS_CODE = StatusCodeInterface::STATUS_FOUND; const DEFAULT_REDIRECT_CACHE_LIFETIME = 30; const LOCAL_LOCK_FACTORY = 'Shlinkio\Shlink\LocalLockFactory'; -const CUSTOM_SLUGS_REGEXP = '/[^\pL\pN._~]/u'; // Any unicode letter or number, plus ".", "_" and "~" chars const TITLE_TAG_VALUE = '/]*>(.*?)<\/title>/i'; // Matches the value inside an html title tag const DEFAULT_QR_CODE_SIZE = 300; const DEFAULT_QR_CODE_MARGIN = 0; diff --git a/module/Core/src/Util/CocurSymfonySluggerBridge.php b/module/Core/src/Util/CocurSymfonySluggerBridge.php deleted file mode 100644 index da60836e..00000000 --- a/module/Core/src/Util/CocurSymfonySluggerBridge.php +++ /dev/null @@ -1,22 +0,0 @@ -slugger->slugify($string, $separator)); - } -} diff --git a/module/Core/src/Validation/ShortUrlInputFilter.php b/module/Core/src/Validation/ShortUrlInputFilter.php index 2497f85d..96896e94 100644 --- a/module/Core/src/Validation/ShortUrlInputFilter.php +++ b/module/Core/src/Validation/ShortUrlInputFilter.php @@ -4,19 +4,18 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Validation; -use Cocur\Slugify\Slugify; use DateTime; use Laminas\Filter; use Laminas\InputFilter\Input; use Laminas\InputFilter\InputFilter; use Laminas\Validator; use Shlinkio\Shlink\Common\Validation; -use Shlinkio\Shlink\Core\Util\CocurSymfonySluggerBridge; use Shlinkio\Shlink\Rest\Entity\ApiKey; +use function is_string; +use function str_replace; use function substr; -use const Shlinkio\Shlink\CUSTOM_SLUGS_REGEXP; use const Shlinkio\Shlink\MIN_SHORT_CODES_LENGTH; class ShortUrlInputFilter extends InputFilter @@ -77,11 +76,9 @@ class ShortUrlInputFilter extends InputFilter // FIXME The only way to enforce the NotEmpty validator to be evaluated when the value is provided but it's // empty, is by using the deprecated setContinueIfEmpty $customSlug = $this->createInput(self::CUSTOM_SLUG, false)->setContinueIfEmpty(true); - $customSlug->getFilterChain()->attach(new Validation\SluggerFilter(new CocurSymfonySluggerBridge(new Slugify([ - 'regexp' => CUSTOM_SLUGS_REGEXP, - 'lowercase' => false, // We want to keep it case-sensitive - 'rulesets' => ['default'], - ])))); + $customSlug->getFilterChain()->attach(new Filter\Callback( + static fn (mixed $value) => is_string($value) ? str_replace([' ', '/'], ['-', ''], $value) : $value, + )); $customSlug->getValidatorChain()->attach(new Validator\NotEmpty([ Validator\NotEmpty::STRING, Validator\NotEmpty::SPACE, diff --git a/module/Core/test/Model/ShortUrlMetaTest.php b/module/Core/test/Model/ShortUrlMetaTest.php index 9a5eac72..ec2d314e 100644 --- a/module/Core/test/Model/ShortUrlMetaTest.php +++ b/module/Core/test/Model/ShortUrlMetaTest.php @@ -30,34 +30,43 @@ class ShortUrlMetaTest extends TestCase public function provideInvalidData(): iterable { + yield [[]]; yield [[ + ShortUrlInputFilter::LONG_URL => 'foo', ShortUrlInputFilter::VALID_SINCE => '', ShortUrlInputFilter::VALID_UNTIL => '', ShortUrlInputFilter::CUSTOM_SLUG => 'foobar', ShortUrlInputFilter::MAX_VISITS => 'invalid', ]]; yield [[ + ShortUrlInputFilter::LONG_URL => 'foo', ShortUrlInputFilter::VALID_SINCE => '2017', ShortUrlInputFilter::MAX_VISITS => 5, ]]; yield [[ + ShortUrlInputFilter::LONG_URL => 'foo', ShortUrlInputFilter::VALID_SINCE => new stdClass(), ShortUrlInputFilter::VALID_UNTIL => 'foo', ]]; yield [[ + ShortUrlInputFilter::LONG_URL => 'foo', ShortUrlInputFilter::VALID_UNTIL => 500, ShortUrlInputFilter::DOMAIN => 4, ]]; yield [[ + ShortUrlInputFilter::LONG_URL => 'foo', ShortUrlInputFilter::SHORT_CODE_LENGTH => 3, ]]; yield [[ + ShortUrlInputFilter::LONG_URL => 'foo', ShortUrlInputFilter::CUSTOM_SLUG => '/', ]]; yield [[ + ShortUrlInputFilter::LONG_URL => 'foo', ShortUrlInputFilter::CUSTOM_SLUG => '', ]]; yield [[ + ShortUrlInputFilter::LONG_URL => 'foo', ShortUrlInputFilter::CUSTOM_SLUG => ' ', ]]; yield [[ @@ -92,12 +101,15 @@ class ShortUrlMetaTest extends TestCase public function provideCustomSlugs(): iterable { + yield ['🔥', '🔥']; + yield ['🦣 🍅', '🦣-🍅']; yield ['foobar', 'foobar']; yield ['foo bar', 'foo-bar']; + yield ['foo bar baz', 'foo-bar-baz']; + yield ['foo bar-baz', 'foo-bar-baz']; yield ['wp-admin.php', 'wp-admin.php']; yield ['UPPER_lower', 'UPPER_lower']; yield ['more~url_special.chars', 'more~url_special.chars']; - yield ['äéñ', 'äen']; yield ['구글', '구글']; yield ['グーグル', 'グーグル']; yield ['谷歌', '谷歌'];