From 6f06b36e50fea548593f15543790c230294f84dd Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 25 Nov 2024 09:12:03 +0100 Subject: [PATCH 1/3] Update API contracts for SHortUrl and Visit --- CHANGELOG.md | 5 +++-- src/ShortUrls/Model/ShortUrl.php | 5 +++++ src/Visits/Model/OrphanVisit.php | 10 +++++++--- src/Visits/Model/Visit.php | 14 ++++++++++++++ src/Visits/Model/VisitInterface.php | 2 ++ 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ffc374..ec4fe12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,10 @@ 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] +## [2.4.0] - 2024-11-25 ### Added -* *Nothing* +* Add support for PHP 8.4 +* Add support for Shlink 4.3 ### Changed * Switch to xdebug for code coverage reports, as pcov is not marking functions as covered diff --git a/src/ShortUrls/Model/ShortUrl.php b/src/ShortUrls/Model/ShortUrl.php index 938a792..2fef6b8 100644 --- a/src/ShortUrls/Model/ShortUrl.php +++ b/src/ShortUrls/Model/ShortUrl.php @@ -15,6 +15,9 @@ /** @deprecated Not returned by Shlink 4.0.0 */ public DeviceLongUrls|null $deviceLongUrls; + /** + * @param $hasRedirectRules - It's `null` for Shlink older than 4.3 + */ private function __construct( public string $shortCode, public string $shortUrl, @@ -24,6 +27,7 @@ private function __construct( public string|null $title, public bool $crawlable, public bool $forwardQuery, + public bool|null $hasRedirectRules, public array $tags, public ShortUrlMeta $meta, public VisitsSummary $visitsSummary, @@ -48,6 +52,7 @@ public static function fromArray(array $payload): self title: $payload['title'] ?? null, crawlable: $payload['crawlable'] ?? false, forwardQuery: $payload['forwardQuery'] ?? false, + hasRedirectRules: $payload['hasRedirectRules'] ?? null, tags: $payload['tags'] ?? [], meta: ShortUrlMeta::fromArray($payload['meta'] ?? []), visitsSummary: VisitsSummary::fromArrayWithFallback($payload['visitsSummary'] ?? [], $visitsCount), diff --git a/src/Visits/Model/OrphanVisit.php b/src/Visits/Model/OrphanVisit.php index 1620be5..ec2766c 100644 --- a/src/Visits/Model/OrphanVisit.php +++ b/src/Visits/Model/OrphanVisit.php @@ -8,7 +8,7 @@ final readonly class OrphanVisit implements VisitInterface { - private function __construct(private Visit $visit, private string $visitedUrl, private OrphanVisitType $type) + private function __construct(private Visit $visit, private OrphanVisitType $type) { } @@ -16,7 +16,6 @@ public static function fromArray(array $payload): self { return new self( visit: Visit::fromArray($payload), - visitedUrl: $payload['visitedUrl'] ?? '', type: OrphanVisitType::tryFrom($payload['type'] ?? '') ?? OrphanVisitType::REGULAR_NOT_FOUND, ); } @@ -48,7 +47,12 @@ public function location(): VisitLocation|null public function visitedUrl(): string { - return $this->visitedUrl; + return $this->visit->visitedUrl(); + } + + public function redirectUrl(): string|null + { + return $this->visit->redirectUrl(); } public function type(): OrphanVisitType diff --git a/src/Visits/Model/Visit.php b/src/Visits/Model/Visit.php index d9a3c92..f49afdc 100644 --- a/src/Visits/Model/Visit.php +++ b/src/Visits/Model/Visit.php @@ -14,6 +14,8 @@ private function __construct( private DateTimeInterface $date, private string $userAgent, private bool $potentialBot, + private string $visitedUrl, + private string|null $redirectUrl, private VisitLocation|null $location, ) { } @@ -26,6 +28,8 @@ public static function fromArray(array $payload): self date: DateTimeImmutable::createFromFormat(DateTimeInterface::ATOM, $payload['date']), userAgent: $payload['userAgent'] ?? '', potentialBot: $payload['potentialBot'] ?? false, + visitedUrl: $payload['visitedUrl'] ?? '', + redirectUrl: $payload['redirectUrl'] ?? null, location: isset($payload['visitLocation']) ? VisitLocation::fromArray($payload['visitLocation']) : null, ); } @@ -54,4 +58,14 @@ public function location(): VisitLocation|null { return $this->location; } + + public function visitedUrl(): string + { + return $this->visitedUrl; + } + + public function redirectUrl(): string|null + { + return $this->redirectUrl; + } } diff --git a/src/Visits/Model/VisitInterface.php b/src/Visits/Model/VisitInterface.php index afd0cc8..9fc7e9f 100644 --- a/src/Visits/Model/VisitInterface.php +++ b/src/Visits/Model/VisitInterface.php @@ -13,4 +13,6 @@ public function date(): DateTimeInterface; public function userAgent(): string; public function potentialBot(): bool; public function location(): VisitLocation|null; + public function visitedUrl(): string; + public function redirectUrl(): string|null; } From c8d51d1b992bfcdfe59848fa8597860acf333ec0 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 25 Nov 2024 09:18:22 +0100 Subject: [PATCH 2/3] Add Shlink 4.3 to integration tests pipeline --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f14ba1b..fd9f06d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: php-version: ['8.2', '8.3', '8.4'] - shlink-version: ['4.2', '4.1', '4.0', '3.7', '3.6', '3.5.4', '3.4.0', '3.3.2'] + shlink-version: ['4.3', '4.2', '4.1', '4.0', '3.7', '3.6', '3.5.4', '3.4.0', '3.3.2'] shlink-api-version: ['3'] steps: - name: Checkout code From 21810d5ac5b84c53866396eb1a65c3f9f8281d09 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 25 Nov 2024 09:18:37 +0100 Subject: [PATCH 3/3] Add support for new Shlink 4.3 features --- src/RedirectRules/Model/RedirectCondition.php | 10 ++++++++++ .../Model/RedirectConditionType.php | 2 ++ src/ShortUrls/Model/ShortUrlsFilter.php | 5 +++++ .../Model/RedirectConditionTest.php | 20 +++++++++++++++++++ test/ShortUrls/Model/ShortUrlsFilterTest.php | 1 + 5 files changed, 38 insertions(+) diff --git a/src/RedirectRules/Model/RedirectCondition.php b/src/RedirectRules/Model/RedirectCondition.php index ec59c58..b967a7f 100644 --- a/src/RedirectRules/Model/RedirectCondition.php +++ b/src/RedirectRules/Model/RedirectCondition.php @@ -41,6 +41,16 @@ public static function forIpAddress(string $ipAddressPattern): self return new self(RedirectConditionType::IP_ADDRESS, $ipAddressPattern); } + public static function forGeolocationCountryCode(string $countryCode): self + { + return new self(RedirectConditionType::GEOLOCATION_COUNTRY_CODE, $countryCode); + } + + public static function forGeolocationCityName(string $cityName): self + { + return new self(RedirectConditionType::GEOLOCATION_CITY_NAME, $cityName); + } + public static function fromArray(array $payload): self { $originalType = $payload['type'] ?? ''; diff --git a/src/RedirectRules/Model/RedirectConditionType.php b/src/RedirectRules/Model/RedirectConditionType.php index 27a2853..2f74255 100644 --- a/src/RedirectRules/Model/RedirectConditionType.php +++ b/src/RedirectRules/Model/RedirectConditionType.php @@ -8,5 +8,7 @@ enum RedirectConditionType: string case LANGUAGE = 'language'; case QUERY_PARAM = 'query-param'; case IP_ADDRESS = 'ip-address'; + case GEOLOCATION_COUNTRY_CODE = 'geolocation-country-code'; + case GEOLOCATION_CITY_NAME = 'geolocation-city-name'; case UNKNOWN = 'unknown'; } diff --git a/src/ShortUrls/Model/ShortUrlsFilter.php b/src/ShortUrls/Model/ShortUrlsFilter.php index 0ac9b00..6278d00 100644 --- a/src/ShortUrls/Model/ShortUrlsFilter.php +++ b/src/ShortUrls/Model/ShortUrlsFilter.php @@ -65,6 +65,11 @@ public function orderingDescBy(ShortUrlListOrderField $field): self return $this->cloneWithProp('orderBy', sprintf('%s-DESC', $field->value)); } + public function forDomain(string $domain): self + { + return $this->cloneWithProp('domain', $domain); + } + private function cloneWithProp(string $prop, mixed $value): self { $clone = new self($this->query); diff --git a/test/RedirectRules/Model/RedirectConditionTest.php b/test/RedirectRules/Model/RedirectConditionTest.php index 3891d4c..0b7da6d 100644 --- a/test/RedirectRules/Model/RedirectConditionTest.php +++ b/test/RedirectRules/Model/RedirectConditionTest.php @@ -85,4 +85,24 @@ public function forIpAddressCreatesExpectedCondition(): void self::assertEquals('192.168.1.100', $condition->matchValue); self::assertNull($condition->matchKey); } + + #[Test] + public function forGeolocationCountryCodeCreatesExpectedCondition(): void + { + $condition = RedirectCondition::forGeolocationCountryCode('US'); + + self::assertEquals(RedirectConditionType::GEOLOCATION_COUNTRY_CODE, $condition->type); + self::assertEquals('US', $condition->matchValue); + self::assertNull($condition->matchKey); + } + + #[Test] + public function forGeolocationCityNameCreatesExpectedCondition(): void + { + $condition = RedirectCondition::forGeolocationCityName('Los Angeles'); + + self::assertEquals(RedirectConditionType::GEOLOCATION_CITY_NAME, $condition->type); + self::assertEquals('Los Angeles', $condition->matchValue); + self::assertNull($condition->matchKey); + } } diff --git a/test/ShortUrls/Model/ShortUrlsFilterTest.php b/test/ShortUrls/Model/ShortUrlsFilterTest.php index a4f8328..8c1ca08 100644 --- a/test/ShortUrls/Model/ShortUrlsFilterTest.php +++ b/test/ShortUrls/Model/ShortUrlsFilterTest.php @@ -56,5 +56,6 @@ public static function providePayloads(): iterable fn () => ShortUrlsFilter::create()->excludingMaxVisitsReached()->excludingPastValidUntil(), ['excludeMaxVisitsReached' => 'true', 'excludePastValidUntil' => 'true'], ]; + yield [fn () => ShortUrlsFilter::create()->forDomain('s.test'), ['domain' => 's.test']]; } }