diff --git a/UPGRADE.md b/UPGRADE.md index e8390213..07862ab7 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -16,6 +16,9 @@ If you want to keep previous behavior, pass `QR_CODE_FOR_DISABLED_SHORT_URLS=false` or the equivalent configuration option. * Long URL title resolution is now enabled by default. You can still disable it by passing `AUTO_RESOLVE_TITLES=false` or the equivalent configuration option. * Shlink no longer allows to opt-in for long URL verification. Long URLs are unconditionally considered correct during short URL creation/edition. +* Device long URLs have been migrated to the new Dynamic rule-based redirects system. + All existing short URLs which were using device long URLs will be automatically migrated and continue working as expected, but the API surface has changed. + If you use shlink-web-client and rely on this feature when creating/updating short URLs, **DO NOT UPDATE YET**. Support for dynamic rule-based redirects will be added to shlink-web-client soon, in v4.1.0 ### Changes in REST API @@ -34,6 +37,7 @@ * Endpoints previously returning props like `"visitsCount": {number}` no longer do it. There should be an alternative `"visitsSummary": {}` object with the amount nested on it. * It is no longer possible to order the short URLs list with `orderBy=visitsCount-ASC`/`orderBy=visitsCount-DESC`. Use `orderBy=visits-ASC`/`orderBy=visits-DESC` instead. * It is no longer possible to get tags with stats using `GET /tags?withStats=true`. Use `GET /tags/stats` endpoint instead. +* The `deviceLongUrls` are ignored when calling `POST /short-urls` or `PATCH /short-urls/{shortCode}`. These should now be configured as dynamic rule-based redirects via `POST /short-urls/{shortCode}/redirect-rules`. ### Changes in Docker image diff --git a/docs/async-api/async-api.json b/docs/async-api/async-api.json index d3177b98..7cd838a8 100644 --- a/docs/async-api/async-api.json +++ b/docs/async-api/async-api.json @@ -111,9 +111,6 @@ "type": "string", "description": "The original long URL." }, - "deviceLongUrls": { - "$ref": "#/components/schemas/DeviceLongUrls" - }, "dateCreated": { "type": "string", "format": "date-time", @@ -150,11 +147,6 @@ "shortCode": "12C18", "shortUrl": "https://s.test/12C18", "longUrl": "https://store.steampowered.com", - "deviceLongUrls": { - "android": "https://store.steampowered.com/android", - "ios": "https://store.steampowered.com/ios", - "desktop": null - }, "dateCreated": "2016-08-21T20:34:16+02:00", "visitsSummary": { "total": 328, @@ -218,24 +210,6 @@ } } }, - "DeviceLongUrls": { - "type": "object", - "required": ["android", "ios", "desktop"], - "properties": { - "android": { - "description": "The long URL to redirect to when the short URL is visited from a device running Android", - "type": "string" - }, - "ios": { - "description": "The long URL to redirect to when the short URL is visited from a device running iOS", - "type": "string" - }, - "desktop": { - "description": "The long URL to redirect to when the short URL is visited from a desktop browser", - "type": "string" - } - } - }, "Visit": { "type": "object", "properties": { diff --git a/docs/swagger/definitions/DeviceLongUrls.json b/docs/swagger/definitions/DeviceLongUrls.json deleted file mode 100644 index 0e8719db..00000000 --- a/docs/swagger/definitions/DeviceLongUrls.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "type": "object", - "properties": { - "android": { - "description": "The long URL to redirect to when the short URL is visited from a device running Android", - "type": ["string"] - }, - "ios": { - "description": "The long URL to redirect to when the short URL is visited from a device running iOS", - "type": ["string"] - }, - "desktop": { - "description": "The long URL to redirect to when the short URL is visited from a desktop browser", - "type": ["string"] - } - } -} diff --git a/docs/swagger/definitions/DeviceLongUrlsEdit.json b/docs/swagger/definitions/DeviceLongUrlsEdit.json deleted file mode 100644 index f1ff255f..00000000 --- a/docs/swagger/definitions/DeviceLongUrlsEdit.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "type": "object", - "allOf": [{ - "$ref": "./DeviceLongUrls.json" - }], - "properties": { - "android": { - "type": ["null"] - }, - "ios": { - "type": ["null"] - }, - "desktop": { - "type": ["null"] - } - } -} diff --git a/docs/swagger/definitions/DeviceLongUrlsResp.json b/docs/swagger/definitions/DeviceLongUrlsResp.json deleted file mode 100644 index 95724581..00000000 --- a/docs/swagger/definitions/DeviceLongUrlsResp.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "object", - "required": ["android", "ios", "desktop"], - "allOf": [{ - "$ref": "./DeviceLongUrlsEdit.json" - }] -} diff --git a/docs/swagger/definitions/ShortUrl.json b/docs/swagger/definitions/ShortUrl.json index 8a420e9b..1535b65f 100644 --- a/docs/swagger/definitions/ShortUrl.json +++ b/docs/swagger/definitions/ShortUrl.json @@ -4,7 +4,6 @@ "shortCode", "shortUrl", "longUrl", - "deviceLongUrls", "dateCreated", "visitsSummary", "tags", @@ -27,9 +26,6 @@ "type": "string", "description": "The original long URL." }, - "deviceLongUrls": { - "$ref": "./DeviceLongUrlsResp.json" - }, "dateCreated": { "type": "string", "format": "date-time", diff --git a/docs/swagger/definitions/ShortUrlEdition.json b/docs/swagger/definitions/ShortUrlEdition.json index baef4f52..edd4c639 100644 --- a/docs/swagger/definitions/ShortUrlEdition.json +++ b/docs/swagger/definitions/ShortUrlEdition.json @@ -5,9 +5,6 @@ "description": "The long URL this short URL will redirect to", "type": "string" }, - "deviceLongUrls": { - "$ref": "./DeviceLongUrlsEdit.json" - }, "validSince": { "description": "The date (in ISO-8601 format) from which this short code will be valid", "type": ["string", "null"] diff --git a/docs/swagger/paths/v1_short-urls.json b/docs/swagger/paths/v1_short-urls.json index c9bbe68a..150eebc0 100644 --- a/docs/swagger/paths/v1_short-urls.json +++ b/docs/swagger/paths/v1_short-urls.json @@ -163,11 +163,6 @@ "shortCode": "12C18", "shortUrl": "https://s.test/12C18", "longUrl": "https://store.steampowered.com", - "deviceLongUrls": { - "android": null, - "ios": null, - "desktop": null - }, "dateCreated": "2016-08-21T20:34:16+02:00", "visitsSummary": { "total": 328, @@ -191,11 +186,6 @@ "shortCode": "12Kb3", "shortUrl": "https://s.test/12Kb3", "longUrl": "https://shlink.io", - "deviceLongUrls": { - "android": null, - "ios": "https://shlink.io/ios", - "desktop": null - }, "dateCreated": "2016-05-01T20:34:16+02:00", "visitsSummary": { "total": 1029, @@ -218,11 +208,6 @@ "shortCode": "123bA", "shortUrl": "https://example.com/123bA", "longUrl": "https://www.google.com", - "deviceLongUrls": { - "android": null, - "ios": null, - "desktop": null - }, "dateCreated": "2015-10-01T20:34:16+02:00", "visitsSummary": { "total": 25, @@ -296,9 +281,6 @@ "type": "object", "required": ["longUrl"], "properties": { - "deviceLongUrls": { - "$ref": "../definitions/DeviceLongUrls.json" - }, "customSlug": { "description": "A unique custom slug to be used instead of the generated short code", "type": "string" @@ -338,11 +320,6 @@ "shortCode": "12C18", "shortUrl": "https://s.test/12C18", "longUrl": "https://store.steampowered.com", - "deviceLongUrls": { - "android": null, - "ios": null, - "desktop": null - }, "dateCreated": "2016-08-21T20:34:16+02:00", "visitsSummary": { "total": 0, diff --git a/docs/swagger/paths/v1_short-urls_shorten.json b/docs/swagger/paths/v1_short-urls_shorten.json index 5c16482c..1136aca1 100644 --- a/docs/swagger/paths/v1_short-urls_shorten.json +++ b/docs/swagger/paths/v1_short-urls_shorten.json @@ -53,11 +53,6 @@ }, "example": { "longUrl": "https://github.com/shlinkio/shlink", - "deviceLongUrls": { - "android": null, - "ios": null, - "desktop": null - }, "shortUrl": "https://s.test/abc123", "shortCode": "abc123", "dateCreated": "2016-08-21T20:34:16+02:00", diff --git a/docs/swagger/paths/v1_short-urls_{shortCode}.json b/docs/swagger/paths/v1_short-urls_{shortCode}.json index 408d166c..120dc43e 100644 --- a/docs/swagger/paths/v1_short-urls_{shortCode}.json +++ b/docs/swagger/paths/v1_short-urls_{shortCode}.json @@ -34,11 +34,6 @@ "shortCode": "12Kb3", "shortUrl": "https://s.test/12Kb3", "longUrl": "https://shlink.io", - "deviceLongUrls": { - "android": null, - "ios": null, - "desktop": null - }, "dateCreated": "2016-05-01T20:34:16+02:00", "visitsSummary": { "total": 1029, @@ -155,11 +150,6 @@ "shortCode": "12Kb3", "shortUrl": "https://s.test/12Kb3", "longUrl": "https://shlink.io", - "deviceLongUrls": { - "android": "https://shlink.io/android", - "ios": null, - "desktop": null - }, "dateCreated": "2016-05-01T20:34:16+02:00", "visitsSummary": { "total": 1029, diff --git a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.ShortUrl.Entity.DeviceLongUrl.php b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.ShortUrl.Entity.DeviceLongUrl.php deleted file mode 100644 index 1e84a292..00000000 --- a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.ShortUrl.Entity.DeviceLongUrl.php +++ /dev/null @@ -1,41 +0,0 @@ -setTable(determineTableName('device_long_urls', $emConfig)); - - $builder->createField('id', Types::BIGINT) - ->columnName('id') - ->makePrimaryKey() - ->generatedValue('IDENTITY') - ->option('unsigned', true) - ->build(); - - (new FieldBuilder($builder, [ - 'fieldName' => 'deviceType', - 'type' => Types::STRING, - 'enumType' => DeviceType::class, - ]))->columnName('device_type') - ->length(255) - ->build(); - - fieldWithUtf8Charset($builder->createField('longUrl', Types::TEXT), $emConfig) - ->columnName('long_url') - ->length(2048) - ->build(); - - $builder->createManyToOne('shortUrl', ShortUrl\Entity\ShortUrl::class) - ->addJoinColumn('short_url_id', 'id', nullable: false, onDelete: 'CASCADE') - ->build(); -}; diff --git a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.ShortUrl.Entity.ShortUrl.php b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.ShortUrl.Entity.ShortUrl.php index ff933b8c..358ee6bd 100644 --- a/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.ShortUrl.Entity.ShortUrl.php +++ b/module/Core/config/entities-mappings/Shlinkio.Shlink.Core.ShortUrl.Entity.ShortUrl.php @@ -67,13 +67,6 @@ return static function (ClassMetadata $metadata, array $emConfig): void { ->fetchExtraLazy() ->build(); - $builder->createOneToMany('deviceLongUrls', ShortUrl\Entity\DeviceLongUrl::class) - ->mappedBy('shortUrl') - ->cascadePersist() - ->orphanRemoval() - ->setIndexBy('deviceType') - ->build(); - $builder->createManyToMany('tags', Tag\Entity\Tag::class) ->setJoinTable(determineTableName('short_urls_in_tags', $emConfig)) ->addInverseJoinColumn('tag_id', 'id', onDelete: 'CASCADE') diff --git a/module/Core/src/RedirectRule/Entity/RedirectCondition.php b/module/Core/src/RedirectRule/Entity/RedirectCondition.php index babbbe67..9505e81d 100644 --- a/module/Core/src/RedirectRule/Entity/RedirectCondition.php +++ b/module/Core/src/RedirectRule/Entity/RedirectCondition.php @@ -4,6 +4,7 @@ namespace Shlinkio\Shlink\Core\RedirectRule\Entity; use Psr\Http\Message\ServerRequestInterface; use Shlinkio\Shlink\Common\Entity\AbstractEntity; +use Shlinkio\Shlink\Core\Model\DeviceType; use Shlinkio\Shlink\Core\RedirectRule\Model\RedirectConditionType; use function Shlinkio\Shlink\Core\acceptLanguageToLocales; @@ -11,6 +12,7 @@ use function Shlinkio\Shlink\Core\ArrayUtils\some; use function Shlinkio\Shlink\Core\normalizeLocale; use function Shlinkio\Shlink\Core\splitLocale; use function sprintf; +use function strtolower; use function trim; class RedirectCondition extends AbstractEntity @@ -39,6 +41,14 @@ class RedirectCondition extends AbstractEntity return new self($name, $type, $language); } + public static function forDevice(DeviceType $device): self + { + $type = RedirectConditionType::DEVICE; + $name = sprintf('%s-%s', $type->value, $device->value); + + return new self($name, $type, $device->value); + } + /** * Tells if this condition matches provided request */ @@ -47,6 +57,7 @@ class RedirectCondition extends AbstractEntity return match ($this->type) { RedirectConditionType::QUERY_PARAM => $this->matchesQueryParam($request), RedirectConditionType::LANGUAGE => $this->matchesLanguage($request), + RedirectConditionType::DEVICE => $this->matchesDevice($request), }; } @@ -81,4 +92,10 @@ class RedirectCondition extends AbstractEntity }, ); } + + private function matchesDevice(ServerRequestInterface $request): bool + { + $device = DeviceType::matchFromUserAgent($request->getHeaderLine('User-Agent')); + return $device !== null && $device->value === strtolower($this->matchValue); + } } diff --git a/module/Core/src/RedirectRule/ShortUrlRedirectionResolver.php b/module/Core/src/RedirectRule/ShortUrlRedirectionResolver.php index b916d78b..0f811600 100644 --- a/module/Core/src/RedirectRule/ShortUrlRedirectionResolver.php +++ b/module/Core/src/RedirectRule/ShortUrlRedirectionResolver.php @@ -4,7 +4,6 @@ namespace Shlinkio\Shlink\Core\RedirectRule; use Doctrine\ORM\EntityManagerInterface; use Psr\Http\Message\ServerRequestInterface; -use Shlinkio\Shlink\Core\Model\DeviceType; use Shlinkio\Shlink\Core\RedirectRule\Entity\ShortUrlRedirectRule; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; @@ -27,7 +26,6 @@ readonly class ShortUrlRedirectionResolver implements ShortUrlRedirectionResolve } } - $device = DeviceType::matchFromUserAgent($request->getHeaderLine('User-Agent')); - return $shortUrl->longUrlForDevice($device); + return $shortUrl->getLongUrl(); } } diff --git a/module/Core/src/ShortUrl/Entity/DeviceLongUrl.php b/module/Core/src/ShortUrl/Entity/DeviceLongUrl.php deleted file mode 100644 index 668741e8..00000000 --- a/module/Core/src/ShortUrl/Entity/DeviceLongUrl.php +++ /dev/null @@ -1,34 +0,0 @@ -deviceType, $pair->longUrl); - } - - public function longUrl(): string - { - return $this->longUrl; - } - - public function updateLongUrl(string $longUrl): void - { - $this->longUrl = $longUrl; - } -} diff --git a/module/Core/src/ShortUrl/Entity/ShortUrl.php b/module/Core/src/ShortUrl/Entity/ShortUrl.php index 411e7bb1..8a577205 100644 --- a/module/Core/src/ShortUrl/Entity/ShortUrl.php +++ b/module/Core/src/ShortUrl/Entity/ShortUrl.php @@ -12,8 +12,6 @@ use Doctrine\Common\Collections\Selectable; use Shlinkio\Shlink\Common\Entity\AbstractEntity; use Shlinkio\Shlink\Core\Domain\Entity\Domain; use Shlinkio\Shlink\Core\Exception\ShortCodeCannotBeRegeneratedException; -use Shlinkio\Shlink\Core\Model\DeviceType; -use Shlinkio\Shlink\Core\ShortUrl\Model\DeviceLongUrlPair; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlEdition; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlMode; @@ -26,10 +24,7 @@ use Shlinkio\Shlink\Core\Visit\Model\VisitType; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use Shlinkio\Shlink\Rest\Entity\ApiKey; -use function array_fill_keys; -use function array_map; use function count; -use function Shlinkio\Shlink\Core\enumValues; use function Shlinkio\Shlink\Core\generateRandomShortCode; use function Shlinkio\Shlink\Core\normalizeDate; use function Shlinkio\Shlink\Core\normalizeOptionalDate; @@ -42,8 +37,6 @@ class ShortUrl extends AbstractEntity private Chronos $dateCreated; /** @var Collection & Selectable */ private Collection & Selectable $visits; - /** @var Collection */ - private Collection $deviceLongUrls; /** @var Collection */ private Collection $tags; private ?Chronos $validSince = null; @@ -91,10 +84,6 @@ class ShortUrl extends AbstractEntity $instance->longUrl = $creation->getLongUrl(); $instance->dateCreated = Chronos::now(); $instance->visits = new ArrayCollection(); - $instance->deviceLongUrls = new ArrayCollection(array_map( - fn (DeviceLongUrlPair $pair) => DeviceLongUrl::fromShortUrlAndPair($instance, $pair), - $creation->deviceLongUrls, - )); $instance->tags = $relationResolver->resolveTags($creation->tags); $instance->validSince = $creation->validSince; $instance->validUntil = $creation->validUntil; @@ -177,21 +166,6 @@ class ShortUrl extends AbstractEntity if ($shortUrlEdit->forwardQueryWasProvided()) { $this->forwardQuery = $shortUrlEdit->forwardQuery; } - - // Update device long URLs, removing, editing or creating where appropriate - foreach ($shortUrlEdit->devicesToRemove as $deviceType) { - $this->deviceLongUrls->remove($deviceType->value); - } - foreach ($shortUrlEdit->deviceLongUrls as $deviceLongUrlPair) { - $key = $deviceLongUrlPair->deviceType->value; - $deviceLongUrl = $this->deviceLongUrls->get($key); - - if ($deviceLongUrl !== null) { - $deviceLongUrl->updateLongUrl($deviceLongUrlPair->longUrl); - } else { - $this->deviceLongUrls->set($key, DeviceLongUrl::fromShortUrlAndPair($this, $deviceLongUrlPair)); - } - } } public function getLongUrl(): string @@ -199,12 +173,6 @@ class ShortUrl extends AbstractEntity return $this->longUrl; } - public function longUrlForDevice(?DeviceType $deviceType): string - { - $deviceLongUrl = $deviceType === null ? null : $this->deviceLongUrls->get($deviceType->value); - return $deviceLongUrl?->longUrl() ?? $this->longUrl; - } - public function getShortCode(): string { return $this->shortCode; @@ -332,14 +300,4 @@ class ShortUrl extends AbstractEntity return true; } - - public function deviceLongUrls(): array - { - $data = array_fill_keys(enumValues(DeviceType::class), null); - foreach ($this->deviceLongUrls as $deviceUrl) { - $data[$deviceUrl->deviceType->value] = $deviceUrl->longUrl(); - } - - return $data; - } } diff --git a/module/Core/src/ShortUrl/Model/DeviceLongUrlPair.php b/module/Core/src/ShortUrl/Model/DeviceLongUrlPair.php deleted file mode 100644 index a48c666b..00000000 --- a/module/Core/src/ShortUrl/Model/DeviceLongUrlPair.php +++ /dev/null @@ -1,45 +0,0 @@ - $map - * @return array{array, DeviceType[]} - */ - public static function fromMapToChangeSet(array $map): array - { - $pairsToKeep = []; - $deviceTypesToRemove = []; - - foreach ($map as $deviceType => $longUrl) { - if ($longUrl === null) { - $deviceTypesToRemove[] = DeviceType::from($deviceType); - } else { - $pairsToKeep[$deviceType] = self::fromRawTypeAndLongUrl($deviceType, $longUrl); - } - } - - return [$pairsToKeep, $deviceTypesToRemove]; - } -} diff --git a/module/Core/src/ShortUrl/Model/ShortUrlCreation.php b/module/Core/src/ShortUrl/Model/ShortUrlCreation.php index 4d22a8be..b0c87f99 100644 --- a/module/Core/src/ShortUrl/Model/ShortUrlCreation.php +++ b/module/Core/src/ShortUrl/Model/ShortUrlCreation.php @@ -22,12 +22,10 @@ final readonly class ShortUrlCreation implements TitleResolutionModelInterface { /** * @param string[] $tags - * @param DeviceLongUrlPair[] $deviceLongUrls */ private function __construct( public string $longUrl, public ShortUrlMode $shortUrlMode, - public array $deviceLongUrls = [], public ?Chronos $validSince = null, public ?Chronos $validUntil = null, public ?string $customSlug = null, @@ -55,14 +53,9 @@ final readonly class ShortUrlCreation implements TitleResolutionModelInterface throw ValidationException::fromInputFilter($inputFilter); } - [$deviceLongUrls] = DeviceLongUrlPair::fromMapToChangeSet( - $inputFilter->getValue(ShortUrlInputFilter::DEVICE_LONG_URLS) ?? [], - ); - return new self( longUrl: $inputFilter->getValue(ShortUrlInputFilter::LONG_URL), shortUrlMode: $options->mode, - deviceLongUrls: $deviceLongUrls, validSince: normalizeOptionalDate($inputFilter->getValue(ShortUrlInputFilter::VALID_SINCE)), validUntil: normalizeOptionalDate($inputFilter->getValue(ShortUrlInputFilter::VALID_UNTIL)), customSlug: $inputFilter->getValue(ShortUrlInputFilter::CUSTOM_SLUG), @@ -87,7 +80,6 @@ final readonly class ShortUrlCreation implements TitleResolutionModelInterface return new self( longUrl: $this->longUrl, shortUrlMode: $this->shortUrlMode, - deviceLongUrls: $this->deviceLongUrls, validSince: $this->validSince, validUntil: $this->validUntil, customSlug: $this->customSlug, diff --git a/module/Core/src/ShortUrl/Model/ShortUrlEdition.php b/module/Core/src/ShortUrl/Model/ShortUrlEdition.php index 2502331a..36a99f5f 100644 --- a/module/Core/src/ShortUrl/Model/ShortUrlEdition.php +++ b/module/Core/src/ShortUrl/Model/ShortUrlEdition.php @@ -6,7 +6,6 @@ namespace Shlinkio\Shlink\Core\ShortUrl\Model; use Cake\Chronos\Chronos; use Shlinkio\Shlink\Core\Exception\ValidationException; -use Shlinkio\Shlink\Core\Model\DeviceType; use Shlinkio\Shlink\Core\ShortUrl\Helper\TitleResolutionModelInterface; use Shlinkio\Shlink\Core\ShortUrl\Model\Validation\ShortUrlInputFilter; @@ -19,14 +18,10 @@ final readonly class ShortUrlEdition implements TitleResolutionModelInterface { /** * @param string[] $tags - * @param DeviceLongUrlPair[] $deviceLongUrls - * @param DeviceType[] $devicesToRemove */ private function __construct( private bool $longUrlPropWasProvided = false, public ?string $longUrl = null, - public array $deviceLongUrls = [], - public array $devicesToRemove = [], private bool $validSincePropWasProvided = false, public ?Chronos $validSince = null, private bool $validUntilPropWasProvided = false, @@ -55,15 +50,9 @@ final readonly class ShortUrlEdition implements TitleResolutionModelInterface throw ValidationException::fromInputFilter($inputFilter); } - [$deviceLongUrls, $devicesToRemove] = DeviceLongUrlPair::fromMapToChangeSet( - $inputFilter->getValue(ShortUrlInputFilter::DEVICE_LONG_URLS) ?? [], - ); - return new self( longUrlPropWasProvided: array_key_exists(ShortUrlInputFilter::LONG_URL, $data), longUrl: $inputFilter->getValue(ShortUrlInputFilter::LONG_URL), - deviceLongUrls: $deviceLongUrls, - devicesToRemove: $devicesToRemove, validSincePropWasProvided: array_key_exists(ShortUrlInputFilter::VALID_SINCE, $data), validSince: normalizeOptionalDate($inputFilter->getValue(ShortUrlInputFilter::VALID_SINCE)), validUntilPropWasProvided: array_key_exists(ShortUrlInputFilter::VALID_UNTIL, $data), @@ -86,8 +75,6 @@ final readonly class ShortUrlEdition implements TitleResolutionModelInterface return new self( longUrlPropWasProvided: $this->longUrlPropWasProvided, longUrl: $this->longUrl, - deviceLongUrls: $this->deviceLongUrls, - devicesToRemove: $this->devicesToRemove, validSincePropWasProvided: $this->validSincePropWasProvided, validSince: $this->validSince, validUntilPropWasProvided: $this->validUntilPropWasProvided, diff --git a/module/Core/src/ShortUrl/Model/Validation/DeviceLongUrlsValidator.php b/module/Core/src/ShortUrl/Model/Validation/DeviceLongUrlsValidator.php deleted file mode 100644 index 82119e4e..00000000 --- a/module/Core/src/ShortUrl/Model/Validation/DeviceLongUrlsValidator.php +++ /dev/null @@ -1,57 +0,0 @@ - 'Provided value is not an array.', - self::INVALID_DEVICE => 'You have provided at least one invalid device identifier.', - self::INVALID_LONG_URL => 'At least one of the long URLs are invalid.', - ]; - - public function __construct(private readonly ValidatorInterface $longUrlValidators) - { - parent::__construct(); - } - - public function isValid(mixed $value): bool - { - if (! is_array($value)) { - $this->error(self::NOT_ARRAY); - return false; - } - - $validValues = enumValues(DeviceType::class); - $keys = array_keys($value); - if (! every($keys, static fn ($key) => contains($key, $validValues))) { - $this->error(self::INVALID_DEVICE); - return false; - } - - $longUrls = array_values($value); - $result = every($longUrls, $this->longUrlValidators->isValid(...)); - if (! $result) { - $this->error(self::INVALID_LONG_URL); - } - - return $result; - } -} diff --git a/module/Core/src/ShortUrl/Model/Validation/ShortUrlInputFilter.php b/module/Core/src/ShortUrl/Model/Validation/ShortUrlInputFilter.php index 287ea746..8818e0f6 100644 --- a/module/Core/src/ShortUrl/Model/Validation/ShortUrlInputFilter.php +++ b/module/Core/src/ShortUrl/Model/Validation/ShortUrlInputFilter.php @@ -31,7 +31,6 @@ class ShortUrlInputFilter extends InputFilter // Fields for creation and edition public const LONG_URL = 'longUrl'; - public const DEVICE_LONG_URLS = 'deviceLongUrls'; public const VALID_SINCE = 'validSince'; public const VALID_UNTIL = 'validUntil'; public const MAX_VISITS = 'maxVisits'; @@ -97,12 +96,6 @@ class ShortUrlInputFilter extends InputFilter $longUrlInput->getValidatorChain()->merge($this->longUrlValidators()); $this->add($longUrlInput); - $deviceLongUrlsInput = InputFactory::basic(self::DEVICE_LONG_URLS); - $deviceLongUrlsInput->getValidatorChain()->attach( - new DeviceLongUrlsValidator($this->longUrlValidators(allowNull: ! $requireLongUrl)), - ); - $this->add($deviceLongUrlsInput); - $validSince = InputFactory::basic(self::VALID_SINCE); $validSince->getValidatorChain()->attach(new Validator\Date(['format' => DateTimeInterface::ATOM])); $this->add($validSince); diff --git a/module/Core/src/ShortUrl/Transformer/ShortUrlDataTransformer.php b/module/Core/src/ShortUrl/Transformer/ShortUrlDataTransformer.php index d0661504..ea694c61 100644 --- a/module/Core/src/ShortUrl/Transformer/ShortUrlDataTransformer.php +++ b/module/Core/src/ShortUrl/Transformer/ShortUrlDataTransformer.php @@ -27,7 +27,6 @@ class ShortUrlDataTransformer implements DataTransformerInterface 'shortCode' => $shortUrl->getShortCode(), 'shortUrl' => $this->stringifier->stringify($shortUrl), 'longUrl' => $shortUrl->getLongUrl(), - 'deviceLongUrls' => $shortUrl->deviceLongUrls(), 'dateCreated' => $shortUrl->getDateCreated()->toAtomString(), 'tags' => array_map(static fn (Tag $tag) => $tag->__toString(), $shortUrl->getTags()->toArray()), 'meta' => $this->buildMeta($shortUrl), diff --git a/module/Core/test/EventDispatcher/PublishingUpdatesGeneratorTest.php b/module/Core/test/EventDispatcher/PublishingUpdatesGeneratorTest.php index c5ebb1a8..545c5b47 100644 --- a/module/Core/test/EventDispatcher/PublishingUpdatesGeneratorTest.php +++ b/module/Core/test/EventDispatcher/PublishingUpdatesGeneratorTest.php @@ -51,7 +51,6 @@ class PublishingUpdatesGeneratorTest extends TestCase 'shortCode' => $shortUrl->getShortCode(), 'shortUrl' => 'http:/' . $shortUrl->getShortCode(), 'longUrl' => 'https://longUrl', - 'deviceLongUrls' => $shortUrl->deviceLongUrls(), 'dateCreated' => $shortUrl->getDateCreated()->toAtomString(), 'tags' => [], 'meta' => [ @@ -125,7 +124,6 @@ class PublishingUpdatesGeneratorTest extends TestCase 'shortCode' => $shortUrl->getShortCode(), 'shortUrl' => 'http:/' . $shortUrl->getShortCode(), 'longUrl' => 'https://longUrl', - 'deviceLongUrls' => $shortUrl->deviceLongUrls(), 'dateCreated' => $shortUrl->getDateCreated()->toAtomString(), 'tags' => [], 'meta' => [ diff --git a/module/Core/test/RedirectRule/Entity/RedirectConditionTest.php b/module/Core/test/RedirectRule/Entity/RedirectConditionTest.php index 20178068..da1e28e8 100644 --- a/module/Core/test/RedirectRule/Entity/RedirectConditionTest.php +++ b/module/Core/test/RedirectRule/Entity/RedirectConditionTest.php @@ -1,6 +1,6 @@ 'https://example.com/foo/bar', - 'deviceLongUrls' => [ - DeviceType::ANDROID->value => 'https://example.com/android', - DeviceType::IOS->value => 'https://example.com/ios', - ], ])); $repo = $this->createMock(EntityRepository::class); @@ -75,12 +71,16 @@ class ShortUrlRedirectionResolverTest extends TestCase 'https://example.com/foo/bar', ]; yield 'desktop user agent' => [$request(DESKTOP_USER_AGENT), null, 'https://example.com/foo/bar']; - yield 'android user agent' => [ + yield 'matching android device' => [ $request(ANDROID_USER_AGENT), - RedirectCondition::forQueryParam('foo', 'bar'), // This condition won't match - 'https://example.com/android', + RedirectCondition::forDevice(DeviceType::ANDROID), + 'https://example.com/from-rule', + ]; + yield 'matching ios device' => [ + $request(IOS_USER_AGENT), + RedirectCondition::forDevice(DeviceType::IOS), + 'https://example.com/from-rule', ]; - yield 'ios user agent' => [$request(IOS_USER_AGENT), null, 'https://example.com/ios']; yield 'matching language' => [ $request()->withHeader('Accept-Language', 'es-ES'), RedirectCondition::forLanguage('es-ES'), diff --git a/module/Core/test/ShortUrl/Entity/ShortUrlTest.php b/module/Core/test/ShortUrl/Entity/ShortUrlTest.php index eb89df5c..be5b4101 100644 --- a/module/Core/test/ShortUrl/Entity/ShortUrlTest.php +++ b/module/Core/test/ShortUrl/Entity/ShortUrlTest.php @@ -10,11 +10,9 @@ use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; use Shlinkio\Shlink\Core\Exception\ShortCodeCannotBeRegeneratedException; -use Shlinkio\Shlink\Core\Model\DeviceType; use Shlinkio\Shlink\Core\Options\UrlShortenerOptions; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation; -use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlEdition; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlMode; use Shlinkio\Shlink\Core\ShortUrl\Model\Validation\ShortUrlInputFilter; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; @@ -113,48 +111,6 @@ class ShortUrlTest extends TestCase self::assertEquals($expectedShortCodeLength, strlen($shortCode)); } - #[Test] - public function deviceLongUrlsAreUpdated(): void - { - $shortUrl = ShortUrl::withLongUrl('https://foo'); - - $shortUrl->update(ShortUrlEdition::fromRawData([ - ShortUrlInputFilter::DEVICE_LONG_URLS => [ - DeviceType::ANDROID->value => 'https://android', - DeviceType::IOS->value => 'https://ios', - ], - ])); - self::assertEquals([ - DeviceType::ANDROID->value => 'https://android', - DeviceType::IOS->value => 'https://ios', - DeviceType::DESKTOP->value => null, - ], $shortUrl->deviceLongUrls()); - - $shortUrl->update(ShortUrlEdition::fromRawData([ - ShortUrlInputFilter::DEVICE_LONG_URLS => [ - DeviceType::ANDROID->value => null, - DeviceType::DESKTOP->value => 'https://desktop', - ], - ])); - self::assertEquals([ - DeviceType::ANDROID->value => null, - DeviceType::IOS->value => 'https://ios', - DeviceType::DESKTOP->value => 'https://desktop', - ], $shortUrl->deviceLongUrls()); - - $shortUrl->update(ShortUrlEdition::fromRawData([ - ShortUrlInputFilter::DEVICE_LONG_URLS => [ - DeviceType::ANDROID->value => null, - DeviceType::IOS->value => null, - ], - ])); - self::assertEquals([ - DeviceType::ANDROID->value => null, - DeviceType::IOS->value => null, - DeviceType::DESKTOP->value => 'https://desktop', - ], $shortUrl->deviceLongUrls()); - } - #[Test] public function generatesLowercaseOnlyShortCodesInLooseMode(): void { diff --git a/module/Core/test/ShortUrl/Model/ShortUrlCreationTest.php b/module/Core/test/ShortUrl/Model/ShortUrlCreationTest.php index 47d4648c..b84b5b27 100644 --- a/module/Core/test/ShortUrl/Model/ShortUrlCreationTest.php +++ b/module/Core/test/ShortUrl/Model/ShortUrlCreationTest.php @@ -9,7 +9,6 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; use Shlinkio\Shlink\Core\Exception\ValidationException; -use Shlinkio\Shlink\Core\Model\DeviceType; use Shlinkio\Shlink\Core\Options\UrlShortenerOptions; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlMode; @@ -79,43 +78,6 @@ class ShortUrlCreationTest extends TestCase yield [[ ShortUrlInputFilter::LONG_URL => 'missing_schema', ]]; - yield [[ - ShortUrlInputFilter::LONG_URL => 'https://foo', - ShortUrlInputFilter::DEVICE_LONG_URLS => [ - 'invalid' => 'https://shlink.io', - ], - ]]; - yield [[ - ShortUrlInputFilter::LONG_URL => 'https://foo', - ShortUrlInputFilter::DEVICE_LONG_URLS => [ - DeviceType::DESKTOP->value => '', - ], - ]]; - yield [[ - ShortUrlInputFilter::LONG_URL => 'https://foo', - ShortUrlInputFilter::DEVICE_LONG_URLS => [ - DeviceType::DESKTOP->value => null, - ], - ]]; - yield [[ - ShortUrlInputFilter::LONG_URL => 'https://foo', - ShortUrlInputFilter::DEVICE_LONG_URLS => [ - DeviceType::IOS->value => ' ', - ], - ]]; - yield [[ - ShortUrlInputFilter::LONG_URL => 'https://foo', - ShortUrlInputFilter::DEVICE_LONG_URLS => [ - DeviceType::ANDROID->value => 'missing_schema', - ], - ]]; - yield [[ - ShortUrlInputFilter::LONG_URL => 'https://foo', - ShortUrlInputFilter::DEVICE_LONG_URLS => [ - DeviceType::IOS->value => 'https://bar', - DeviceType::ANDROID->value => [], - ], - ]]; } #[Test, DataProvider('provideCustomSlugs')] diff --git a/module/Core/test/ShortUrl/Model/ShortUrlEditionTest.php b/module/Core/test/ShortUrl/Model/ShortUrlEditionTest.php deleted file mode 100644 index 5d77d806..00000000 --- a/module/Core/test/ShortUrl/Model/ShortUrlEditionTest.php +++ /dev/null @@ -1,59 +0,0 @@ - $deviceLongUrls]); - - self::assertEquals($expectedDeviceLongUrls, $edition->deviceLongUrls); - self::assertEquals($expectedDevicesToRemove, $edition->devicesToRemove); - } - - public static function provideDeviceLongUrls(): iterable - { - yield 'null' => [null, [], []]; - yield 'empty' => [[], [], []]; - yield 'only new urls' => [[ - DeviceType::DESKTOP->value => 'https://foo', - DeviceType::IOS->value => 'https://bar', - ], [ - DeviceType::DESKTOP->value => DeviceLongUrlPair::fromRawTypeAndLongUrl( - DeviceType::DESKTOP->value, - 'https://foo', - ), - DeviceType::IOS->value => DeviceLongUrlPair::fromRawTypeAndLongUrl(DeviceType::IOS->value, 'https://bar'), - ], []]; - yield 'only urls to remove' => [[ - DeviceType::ANDROID->value => null, - DeviceType::IOS->value => null, - ], [], [DeviceType::ANDROID, DeviceType::IOS]]; - yield 'both' => [[ - DeviceType::DESKTOP->value => 'https://bar', - DeviceType::IOS->value => 'https://foo', - DeviceType::ANDROID->value => null, - ], [ - DeviceType::DESKTOP->value => DeviceLongUrlPair::fromRawTypeAndLongUrl( - DeviceType::DESKTOP->value, - 'https://bar', - ), - DeviceType::IOS->value => DeviceLongUrlPair::fromRawTypeAndLongUrl(DeviceType::IOS->value, 'https://foo'), - ], [DeviceType::ANDROID]]; - } -} diff --git a/module/Core/test/ShortUrl/Model/Validation/DeviceLongUrlsValidatorTest.php b/module/Core/test/ShortUrl/Model/Validation/DeviceLongUrlsValidatorTest.php deleted file mode 100644 index 860e2a39..00000000 --- a/module/Core/test/ShortUrl/Model/Validation/DeviceLongUrlsValidatorTest.php +++ /dev/null @@ -1,70 +0,0 @@ -validator = new DeviceLongUrlsValidator(new NotEmpty()); - } - - #[Test, DataProvider('provideNonArrayValues')] - public function nonArrayValuesAreNotValid(mixed $invalidValue): void - { - self::assertFalse($this->validator->isValid($invalidValue)); - self::assertEquals(['NOT_ARRAY' => 'Provided value is not an array.'], $this->validator->getMessages()); - } - - public static function provideNonArrayValues(): iterable - { - yield 'int' => [0]; - yield 'float' => [100.45]; - yield 'string' => ['foo']; - yield 'boolean' => [true]; - yield 'object' => [new stdClass()]; - yield 'null' => [null]; - } - - #[Test] - public function unrecognizedKeysAreNotValid(): void - { - self::assertFalse($this->validator->isValid(['foo' => 'bar'])); - self::assertEquals( - ['INVALID_DEVICE' => 'You have provided at least one invalid device identifier.'], - $this->validator->getMessages(), - ); - } - - #[Test] - public function everyUrlMustMatchLongUrlValidator(): void - { - self::assertFalse($this->validator->isValid([DeviceType::ANDROID->value => ''])); - self::assertEquals( - ['INVALID_LONG_URL' => 'At least one of the long URLs are invalid.'], - $this->validator->getMessages(), - ); - } - - #[Test] - public function validValuesResultInValidResult(): void - { - self::assertTrue($this->validator->isValid([ - DeviceType::ANDROID->value => 'foo', - DeviceType::IOS->value => 'bar', - DeviceType::DESKTOP->value => 'baz', - ])); - } -} diff --git a/module/Core/test/ShortUrl/ShortUrlServiceTest.php b/module/Core/test/ShortUrl/ShortUrlServiceTest.php index dfbf7d75..ae73ba33 100644 --- a/module/Core/test/ShortUrl/ShortUrlServiceTest.php +++ b/module/Core/test/ShortUrl/ShortUrlServiceTest.php @@ -12,7 +12,6 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\Rule\InvocationOrder; use PHPUnit\Framework\MockObject\Rule\InvokedCount; use PHPUnit\Framework\TestCase; -use Shlinkio\Shlink\Core\Model\DeviceType; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlTitleResolutionHelperInterface; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlEdition; @@ -22,9 +21,6 @@ use Shlinkio\Shlink\Core\ShortUrl\ShortUrlResolverInterface; use Shlinkio\Shlink\Core\ShortUrl\ShortUrlService; use Shlinkio\Shlink\Rest\Entity\ApiKey; -use function array_fill_keys; -use function Shlinkio\Shlink\Core\enumValues; - class ShortUrlServiceTest extends TestCase { private ShortUrlService $service; @@ -73,21 +69,11 @@ class ShortUrlServiceTest extends TestCase $apiKey, ); - $resolveDeviceLongUrls = function () use ($shortUrlEdit): array { - $result = array_fill_keys(enumValues(DeviceType::class), null); - foreach ($shortUrlEdit->deviceLongUrls ?? [] as $longUrl) { - $result[$longUrl->deviceType->value] = $longUrl->longUrl; - } - - return $result; - }; - self::assertSame($shortUrl, $result); self::assertEquals($shortUrlEdit->validSince, $shortUrl->getValidSince()); self::assertEquals($shortUrlEdit->validUntil, $shortUrl->getValidUntil()); self::assertEquals($shortUrlEdit->maxVisits, $shortUrl->getMaxVisits()); self::assertEquals($shortUrlEdit->longUrl ?? $originalLongUrl, $shortUrl->getLongUrl()); - self::assertEquals($resolveDeviceLongUrls(), $shortUrl->deviceLongUrls()); } public static function provideShortUrlEdits(): iterable @@ -102,11 +88,5 @@ class ShortUrlServiceTest extends TestCase 'maxVisits' => 10, 'longUrl' => 'https://modifiedLongUrl', ]), ApiKey::create()]; - yield 'device redirects' => [new InvokedCount(0), ShortUrlEdition::fromRawData([ - 'deviceLongUrls' => [ - DeviceType::IOS->value => 'https://iosLongUrl', - DeviceType::ANDROID->value => 'https://androidLongUrl', - ], - ]), null]; } } diff --git a/module/Rest/test-api/Action/CreateShortUrlTest.php b/module/Rest/test-api/Action/CreateShortUrlTest.php index 96dc5e7b..42742bbb 100644 --- a/module/Rest/test-api/Action/CreateShortUrlTest.php +++ b/module/Rest/test-api/Action/CreateShortUrlTest.php @@ -250,18 +250,6 @@ class CreateShortUrlTest extends ApiTestCase yield 'empty long url v3' => [['longUrl' => ' '], '3', 'https://shlink.io/api/error/invalid-data']; yield 'missing url schema v2' => [['longUrl' => 'foo.com'], '2', 'https://shlink.io/api/error/invalid-data']; yield 'missing url schema v3' => [['longUrl' => 'foo.com'], '3', 'https://shlink.io/api/error/invalid-data']; - yield 'empty device long url v2' => [[ - 'longUrl' => 'foo', - 'deviceLongUrls' => [ - 'android' => null, - ], - ], '2', 'https://shlink.io/api/error/invalid-data']; - yield 'empty device long url v3' => [[ - 'longUrl' => 'foo', - 'deviceLongUrls' => [ - 'ios' => ' ', - ], - ], '3', 'https://shlink.io/api/error/invalid-data']; } #[Test] @@ -313,22 +301,6 @@ class CreateShortUrlTest extends ApiTestCase self::assertEquals('http://s.test/🦣🦣🦣', $payload['shortUrl']); } - #[Test] - public function canCreateShortUrlsWithDeviceLongUrls(): void - { - [$statusCode, $payload] = $this->createShortUrl([ - 'longUrl' => 'https://github.com/shlinkio/shlink/issues/1557', - 'deviceLongUrls' => [ - 'ios' => 'https://github.com/shlinkio/shlink/ios', - 'android' => 'https://github.com/shlinkio/shlink/android', - ], - ]); - - self::assertEquals(self::STATUS_OK, $statusCode); - self::assertEquals('https://github.com/shlinkio/shlink/ios', $payload['deviceLongUrls']['ios'] ?? null); - self::assertEquals('https://github.com/shlinkio/shlink/android', $payload['deviceLongUrls']['android'] ?? null); - } - #[Test] public function titleIsIgnoredIfLongUrlTimesOut(): void { diff --git a/module/Rest/test-api/Action/EditShortUrlTest.php b/module/Rest/test-api/Action/EditShortUrlTest.php index 07fe84d3..24f91e58 100644 --- a/module/Rest/test-api/Action/EditShortUrlTest.php +++ b/module/Rest/test-api/Action/EditShortUrlTest.php @@ -153,27 +153,4 @@ class EditShortUrlTest extends ApiTestCase ]; yield 'no domain' => [null, 'https://shlink.io/documentation/']; } - - #[Test] - public function deviceLongUrlsCanBeEdited(): void - { - $shortCode = 'def456'; - $url = new Uri(sprintf('/short-urls/%s', $shortCode)); - $editResp = $this->callApiWithKey(self::METHOD_PATCH, (string) $url, [RequestOptions::JSON => [ - 'deviceLongUrls' => [ - 'android' => null, // This one will get removed - 'ios' => 'https://blog.alejandrocelaya.com/ios/edited', // This one will be edited - 'desktop' => 'https://blog.alejandrocelaya.com/desktop', // This one is new and will be created - ], - ]]); - $deviceLongUrls = $this->getJsonResponsePayload($editResp)['deviceLongUrls'] ?? []; - - self::assertEquals(self::STATUS_OK, $editResp->getStatusCode()); - self::assertArrayHasKey('ios', $deviceLongUrls); - self::assertEquals('https://blog.alejandrocelaya.com/ios/edited', $deviceLongUrls['ios']); - self::assertArrayHasKey('desktop', $deviceLongUrls); - self::assertEquals('https://blog.alejandrocelaya.com/desktop', $deviceLongUrls['desktop']); - self::assertArrayHasKey('android', $deviceLongUrls); - self::assertNull($deviceLongUrls['android']); - } } diff --git a/module/Rest/test-api/Action/ListShortUrlsTest.php b/module/Rest/test-api/Action/ListShortUrlsTest.php index 3591ea60..c3b9b41e 100644 --- a/module/Rest/test-api/Action/ListShortUrlsTest.php +++ b/module/Rest/test-api/Action/ListShortUrlsTest.php @@ -8,7 +8,6 @@ use Cake\Chronos\Chronos; use GuzzleHttp\RequestOptions; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; -use Shlinkio\Shlink\Core\Model\DeviceType; use Shlinkio\Shlink\TestUtils\ApiTest\ApiTestCase; use function count; @@ -163,123 +162,109 @@ class ListShortUrlsTest extends ApiTestCase public static function provideFilteredLists(): iterable { - $withDeviceLongUrls = static fn (array $shortUrl, ?array $longUrls = null) => [ - ...$shortUrl, - 'deviceLongUrls' => $longUrls ?? [ - DeviceType::ANDROID->value => null, - DeviceType::IOS->value => null, - DeviceType::DESKTOP->value => null, - ], - ]; - $shortUrlMeta = $withDeviceLongUrls(self::SHORT_URL_META, [ - DeviceType::ANDROID->value => 'https://blog.alejandrocelaya.com/android', - DeviceType::IOS->value => 'https://blog.alejandrocelaya.com/ios', - DeviceType::DESKTOP->value => null, - ]); - yield [[], [ - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG), - $shortUrlMeta, - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN), - $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), - $withDeviceLongUrls(self::SHORT_URL_DOCS), + self::SHORT_URL_CUSTOM_DOMAIN, + self::SHORT_URL_CUSTOM_SLUG, + self::SHORT_URL_META, + self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN, + self::SHORT_URL_SHLINK_WITH_TITLE, + self::SHORT_URL_DOCS, ], 'valid_api_key']; yield [['excludePastValidUntil' => 'true'], [ - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG), - $shortUrlMeta, - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN), - $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), + self::SHORT_URL_CUSTOM_DOMAIN, + self::SHORT_URL_CUSTOM_SLUG, + self::SHORT_URL_META, + self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN, + self::SHORT_URL_SHLINK_WITH_TITLE, ], 'valid_api_key']; yield [['excludeMaxVisitsReached' => 'true'], [ - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG), - $shortUrlMeta, - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN), - $withDeviceLongUrls(self::SHORT_URL_DOCS), + self::SHORT_URL_CUSTOM_DOMAIN, + self::SHORT_URL_CUSTOM_SLUG, + self::SHORT_URL_META, + self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN, + self::SHORT_URL_DOCS, ], 'valid_api_key']; yield [['orderBy' => 'shortCode'], [ - $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG), - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN), - $shortUrlMeta, - $withDeviceLongUrls(self::SHORT_URL_DOCS), - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), + self::SHORT_URL_SHLINK_WITH_TITLE, + self::SHORT_URL_CUSTOM_SLUG, + self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN, + self::SHORT_URL_META, + self::SHORT_URL_DOCS, + self::SHORT_URL_CUSTOM_DOMAIN, ], 'valid_api_key']; yield [['orderBy' => 'shortCode-DESC'], [ - $withDeviceLongUrls(self::SHORT_URL_DOCS), - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), - $shortUrlMeta, - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN), - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG), - $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), + self::SHORT_URL_DOCS, + self::SHORT_URL_CUSTOM_DOMAIN, + self::SHORT_URL_META, + self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN, + self::SHORT_URL_CUSTOM_SLUG, + self::SHORT_URL_SHLINK_WITH_TITLE, ], 'valid_api_key']; yield [['orderBy' => 'title-DESC'], [ - $shortUrlMeta, - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG), - $withDeviceLongUrls(self::SHORT_URL_DOCS), - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN), - $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), + self::SHORT_URL_META, + self::SHORT_URL_CUSTOM_SLUG, + self::SHORT_URL_DOCS, + self::SHORT_URL_CUSTOM_DOMAIN, + self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN, + self::SHORT_URL_SHLINK_WITH_TITLE, ], 'valid_api_key']; yield [['startDate' => Chronos::parse('2018-12-01')->toAtomString()], [ - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG), - $shortUrlMeta, + self::SHORT_URL_CUSTOM_DOMAIN, + self::SHORT_URL_CUSTOM_SLUG, + self::SHORT_URL_META, ], 'valid_api_key']; yield [['endDate' => Chronos::parse('2018-12-01')->toAtomString()], [ - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN), - $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), - $withDeviceLongUrls(self::SHORT_URL_DOCS), + self::SHORT_URL_CUSTOM_SLUG_AND_DOMAIN, + self::SHORT_URL_SHLINK_WITH_TITLE, + self::SHORT_URL_DOCS, ], 'valid_api_key']; yield [['tags' => ['foo']], [ - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), - $shortUrlMeta, - $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), + self::SHORT_URL_CUSTOM_DOMAIN, + self::SHORT_URL_META, + self::SHORT_URL_SHLINK_WITH_TITLE, ], 'valid_api_key']; yield [['tags' => ['bar']], [ - $shortUrlMeta, + self::SHORT_URL_META, ], 'valid_api_key']; yield [['tags' => ['foo', 'bar']], [ - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), - $shortUrlMeta, - $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), + self::SHORT_URL_CUSTOM_DOMAIN, + self::SHORT_URL_META, + self::SHORT_URL_SHLINK_WITH_TITLE, ], 'valid_api_key']; yield [['tags' => ['foo', 'bar'], 'tagsMode' => 'any'], [ - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), - $shortUrlMeta, - $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), + self::SHORT_URL_CUSTOM_DOMAIN, + self::SHORT_URL_META, + self::SHORT_URL_SHLINK_WITH_TITLE, ], 'valid_api_key']; yield [['tags' => ['foo', 'bar'], 'tagsMode' => 'all'], [ - $shortUrlMeta, + self::SHORT_URL_META, ], 'valid_api_key']; yield [['tags' => ['foo', 'bar', 'baz']], [ - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), - $shortUrlMeta, - $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), + self::SHORT_URL_CUSTOM_DOMAIN, + self::SHORT_URL_META, + self::SHORT_URL_SHLINK_WITH_TITLE, ], 'valid_api_key']; yield [['tags' => ['foo', 'bar', 'baz'], 'tagsMode' => 'all'], [], 'valid_api_key']; yield [['tags' => ['foo'], 'endDate' => Chronos::parse('2018-12-01')->toAtomString()], [ - $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), + self::SHORT_URL_SHLINK_WITH_TITLE, ], 'valid_api_key']; yield [['searchTerm' => 'alejandro'], [ - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), - $shortUrlMeta, + self::SHORT_URL_CUSTOM_DOMAIN, + self::SHORT_URL_META, ], 'valid_api_key']; yield [['searchTerm' => 'cool'], [ - $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), + self::SHORT_URL_SHLINK_WITH_TITLE, ], 'valid_api_key']; yield [['searchTerm' => 'example.com'], [ - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), + self::SHORT_URL_CUSTOM_DOMAIN, ], 'valid_api_key']; yield [[], [ - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_SLUG), - $shortUrlMeta, - $withDeviceLongUrls(self::SHORT_URL_SHLINK_WITH_TITLE), + self::SHORT_URL_CUSTOM_SLUG, + self::SHORT_URL_META, + self::SHORT_URL_SHLINK_WITH_TITLE, ], 'author_api_key']; yield [[], [ - $withDeviceLongUrls(self::SHORT_URL_CUSTOM_DOMAIN), + self::SHORT_URL_CUSTOM_DOMAIN, ], 'domain_api_key']; } diff --git a/module/Rest/test-api/Fixtures/ShortUrlRedirectRulesFixture.php b/module/Rest/test-api/Fixtures/ShortUrlRedirectRulesFixture.php index 6d321a91..ab0e8dce 100644 --- a/module/Rest/test-api/Fixtures/ShortUrlRedirectRulesFixture.php +++ b/module/Rest/test-api/Fixtures/ShortUrlRedirectRulesFixture.php @@ -8,6 +8,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\DependentFixtureInterface; use Doctrine\Persistence\ObjectManager; +use Shlinkio\Shlink\Core\Model\DeviceType; use Shlinkio\Shlink\Core\RedirectRule\Entity\RedirectCondition; use Shlinkio\Shlink\Core\RedirectRule\Entity\ShortUrlRedirectRule; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; @@ -33,6 +34,12 @@ class ShortUrlRedirectRulesFixture extends AbstractFixture implements DependentF $helloQueryCondition = RedirectCondition::forQueryParam('hello', 'world'); $manager->persist($helloQueryCondition); + $androidCondition = RedirectCondition::forDevice(DeviceType::ANDROID); + $manager->persist($androidCondition); + + $iosCondition = RedirectCondition::forDevice(DeviceType::IOS); + $manager->persist($iosCondition); + $englishAndFooQueryRule = new ShortUrlRedirectRule( $defShortUrl, 1, @@ -57,6 +64,22 @@ class ShortUrlRedirectRulesFixture extends AbstractFixture implements DependentF ); $manager->persist($onlyEnglishRule); + $androidRule = new ShortUrlRedirectRule( + $defShortUrl, + 4, + 'https://blog.alejandrocelaya.com/android', + new ArrayCollection([$androidCondition]), + ); + $manager->persist($androidRule); + + $iosRule = new ShortUrlRedirectRule( + $defShortUrl, + 5, + 'https://blog.alejandrocelaya.com/ios', + new ArrayCollection([$iosCondition]), + ); + $manager->persist($iosRule); + $manager->flush(); } } diff --git a/module/Rest/test-api/Fixtures/ShortUrlsFixture.php b/module/Rest/test-api/Fixtures/ShortUrlsFixture.php index 03f3f603..31ca6361 100644 --- a/module/Rest/test-api/Fixtures/ShortUrlsFixture.php +++ b/module/Rest/test-api/Fixtures/ShortUrlsFixture.php @@ -9,7 +9,6 @@ use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\DependentFixtureInterface; use Doctrine\Persistence\ObjectManager; use ReflectionObject; -use Shlinkio\Shlink\Core\Model\DeviceType; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation; use Shlinkio\Shlink\Core\ShortUrl\Resolver\PersistenceShortUrlRelationResolver; @@ -49,10 +48,6 @@ class ShortUrlsFixture extends AbstractFixture implements DependentFixtureInterf 'apiKey' => $authorApiKey, 'longUrl' => 'https://blog.alejandrocelaya.com/2017/12/09/acmailer-7-0-the-most-important-release-in-a-long-time/', - 'deviceLongUrls' => [ - DeviceType::ANDROID->value => 'https://blog.alejandrocelaya.com/android', - DeviceType::IOS->value => 'https://blog.alejandrocelaya.com/ios', - ], 'tags' => ['foo', 'bar'], ]), $relationResolver), '2019-01-01 00:00:10'); $manager->persist($defShortUrl);