From 97c94f8fcca45f905227e44c9c305c9c2f3acf9e Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Tue, 5 Mar 2024 11:14:07 +0100 Subject: [PATCH 1/4] Document color and bgColor QR code query params --- CHANGELOG.md | 18 +++++++++++++++++- docs/swagger/paths/{shortCode}_qr-code.json | 20 ++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c59a0a531..2c2caa10a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org). +## [Unreleased] +### Added +* [#2041](https://github.com/shlinkio/shlink/issues/2041) Document `color` and `bgColor` params for the QR code route in the OAS docs. + +### Changed +* *Nothing* + +### Deprecated +* *Nothing* + +### Removed +* *Nothing* + +### Fixed +* *Nothing* + + ## [4.0.0] - 2024-03-03 ### Added * [#1914](https://github.com/shlinkio/shlink/issues/1914) Add new dynamic redirects engine based on rules. Rules are conditions checked against the visitor's request, and when matching, they can result in a redirect to a different long URL. @@ -56,7 +73,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this ### Changed * [#1968](https://github.com/shlinkio/shlink/issues/1968) Move migrations from `data` to `module/Core`. -* *Nothing* ### Deprecated * *Nothing* diff --git a/docs/swagger/paths/{shortCode}_qr-code.json b/docs/swagger/paths/{shortCode}_qr-code.json index ca66a079a..39be2dc9e 100644 --- a/docs/swagger/paths/{shortCode}_qr-code.json +++ b/docs/swagger/paths/{shortCode}_qr-code.json @@ -65,6 +65,26 @@ "enum": ["true", "false"], "default": "false" } + }, + { + "name": "color", + "in": "query", + "description": "The QR code foreground color. It should be an hex representation of a color, in 3 or 6 characters, optionally preceded by the \"#\" character.", + "required": false, + "schema": { + "type": "string", + "default": "#000000" + } + }, + { + "name": "bgColor", + "in": "query", + "description": "The QR code background color. It should be an hex representation of a color, in 3 or 6 characters, optionally preceded by the \"#\" character.", + "required": false, + "schema": { + "type": "string", + "default": "#ffffff" + } } ], "responses": { From 0bc741243042772b1522a791c6520ecc83e4e319 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Tue, 5 Mar 2024 15:09:44 +0100 Subject: [PATCH 2/4] Fix incorrect redirect condition type definiition --- docs/swagger/definitions/SetShortUrlRedirectRule.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/swagger/definitions/SetShortUrlRedirectRule.json b/docs/swagger/definitions/SetShortUrlRedirectRule.json index fd794712b..d17bedb37 100644 --- a/docs/swagger/definitions/SetShortUrlRedirectRule.json +++ b/docs/swagger/definitions/SetShortUrlRedirectRule.json @@ -15,7 +15,7 @@ "properties": { "type": { "type": "string", - "enum": ["device", "language", "query"], + "enum": ["device", "language", "query-param"], "description": "The type of the condition, which will condition the logic used to match it" }, "matchKey": { From be8cf56240c6b9cc1d0400cf5a49df3aaf46cc6e Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 7 Mar 2024 10:03:11 +0100 Subject: [PATCH 3/4] Ensure language redirect conditions do not match for too low quality accepted languages --- CHANGELOG.md | 5 +-- module/Core/functions/functions.php | 35 +++++++++++++------ .../RedirectRule/Entity/RedirectCondition.php | 2 +- module/Core/test-api/Action/RedirectTest.php | 8 ++++- .../Entity/RedirectConditionTest.php | 2 ++ 5 files changed, 38 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c2caa10a..9e8232650 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this ## [Unreleased] ### Added -* [#2041](https://github.com/shlinkio/shlink/issues/2041) Document `color` and `bgColor` params for the QR code route in the OAS docs. +* *Nothing* ### Changed * *Nothing* @@ -18,7 +18,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this * *Nothing* ### Fixed -* *Nothing* +* [#2041](https://github.com/shlinkio/shlink/issues/2041) Document missing `color` and `bgColor` params for the QR code route in the OAS docs. +* [#2043](https://github.com/shlinkio/shlink/issues/2043) Fix language redirect conditions matching too low quality accepted languages. ## [4.0.0] - 2024-03-03 diff --git a/module/Core/functions/functions.php b/module/Core/functions/functions.php index e910833ad..5ba45ac21 100644 --- a/module/Core/functions/functions.php +++ b/module/Core/functions/functions.php @@ -8,6 +8,7 @@ use Cake\Chronos\Chronos; use DateTimeInterface; use Doctrine\ORM\Mapping\Builder\FieldBuilder; +use GuzzleHttp\Psr7\Query; use Jaybizzle\CrawlerDetect\CrawlerDetect; use Laminas\Filter\Word\CamelCaseToSeparator; use Laminas\Filter\Word\CamelCaseToUnderscore; @@ -16,7 +17,6 @@ use Shlinkio\Shlink\Common\Util\DateRange; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlMode; -use function array_filter; use function array_keys; use function array_map; use function array_pad; @@ -27,6 +27,7 @@ use function is_array; use function print_r; use function Shlinkio\Shlink\Common\buildDateRange; +use function Shlinkio\Shlink\Core\ArrayUtils\map; use function sprintf; use function str_repeat; use function str_replace; @@ -85,16 +86,30 @@ function normalizeLocale(string $locale): string } /** + * Parse an accept-language-like pattern into a list of locales, optionally filtering out those which do not match a + * minimum quality + * * @param non-empty-string $acceptLanguage - * @return string[]; + * @param float<0, 1> $minQuality + * @return iterable; */ -function acceptLanguageToLocales(string $acceptLanguage): array -{ - $acceptLanguagesList = array_map(function (string $lang): string { - [$lang] = explode(';', $lang); // Discard everything after the semicolon (en-US;q=0.7) - return normalizeLocale($lang); - }, explode(',', $acceptLanguage)); - return array_filter($acceptLanguagesList, static fn (string $lang) => $lang !== '*'); +function acceptLanguageToLocales(string $acceptLanguage, float $minQuality = 0): iterable +{ + /** @var array{string, float|null}[] $acceptLanguagesList */ + $acceptLanguagesList = map(explode(',', $acceptLanguage), static function (string $lang): array { + // Split locale/language and quality (en-US;q=0.7) -> [en-US, q=0.7] + [$lang, $qualityString] = array_pad(explode(';', $lang), length: 2, value: ''); + $normalizedLang = normalizeLocale($lang); + $quality = Query::parse(trim($qualityString))['q'] ?? 1; + + return [$normalizedLang, (float) $quality]; + }); + + foreach ($acceptLanguagesList as [$lang, $quality]) { + if ($lang !== '*' && $quality >= $minQuality) { + yield $lang; + } + } } /** @@ -108,7 +123,7 @@ function acceptLanguageToLocales(string $acceptLanguage): array */ function splitLocale(string $locale): array { - return array_pad(explode('-', $locale), 2, null); + return array_pad(explode('-', $locale), length: 2, value: null); } function getOptionalIntFromInputFilter(InputFilter $inputFilter, string $fieldName): ?int diff --git a/module/Core/src/RedirectRule/Entity/RedirectCondition.php b/module/Core/src/RedirectRule/Entity/RedirectCondition.php index 291237332..5e6df4946 100644 --- a/module/Core/src/RedirectRule/Entity/RedirectCondition.php +++ b/module/Core/src/RedirectRule/Entity/RedirectCondition.php @@ -77,7 +77,7 @@ private function matchesLanguage(ServerRequestInterface $request): bool return false; } - $acceptedLanguages = acceptLanguageToLocales($acceptLanguage); + $acceptedLanguages = acceptLanguageToLocales($acceptLanguage, minQuality: 0.9); [$matchLanguage, $matchCountryCode] = splitLocale(normalizeLocale($this->matchValue)); return some( diff --git a/module/Core/test-api/Action/RedirectTest.php b/module/Core/test-api/Action/RedirectTest.php index cb623edca..e8cf211a2 100644 --- a/module/Core/test-api/Action/RedirectTest.php +++ b/module/Core/test-api/Action/RedirectTest.php @@ -75,9 +75,15 @@ public static function provideUserAgents(): iterable ]; yield 'rule: complex matching accept language' => [ [ - RequestOptions::HEADERS => ['Accept-Language' => 'fr-FR, es;q=08, en;q=0.5, *;q=0.2'], + RequestOptions::HEADERS => ['Accept-Language' => 'fr-FR, es;q=0.9, en;q=0.9, *;q=0.2'], ], 'https://example.com/only-english', ]; + yield 'rule: too low quality accept language' => [ + [ + RequestOptions::HEADERS => ['Accept-Language' => 'fr-FR, es;q=0.8, en;q=0.5, *;q=0.2'], + ], + 'https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/', + ]; } } diff --git a/module/Core/test/RedirectRule/Entity/RedirectConditionTest.php b/module/Core/test/RedirectRule/Entity/RedirectConditionTest.php index eaea4c25e..a8ab2a167 100644 --- a/module/Core/test/RedirectRule/Entity/RedirectConditionTest.php +++ b/module/Core/test/RedirectRule/Entity/RedirectConditionTest.php @@ -35,6 +35,8 @@ public function matchesQueryParams(string $param, string $value, bool $expectedR #[TestWith(['es, en,fr', 'en', true])] // multiple languages match #[TestWith(['es, en-US,fr', 'EN', true])] // multiple locales match #[TestWith(['es_ES', 'es-ES', true])] // single locale match + #[TestWith(['en-US,es-ES;q=0.6', 'es-ES', false])] // too low quality + #[TestWith(['en-US,es-ES;q=0.9', 'es-ES', true])] // quality high enough #[TestWith(['en-UK', 'en-uk', true])] // different casing match #[TestWith(['en-UK', 'en', true])] // only lang #[TestWith(['es-AR', 'en', false])] // different only lang From e244b2dc514661d0a6b2852eaa7b105949ad8cd1 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 8 Mar 2024 08:56:55 +0100 Subject: [PATCH 4/4] Add v4.0.1 to changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e8232650..c25cd5b1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org). -## [Unreleased] +## [4.0.1] - 2024-03-08 ### Added * *Nothing*