From 1ff241411be161af88fccf53f83ef250aa733661 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Tue, 14 Dec 2021 22:21:53 +0100 Subject: [PATCH] Removed everything that was deprecated --- UPGRADE.md | 39 +++++ bin/helper/mezzio-swoole | 51 ------ build.sh | 3 - config/autoload/geolite2.global.php | 2 +- config/autoload/redirects.global.php | 7 +- config/autoload/url-shortener.global.php | 17 +- config/config.php | 5 +- .../paths/v1_short-urls_{shortCode}.json | 2 +- .../paths/v1_short-urls_{shortCode}_tags.json | 106 ------------ docs/swagger/paths/v1_tags.json | 78 --------- .../paths/{shortCode}_qr-code_{size}.json | 66 -------- docs/swagger/swagger.json | 6 - module/CLI/config/cli.config.php | 1 - module/CLI/config/dependencies.config.php | 2 - .../src/Command/Api/GenerateKeyCommand.php | 8 +- .../CLI/src/Command/Api/ListKeysCommand.php | 8 +- module/CLI/src/Command/BaseCommand.php | 47 ------ .../ShortUrl/CreateShortUrlCommand.php | 59 ++----- .../Command/ShortUrl/ListShortUrlsCommand.php | 14 +- .../CLI/src/Command/Tag/CreateTagCommand.php | 52 ------ .../Util/AbstractWithDateRangeCommand.php | 20 +-- .../ShortUrl/CreateShortUrlCommandTest.php | 4 +- .../ShortUrl/ListShortUrlsCommandTest.php | 7 +- .../test/Command/Tag/CreateTagCommandTest.php | 51 ------ module/Core/config/routes.config.php | 10 -- module/Core/src/Action/Model/QrCodeParams.php | 8 +- .../src/Config/DeprecatedConfigParser.php | 41 ----- .../src/Config/SimplifiedConfigParser.php | 94 ----------- .../src/Exception/DeleteShortUrlException.php | 2 +- module/Core/src/Model/ShortUrlsOrdering.php | 22 +-- module/Core/src/Options/AppOptions.php | 11 +- .../Core/src/Options/UrlShortenerOptions.php | 12 -- .../src/Repository/ShortUrlRepository.php | 17 +- module/Core/src/Tag/TagService.php | 23 +-- module/Core/src/Tag/TagServiceInterface.php | 8 - module/Core/src/Util/TagManagerTrait.php | 37 ---- module/Core/test/Action/QrCodeActionTest.php | 6 - .../Config/DeprecatedConfigParserTest.php | 111 ------------ .../Config/SimplifiedConfigParserTest.php | 158 ------------------ .../Exception/DeleteShortUrlExceptionTest.php | 2 +- .../Core/test/Service/Tag/TagServiceTest.php | 15 -- module/Rest/config/dependencies.config.php | 4 - module/Rest/config/routes.config.php | 2 - .../ShortUrl/EditShortUrlTagsAction.php | 47 ------ .../Rest/src/Action/Tag/CreateTagsAction.php | 35 ---- .../MissingAuthenticationException.php | 5 +- .../src/Middleware/BodyParserMiddleware.php | 35 +--- .../test-api/Action/DeleteShortUrlTest.php | 2 +- .../test-api/Action/EditShortUrlTagsTest.php | 94 ----------- .../test-api/Action/ListShortUrlsTest.php | 8 - .../ShortUrl/EditShortUrlTagsActionTest.php | 63 ------- .../test/Action/Tag/CreateTagsActionTest.php | 50 ------ .../MissingAuthenticationExceptionTest.php | 9 +- .../Middleware/BodyParserMiddlewareTest.php | 29 ---- 54 files changed, 108 insertions(+), 1507 deletions(-) delete mode 100755 bin/helper/mezzio-swoole delete mode 100644 docs/swagger/paths/v1_short-urls_{shortCode}_tags.json delete mode 100644 docs/swagger/paths/{shortCode}_qr-code_{size}.json delete mode 100644 module/CLI/src/Command/BaseCommand.php delete mode 100644 module/CLI/src/Command/Tag/CreateTagCommand.php delete mode 100644 module/CLI/test/Command/Tag/CreateTagCommandTest.php delete mode 100644 module/Core/src/Config/DeprecatedConfigParser.php delete mode 100644 module/Core/src/Config/SimplifiedConfigParser.php delete mode 100644 module/Core/src/Util/TagManagerTrait.php delete mode 100644 module/Core/test/Config/DeprecatedConfigParserTest.php delete mode 100644 module/Core/test/Config/SimplifiedConfigParserTest.php delete mode 100644 module/Rest/src/Action/ShortUrl/EditShortUrlTagsAction.php delete mode 100644 module/Rest/src/Action/Tag/CreateTagsAction.php delete mode 100644 module/Rest/test-api/Action/EditShortUrlTagsTest.php delete mode 100644 module/Rest/test/Action/ShortUrl/EditShortUrlTagsActionTest.php delete mode 100644 module/Rest/test/Action/Tag/CreateTagsActionTest.php diff --git a/UPGRADE.md b/UPGRADE.md index ebe6ad17..815dc2dd 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,5 +1,44 @@ # Upgrading +## From v2.x to v3.x + +### Changes in REST API + +* The `type` property returned when trying to delete a URL that reached the visits threshold, when using the `DELETE /short-urls/{shortCode}` endpoint, is now `INVALID_SHORT_URL_DELETION` instead pf `INVALID_SHORTCODE_DELETION`. +* The `INVALID_AUTHORIZATION` error no longer includes the `expectedTypes` property. Use `expectedHeaders` one instead. +* The `GET /rest/v2/short-urls` endpoint no longer allows ordering by `visitsCount`, `visitCount` or `originalUrl`. Use `visits` to replace the first two, and `longUrl` to replace the last one. +* The `GET /rest/v2/short-urls` endpoint no longer allows providing the ordering params with array notation, as in `/shortUrls?orderBy[longUrl]=DESC`. Instead, use the following notation `/shortUrls?orderBy?longUrl-DESC`. +* Requests expecting a body no longer support url-encoded payloads. Instead, always use JSON bodies. +* The next endpoints have been removed: + * `PUT /rest/v2/short-urls/{shortCode}/tags`: Use the `PATCH /rest/v2/short-urls/{shortCode}` endpoint to set the short URL tags. + * `POST /rest/v2/tags`: Use `POST /rest/v2/short-urls` or `PATCH /rest/v2/short-urls/{shortCodes}` to create new tags already attached to a short URL. Creating orphan tags makes no sense. + +### Changes in CLI + +* The next commands have been removed: + * `short-url:generate`: Use `short-url:create` instead. + * `tag:create`: Creating orphan tags makes no sense. +* Params in camelCase format are no longer supported. They all have an equivalent kebab-case replacement. (for example, from `--startDate` to `--start-date`). +* The `short-url:create` command no longer accepts the `--no-validate-url` flag. Now URLs are never validated, unless `--validate-url` is passed. + +### Changes in config + +* The next env vars have been removed: + * `INVALID_SHORT_URL_REDIRECT_TO`: Replaced by `DEFAULT_INVALID_SHORT_URL_REDIRECT`. + * `REGULAR_404_REDIRECT_TO`: Replaced by `DEFAULT_REGULAR_404_REDIRECT`. + * `BASE_URL_REDIRECT_TO`: Replaced by `DEFAULT_BASE_URL_REDIRECT`. + * `SHORT_DOMAIN_HOST`: Replaced by `DEFAULT_DOMAIN`. + * `SHORT_DOMAIN_SCHEMA`: Replaced by `IS_HTTPS_ENABLED`. + * `USE_HTTPS`: Replaced by `IS_HTTPS_ENABLED`. + * `VALIDATE_URLS`: There's no replacement. URLs are not validated, unless explicitly requested during creation or edition. + +### Other changes + +* A default GeoLite2 license key is no longer provided. If you don't provide your own as explained in [the docs](https://shlink.io/documentation/geolite-license-key/), Shlink will not try to update the file anymore. +* The docker image no longer accepts providing configuration via json files mounted in the `config/params` folder. Only env vars are supported now. +* If you were manually serving Shlink with swoole, the entry script has to be changed from `/path/to/shlink/vendor/bin/mezzio-swoole start` to `/path/to/shlink/vendor/bin/laminas mezzio:swoole:start` +* The `GET /{shortCode}/qr-code/{size}` url has been removed. Use `GET /{shortCode}/qr-code?size={size}` instead. + ## From v1.x to v2.x ### PHP 7.4 required diff --git a/bin/helper/mezzio-swoole b/bin/helper/mezzio-swoole deleted file mode 100755 index 2c341326..00000000 --- a/bin/helper/mezzio-swoole +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env php -get('config')['laminas-cli']['commands'] ?? [], - fn ($c, string $command) => str_starts_with($command, $commandsPrefix), -); -$registeredCommands = []; - -foreach ($commands as $newName => $commandServiceName) { - [, $oldName] = explode($commandsPrefix, $newName); - $registeredCommands[$oldName] = $commandServiceName; - - $container->addDelegator($commandServiceName, static function ($c, $n, callable $factory) use ($oldName) { - /** @var Command $command */ - $command = $factory(); - $command->setAliases([$oldName]); - - return $command; - }); -} - -$commandLine = new CommandLine('Mezzio web server', $version); -$commandLine->setAutoExit(true); -$commandLine->setCommandLoader(new ContainerCommandLoader($container, $registeredCommands)); -$commandLine->run(); diff --git a/build.sh b/build.sh index c6abbc8a..eb97aef6 100755 --- a/build.sh +++ b/build.sh @@ -36,9 +36,6 @@ ${composerBin} install --no-dev --prefer-dist $composerFlags if [[ $noSwoole ]]; then # If generating a dist not for swoole, uninstall mezzio-swoole ${composerBin} remove mezzio/mezzio-swoole --with-all-dependencies --update-no-dev $composerFlags -else - # Copy mezzio helper script to vendor (deprecated - Remove with Shlink 3.0.0) - cp "${projectdir}/bin/helper/mezzio-swoole" "./vendor/bin" fi # Delete development files diff --git a/config/autoload/geolite2.global.php b/config/autoload/geolite2.global.php index 3d8f0848..64127173 100644 --- a/config/autoload/geolite2.global.php +++ b/config/autoload/geolite2.global.php @@ -9,7 +9,7 @@ return [ 'geolite2' => [ 'db_location' => __DIR__ . '/../../data/GeoLite2-City.mmdb', 'temp_dir' => __DIR__ . '/../../data', - 'license_key' => env('GEOLITE_LICENSE_KEY', 'G4Lm0C60yJsnkdPi'), // Deprecated. Remove hardcoded license on v3 + 'license_key' => env('GEOLITE_LICENSE_KEY'), ], ]; diff --git a/config/autoload/redirects.global.php b/config/autoload/redirects.global.php index d2c73884..18f2719e 100644 --- a/config/autoload/redirects.global.php +++ b/config/autoload/redirects.global.php @@ -10,10 +10,9 @@ use const Shlinkio\Shlink\DEFAULT_REDIRECT_STATUS_CODE; return [ 'not_found_redirects' => [ - // Deprecated env vars - 'invalid_short_url' => env('DEFAULT_INVALID_SHORT_URL_REDIRECT', env('INVALID_SHORT_URL_REDIRECT_TO')), - 'regular_404' => env('DEFAULT_REGULAR_404_REDIRECT', env('REGULAR_404_REDIRECT_TO')), - 'base_url' => env('DEFAULT_BASE_URL_REDIRECT', env('BASE_URL_REDIRECT_TO')), + 'invalid_short_url' => env('DEFAULT_INVALID_SHORT_URL_REDIRECT'), + 'regular_404' => env('DEFAULT_REGULAR_404_REDIRECT'), + 'base_url' => env('DEFAULT_BASE_URL_REDIRECT'), ], 'url_shortener' => [ diff --git a/config/autoload/url-shortener.global.php b/config/autoload/url-shortener.global.php index e14ceddb..1e5df0a3 100644 --- a/config/autoload/url-shortener.global.php +++ b/config/autoload/url-shortener.global.php @@ -12,27 +12,14 @@ return (static function (): array { (int) env('DEFAULT_SHORT_CODES_LENGTH', DEFAULT_SHORT_CODES_LENGTH), MIN_SHORT_CODES_LENGTH, ); - $resolveSchema = static function (): string { - // Deprecated. For v3, IS_HTTPS_ENABLED should be true by default, instead of null -// return ((bool) env('IS_HTTPS_ENABLED', true)) ? 'https' : 'http'; - $isHttpsEnabled = env('IS_HTTPS_ENABLED', env('USE_HTTPS')); - if ($isHttpsEnabled !== null) { - $boolIsHttpsEnabled = (bool) $isHttpsEnabled; - return $boolIsHttpsEnabled ? 'https' : 'http'; - } - - return env('SHORT_DOMAIN_SCHEMA', 'http'); - }; return [ 'url_shortener' => [ 'domain' => [ - // Deprecated SHORT_DOMAIN_* env vars - 'schema' => $resolveSchema(), - 'hostname' => env('DEFAULT_DOMAIN', env('SHORT_DOMAIN_HOST', '')), + 'schema' => ((bool) env('IS_HTTPS_ENABLED', true)) ? 'https' : 'http', + 'hostname' => env('DEFAULT_DOMAIN', ''), ], - 'validate_url' => (bool) env('VALIDATE_URLS', false), // Deprecated 'default_short_codes_length' => $shortCodesLength, 'auto_resolve_titles' => (bool) env('AUTO_RESOLVE_TITLES', false), 'append_extra_path' => (bool) env('REDIRECT_APPEND_EXTRA_PATH', false), diff --git a/config/config.php b/config/config.php index ccb61cbb..c62828f3 100644 --- a/config/config.php +++ b/config/config.php @@ -37,10 +37,7 @@ return (new ConfigAggregator\ConfigAggregator([ new ConfigAggregator\PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'), env('APP_ENV') === 'test' ? new ConfigAggregator\PhpFileProvider('config/test/*.global.php') - // Deprecated. When the SimplifiedConfigParser is removed, load only generated_config.php here - : new ConfigAggregator\LaminasConfigProvider('config/params/{generated_config.php,*.config.{php,json}}'), + : new ConfigAggregator\LaminasConfigProvider('config/params/generated_config.php'), ], 'data/cache/app_config.php', [ - Core\Config\SimplifiedConfigParser::class, Core\Config\BasePathPrefixer::class, - Core\Config\DeprecatedConfigParser::class, ]))->getMergedConfig(); diff --git a/docs/swagger/paths/v1_short-urls_{shortCode}.json b/docs/swagger/paths/v1_short-urls_{shortCode}.json index eec1cec3..2f7a9600 100644 --- a/docs/swagger/paths/v1_short-urls_{shortCode}.json +++ b/docs/swagger/paths/v1_short-urls_{shortCode}.json @@ -320,7 +320,7 @@ }, "example": { "title": "Cannot delete short URL", - "type": "INVALID_SHORTCODE_DELETION", + "type": "INVALID_SHORT_URL_DELETION", "detail": "Impossible to delete short URL with short code \"abc123\", since it has more than \"15\" visits.", "status": 422, "shortCode": "abc123", diff --git a/docs/swagger/paths/v1_short-urls_{shortCode}_tags.json b/docs/swagger/paths/v1_short-urls_{shortCode}_tags.json deleted file mode 100644 index 645c6ef2..00000000 --- a/docs/swagger/paths/v1_short-urls_{shortCode}_tags.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "put": { - "deprecated": true, - "operationId": "editShortUrlTags", - "tags": [ - "Short URLs" - ], - "summary": "Edit tags on short URL", - "description": "Edit the tags on URL identified by provided short code.
This endpoint is deprecated. Use the [Edit short URL](#/Short%20URLs/editShortUrl) endpoint to edit tags.", - "parameters": [ - { - "$ref": "../parameters/version.json" - }, - { - "name": "shortCode", - "in": "path", - "description": "The short code for the short URL in which we want to edit tags.", - "required": true, - "schema": { - "type": "string" - } - }, - { - "$ref": "../parameters/domain.json" - } - ], - "requestBody": { - "description": "Request body.", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "tags" - ], - "properties": { - "tags": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The list of tags to set to the short URL." - } - } - } - } - } - }, - "security": [ - { - "ApiKey": [] - } - ], - "responses": { - "200": { - "description": "List of tags.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "tags": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - } - } - }, - "400": { - "description": "The request body does not contain a \"tags\" param with array type.", - "content": { - "application/problem+json": { - "schema": { - "$ref": "../definitions/Error.json" - } - } - } - }, - "404": { - "description": "No short URL was found for provided short code.", - "content": { - "application/json": { - "schema": { - "$ref": "../definitions/Error.json" - } - } - } - }, - "default": { - "description": "Unexpected error.", - "content": { - "application/json": { - "schema": { - "$ref": "../definitions/Error.json" - } - } - } - } - } - } -} diff --git a/docs/swagger/paths/v1_tags.json b/docs/swagger/paths/v1_tags.json index 12cdef81..b4fca99c 100644 --- a/docs/swagger/paths/v1_tags.json +++ b/docs/swagger/paths/v1_tags.json @@ -110,84 +110,6 @@ } }, - "post": { - "deprecated": true, - "operationId": "createTags", - "tags": [ - "Tags" - ], - "summary": "Create tags", - "description": "Provided a list of tags, creates all that do not yet exist
This endpoint is deprecated, as tags are automatically created while creating a short URL", - "security": [ - { - "ApiKey": [] - } - ], - "parameters": [ - { - "$ref": "../parameters/version.json" - } - ], - "requestBody": { - "description": "Request body.", - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "tags" - ], - "properties": { - "tags": { - "description": "The list of tag names to create", - "type": "array", - "items": { - "type": "string" - } - } - } - } - } - } - }, - "responses": { - "200": { - "description": "The list of tags", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "tags": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - } - } - } - } - }, - "default": { - "description": "Unexpected error.", - "content": { - "application/problem+json": { - "schema": { - "$ref": "../definitions/Error.json" - } - } - } - } - } - }, - "put": { "operationId": "renameTag", "tags": [ diff --git a/docs/swagger/paths/{shortCode}_qr-code_{size}.json b/docs/swagger/paths/{shortCode}_qr-code_{size}.json deleted file mode 100644 index 54c5152e..00000000 --- a/docs/swagger/paths/{shortCode}_qr-code_{size}.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "get": { - "operationId": "shortUrlQrCodeSize", - "deprecated": true, - "tags": [ - "URL Shortener" - ], - "summary": "Short URL QR code", - "description": "Generates a QR code image pointing to a short URL", - "parameters": [ - { - "name": "shortCode", - "in": "path", - "description": "The short code to resolve.", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "size", - "in": "path", - "description": "The size of the image to be returned.", - "required": true, - "schema": { - "type": "integer", - "minimum": 50, - "maximum": 1000, - "default": 300 - } - }, - { - "name": "format", - "in": "query", - "description": "The format for the QR code image, being valid values png and svg. Not providing the param or providing any other value will fall back to png.", - "required": false, - "schema": { - "type": "string", - "enum": [ - "png", - "svg" - ] - } - } - ], - "responses": { - "200": { - "description": "QR code in PNG format", - "content": { - "image/png": { - "schema": { - "type": "string", - "format": "binary" - } - }, - "image/svg+xml": { - "schema": { - "type": "string", - "format": "binary" - } - } - } - } - } - } -} diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json index 705069cc..8e71f362 100644 --- a/docs/swagger/swagger.json +++ b/docs/swagger/swagger.json @@ -78,9 +78,6 @@ "/rest/v{version}/short-urls/{shortCode}": { "$ref": "paths/v1_short-urls_{shortCode}.json" }, - "/rest/v{version}/short-urls/{shortCode}/tags": { - "$ref": "paths/v1_short-urls_{shortCode}_tags.json" - }, "/rest/v{version}/tags": { "$ref": "paths/v1_tags.json" @@ -122,9 +119,6 @@ }, "/{shortCode}/qr-code": { "$ref": "paths/{shortCode}_qr-code.json" - }, - "/{shortCode}/qr-code/{size}": { - "$ref": "paths/{shortCode}_qr-code_{size}.json" } } } diff --git a/module/CLI/config/cli.config.php b/module/CLI/config/cli.config.php index e06ad727..2b5b5afd 100644 --- a/module/CLI/config/cli.config.php +++ b/module/CLI/config/cli.config.php @@ -22,7 +22,6 @@ return [ Command\Api\ListKeysCommand::NAME => Command\Api\ListKeysCommand::class, Command\Tag\ListTagsCommand::NAME => Command\Tag\ListTagsCommand::class, - Command\Tag\CreateTagCommand::NAME => Command\Tag\CreateTagCommand::class, Command\Tag\RenameTagCommand::NAME => Command\Tag\RenameTagCommand::class, Command\Tag\DeleteTagsCommand::NAME => Command\Tag\DeleteTagsCommand::class, diff --git a/module/CLI/config/dependencies.config.php b/module/CLI/config/dependencies.config.php index 41d415dc..da23b0f6 100644 --- a/module/CLI/config/dependencies.config.php +++ b/module/CLI/config/dependencies.config.php @@ -53,7 +53,6 @@ return [ Command\Api\ListKeysCommand::class => ConfigAbstractFactory::class, Command\Tag\ListTagsCommand::class => ConfigAbstractFactory::class, - Command\Tag\CreateTagCommand::class => ConfigAbstractFactory::class, Command\Tag\RenameTagCommand::class => ConfigAbstractFactory::class, Command\Tag\DeleteTagsCommand::class => ConfigAbstractFactory::class, @@ -101,7 +100,6 @@ return [ Command\Api\ListKeysCommand::class => [ApiKeyService::class], Command\Tag\ListTagsCommand::class => [TagService::class], - Command\Tag\CreateTagCommand::class => [TagService::class], Command\Tag\RenameTagCommand::class => [TagService::class], Command\Tag\DeleteTagsCommand::class => [TagService::class], diff --git a/module/CLI/src/Command/Api/GenerateKeyCommand.php b/module/CLI/src/Command/Api/GenerateKeyCommand.php index a43c9e65..2655d1fb 100644 --- a/module/CLI/src/Command/Api/GenerateKeyCommand.php +++ b/module/CLI/src/Command/Api/GenerateKeyCommand.php @@ -6,11 +6,11 @@ namespace Shlinkio\Shlink\CLI\Command\Api; use Cake\Chronos\Chronos; use Shlinkio\Shlink\CLI\ApiKey\RoleResolverInterface; -use Shlinkio\Shlink\CLI\Command\BaseCommand; use Shlinkio\Shlink\CLI\Util\ExitCodes; use Shlinkio\Shlink\CLI\Util\ShlinkTable; use Shlinkio\Shlink\Rest\ApiKey\Role; use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -19,7 +19,7 @@ use Symfony\Component\Console\Style\SymfonyStyle; use function Shlinkio\Shlink\Core\arrayToString; use function sprintf; -class GenerateKeyCommand extends BaseCommand +class GenerateKeyCommand extends Command { public const NAME = 'api-key:generate'; @@ -63,7 +63,7 @@ class GenerateKeyCommand extends BaseCommand InputOption::VALUE_REQUIRED, 'The name by which this API key will be known.', ) - ->addOptionWithDeprecatedFallback( + ->addOption( 'expiration-date', 'e', InputOption::VALUE_REQUIRED, @@ -86,7 +86,7 @@ class GenerateKeyCommand extends BaseCommand protected function execute(InputInterface $input, OutputInterface $output): ?int { - $expirationDate = $this->getOptionWithDeprecatedFallback($input, 'expiration-date'); + $expirationDate = $input->getOption('expiration-date'); $apiKey = $this->apiKeyService->create( isset($expirationDate) ? Chronos::parse($expirationDate) : null, $input->getOption('name'), diff --git a/module/CLI/src/Command/Api/ListKeysCommand.php b/module/CLI/src/Command/Api/ListKeysCommand.php index 23258993..0a331086 100644 --- a/module/CLI/src/Command/Api/ListKeysCommand.php +++ b/module/CLI/src/Command/Api/ListKeysCommand.php @@ -4,12 +4,12 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Command\Api; -use Shlinkio\Shlink\CLI\Command\BaseCommand; use Shlinkio\Shlink\CLI\Util\ExitCodes; use Shlinkio\Shlink\CLI\Util\ShlinkTable; use Shlinkio\Shlink\Rest\ApiKey\Role; use Shlinkio\Shlink\Rest\Entity\ApiKey; use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -19,7 +19,7 @@ use function Functional\map; use function implode; use function sprintf; -class ListKeysCommand extends BaseCommand +class ListKeysCommand extends Command { private const ERROR_STRING_PATTERN = '%s'; private const SUCCESS_STRING_PATTERN = '%s'; @@ -37,7 +37,7 @@ class ListKeysCommand extends BaseCommand $this ->setName(self::NAME) ->setDescription('Lists all the available API keys.') - ->addOptionWithDeprecatedFallback( + ->addOption( 'enabled-only', 'e', InputOption::VALUE_NONE, @@ -47,7 +47,7 @@ class ListKeysCommand extends BaseCommand protected function execute(InputInterface $input, OutputInterface $output): ?int { - $enabledOnly = $this->getOptionWithDeprecatedFallback($input, 'enabled-only'); + $enabledOnly = $input->getOption('enabled-only'); $rows = map($this->apiKeyService->listKeys($enabledOnly), function (ApiKey $apiKey) use ($enabledOnly) { $expiration = $apiKey->getExpirationDate(); diff --git a/module/CLI/src/Command/BaseCommand.php b/module/CLI/src/Command/BaseCommand.php deleted file mode 100644 index fbee8681..00000000 --- a/module/CLI/src/Command/BaseCommand.php +++ /dev/null @@ -1,47 +0,0 @@ -addOption($name, $shortcut, $mode, $description, $default); - - if (str_contains($name, '-')) { - $camelCaseName = kebabCaseToCamelCase($name); - $this->addOption($camelCaseName, null, $mode, sprintf('[DEPRECATED] Alias for "%s".', $name), $default); - } - - return $this; - } - - // @phpstan-ignore-next-line - protected function getOptionWithDeprecatedFallback(InputInterface $input, string $name) // phpcs:ignore - { - $rawInput = method_exists($input, '__toString') ? $input->__toString() : ''; - $camelCaseName = kebabCaseToCamelCase($name); - $resolvedOptionName = str_contains($rawInput, $camelCaseName) ? $camelCaseName : $name; - - return $input->getOption($resolvedOptionName); - } -} diff --git a/module/CLI/src/Command/ShortUrl/CreateShortUrlCommand.php b/module/CLI/src/Command/ShortUrl/CreateShortUrlCommand.php index 62b50456..3334ae6a 100644 --- a/module/CLI/src/Command/ShortUrl/CreateShortUrlCommand.php +++ b/module/CLI/src/Command/ShortUrl/CreateShortUrlCommand.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Command\ShortUrl; -use Shlinkio\Shlink\CLI\Command\BaseCommand; use Shlinkio\Shlink\CLI\Util\ExitCodes; use Shlinkio\Shlink\Core\Exception\InvalidUrlException; use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; @@ -12,6 +11,7 @@ use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Service\UrlShortenerInterface; use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface; use Shlinkio\Shlink\Core\Validation\ShortUrlInputFilter; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -22,11 +22,9 @@ use function array_map; use function Functional\curry; use function Functional\flatten; use function Functional\unique; -use function method_exists; use function sprintf; -use function str_contains; -class CreateShortUrlCommand extends BaseCommand +class CreateShortUrlCommand extends Command { public const NAME = 'short-url:create'; @@ -45,7 +43,6 @@ class CreateShortUrlCommand extends BaseCommand { $this ->setName(self::NAME) - ->setAliases(['short-url:generate']) // Deprecated ->setDescription('Generates a short URL for provided long URL and returns it') ->addArgument('longUrl', InputArgument::REQUIRED, 'The long URL to parse') ->addOption( @@ -54,33 +51,33 @@ class CreateShortUrlCommand extends BaseCommand InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Tags to apply to the new short URL', ) - ->addOptionWithDeprecatedFallback( + ->addOption( 'valid-since', 's', InputOption::VALUE_REQUIRED, 'The date from which this short URL will be valid. ' . 'If someone tries to access it before this date, it will not be found.', ) - ->addOptionWithDeprecatedFallback( + ->addOption( 'valid-until', 'u', InputOption::VALUE_REQUIRED, 'The date until which this short URL will be valid. ' . 'If someone tries to access it after this date, it will not be found.', ) - ->addOptionWithDeprecatedFallback( + ->addOption( 'custom-slug', 'c', InputOption::VALUE_REQUIRED, 'If provided, this slug will be used instead of generating a short code', ) - ->addOptionWithDeprecatedFallback( + ->addOption( 'max-visits', 'm', InputOption::VALUE_REQUIRED, 'This will limit the number of visits for this short URL.', ) - ->addOptionWithDeprecatedFallback( + ->addOption( 'find-if-exists', 'f', InputOption::VALUE_NONE, @@ -92,7 +89,7 @@ class CreateShortUrlCommand extends BaseCommand InputOption::VALUE_REQUIRED, 'The domain to which this short URL will be attached.', ) - ->addOptionWithDeprecatedFallback( + ->addOption( 'short-code-length', 'l', InputOption::VALUE_REQUIRED, @@ -104,12 +101,6 @@ class CreateShortUrlCommand extends BaseCommand InputOption::VALUE_NONE, 'Forces the long URL to be validated, regardless what is globally configured.', ) - ->addOption( - 'no-validate-url', - null, - InputOption::VALUE_NONE, - '[DEPRECATED] Forces the long URL to not be validated, regardless what is globally configured.', - ) ->addOption( 'crawlable', 'r', @@ -161,25 +152,19 @@ class CreateShortUrlCommand extends BaseCommand $explodeWithComma = curry('explode')(','); $tags = unique(flatten(array_map($explodeWithComma, $input->getOption('tags')))); - $customSlug = $this->getOptionWithDeprecatedFallback($input, 'custom-slug'); - $maxVisits = $this->getOptionWithDeprecatedFallback($input, 'max-visits'); - $shortCodeLength = $this->getOptionWithDeprecatedFallback( - $input, - 'short-code-length', - ) ?? $this->defaultShortCodeLength; - $doValidateUrl = $this->doValidateUrl($input); + $customSlug = $input->getOption('custom-slug'); + $maxVisits = $input->getOption('max-visits'); + $shortCodeLength = $input->getOption('short-code-length') ?? $this->defaultShortCodeLength; + $doValidateUrl = $input->getOption('validate-url'); try { $shortUrl = $this->urlShortener->shorten(ShortUrlMeta::fromRawData([ ShortUrlInputFilter::LONG_URL => $longUrl, - ShortUrlInputFilter::VALID_SINCE => $this->getOptionWithDeprecatedFallback($input, 'valid-since'), - ShortUrlInputFilter::VALID_UNTIL => $this->getOptionWithDeprecatedFallback($input, 'valid-until'), + ShortUrlInputFilter::VALID_SINCE => $input->getOption('valid-since'), + ShortUrlInputFilter::VALID_UNTIL => $input->getOption('valid-until'), ShortUrlInputFilter::CUSTOM_SLUG => $customSlug, ShortUrlInputFilter::MAX_VISITS => $maxVisits !== null ? (int) $maxVisits : null, - ShortUrlInputFilter::FIND_IF_EXISTS => $this->getOptionWithDeprecatedFallback( - $input, - 'find-if-exists', - ), + ShortUrlInputFilter::FIND_IF_EXISTS => $input->getOption('find-if-exists'), ShortUrlInputFilter::DOMAIN => $input->getOption('domain'), ShortUrlInputFilter::SHORT_CODE_LENGTH => $shortCodeLength, ShortUrlInputFilter::VALIDATE_URL => $doValidateUrl, @@ -199,20 +184,6 @@ class CreateShortUrlCommand extends BaseCommand } } - private function doValidateUrl(InputInterface $input): ?bool - { - $rawInput = method_exists($input, '__toString') ? $input->__toString() : ''; - - if (str_contains($rawInput, '--no-validate-url')) { - return false; - } - if (str_contains($rawInput, '--validate-url')) { - return true; - } - - return null; - } - private function getIO(InputInterface $input, OutputInterface $output): SymfonyStyle { return $this->io ?? ($this->io = new SymfonyStyle($input, $output)); diff --git a/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php b/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php index cbc6e3ee..83e7bc2e 100644 --- a/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php +++ b/module/CLI/src/Command/ShortUrl/ListShortUrlsCommand.php @@ -52,7 +52,7 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand 'The first page to list (10 items per page unless "--all" is provided).', '1', ) - ->addOptionWithDeprecatedFallback( + ->addOption( 'search-term', 'st', InputOption::VALUE_REQUIRED, @@ -64,14 +64,14 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand InputOption::VALUE_REQUIRED, 'A comma-separated list of tags to filter results.', ) - ->addOptionWithDeprecatedFallback( + ->addOption( 'order-by', 'o', InputOption::VALUE_REQUIRED, 'The field from which you want to order by. ' - . 'Define ordering dir by passing ASC or DESC after "," or "-".', + . 'Define ordering dir by passing ASC or DESC after "-" or ",".', ) - ->addOptionWithDeprecatedFallback( + ->addOption( 'show-tags', null, InputOption::VALUE_NONE, @@ -113,7 +113,7 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand $io = new SymfonyStyle($input, $output); $page = (int) $input->getOption('page'); - $searchTerm = $this->getOptionWithDeprecatedFallback($input, 'search-term'); + $searchTerm = $input->getOption('search-term'); $tags = $input->getOption('tags'); $tags = ! empty($tags) ? explode(',', $tags) : []; $all = $input->getOption('all'); @@ -175,7 +175,7 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand private function processOrderBy(InputInterface $input): ?string { - $orderBy = $this->getOptionWithDeprecatedFallback($input, 'order-by'); + $orderBy = $input->getOption('order-by'); if (empty($orderBy)) { return null; } @@ -195,7 +195,7 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand 'Date created' => $pickProp('dateCreated'), 'Visits count' => $pickProp('visitsCount'), ]; - if ($this->getOptionWithDeprecatedFallback($input, 'show-tags')) { + if ($input->getOption('show-tags')) { $columnsMap['Tags'] = static fn (array $shortUrl): string => implode(', ', $shortUrl['tags']); } if ($input->getOption('show-api-key')) { diff --git a/module/CLI/src/Command/Tag/CreateTagCommand.php b/module/CLI/src/Command/Tag/CreateTagCommand.php deleted file mode 100644 index 99eef614..00000000 --- a/module/CLI/src/Command/Tag/CreateTagCommand.php +++ /dev/null @@ -1,52 +0,0 @@ -setName(self::NAME) - ->setDescription('[Deprecated] Creates one or more tags.') - ->addOption( - 'name', - 't', - InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, - 'The name of the tags to create', - ); - } - - protected function execute(InputInterface $input, OutputInterface $output): ?int - { - $io = new SymfonyStyle($input, $output); - $tagNames = $input->getOption('name'); - - if (empty($tagNames)) { - $io->warning('You have to provide at least one tag name'); - return ExitCodes::EXIT_WARNING; - } - - $this->tagService->createTags($tagNames); - $io->success('Tags properly created'); - return ExitCodes::EXIT_SUCCESS; - } -} diff --git a/module/CLI/src/Command/Util/AbstractWithDateRangeCommand.php b/module/CLI/src/Command/Util/AbstractWithDateRangeCommand.php index 9d7f5723..c3e3c407 100644 --- a/module/CLI/src/Command/Util/AbstractWithDateRangeCommand.php +++ b/module/CLI/src/Command/Util/AbstractWithDateRangeCommand.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Command\Util; use Cake\Chronos\Chronos; -use Shlinkio\Shlink\CLI\Command\BaseCommand; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -14,7 +14,7 @@ use Throwable; use function is_string; use function sprintf; -abstract class AbstractWithDateRangeCommand extends BaseCommand +abstract class AbstractWithDateRangeCommand extends Command { private const START_DATE = 'start-date'; private const END_DATE = 'end-date'; @@ -23,18 +23,8 @@ abstract class AbstractWithDateRangeCommand extends BaseCommand { $this->doConfigure(); $this - ->addOptionWithDeprecatedFallback( - self::START_DATE, - 's', - InputOption::VALUE_REQUIRED, - $this->getStartDateDesc(self::START_DATE), - ) - ->addOptionWithDeprecatedFallback( - self::END_DATE, - 'e', - InputOption::VALUE_REQUIRED, - $this->getEndDateDesc(self::END_DATE), - ); + ->addOption(self::START_DATE, 's', InputOption::VALUE_REQUIRED, $this->getStartDateDesc(self::START_DATE)) + ->addOption(self::END_DATE, 'e', InputOption::VALUE_REQUIRED, $this->getEndDateDesc(self::END_DATE)); } protected function getStartDateOption(InputInterface $input, OutputInterface $output): ?Chronos @@ -49,7 +39,7 @@ abstract class AbstractWithDateRangeCommand extends BaseCommand private function getDateOption(InputInterface $input, OutputInterface $output, string $key): ?Chronos { - $value = $this->getOptionWithDeprecatedFallback($input, $key); + $value = $input->getOption($key); if (empty($value) || ! is_string($value)) { return null; } diff --git a/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php index 08389d61..3ec90412 100644 --- a/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php @@ -149,7 +149,7 @@ class CreateShortUrlCommandTest extends TestCase * @test * @dataProvider provideFlags */ - public function urlValidationHasExpectedValueBasedOnProvidedTags(array $options, ?bool $expectedValidateUrl): void + public function urlValidationHasExpectedValueBasedOnProvidedFlags(array $options, ?bool $expectedValidateUrl): void { $shortUrl = ShortUrl::createEmpty(); $urlToShortCode = $this->urlShortener->shorten( @@ -168,8 +168,6 @@ class CreateShortUrlCommandTest extends TestCase public function provideFlags(): iterable { yield 'no flags' => [[], null]; - yield 'no-validate-url only' => [['--no-validate-url' => true], false]; yield 'validate-url' => [['--validate-url' => true], true]; - yield 'both flags' => [['--validate-url' => true, '--no-validate-url' => true], false]; } } diff --git a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php index 4a974d73..e7dae690 100644 --- a/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/ListShortUrlsCommandTest.php @@ -241,7 +241,7 @@ class ListShortUrlsCommandTest extends TestCase * @test * @dataProvider provideOrderBy */ - public function orderByIsProperlyComputed(array $commandArgs, string|array|null $expectedOrderBy): void + public function orderByIsProperlyComputed(array $commandArgs, ?string $expectedOrderBy): void { $listShortUrls = $this->shortUrlService->listShortUrls(ShortUrlsParams::fromRawData([ 'orderBy' => $expectedOrderBy, @@ -257,8 +257,9 @@ class ListShortUrlsCommandTest extends TestCase { yield [[], null]; yield [['--order-by' => 'foo'], 'foo']; - yield [['--order-by' => 'foo,ASC'], ['foo' => 'ASC']]; - yield [['--order-by' => 'bar,DESC'], ['bar' => 'DESC']]; + yield [['--order-by' => 'foo,ASC'], 'foo-ASC']; + yield [['--order-by' => 'bar,DESC'], 'bar-DESC']; + yield [['--order-by' => 'baz-DESC'], 'baz-DESC']; } /** @test */ diff --git a/module/CLI/test/Command/Tag/CreateTagCommandTest.php b/module/CLI/test/Command/Tag/CreateTagCommandTest.php deleted file mode 100644 index 7062cb45..00000000 --- a/module/CLI/test/Command/Tag/CreateTagCommandTest.php +++ /dev/null @@ -1,51 +0,0 @@ -tagService = $this->prophesize(TagServiceInterface::class); - $this->commandTester = $this->testerForCommand(new CreateTagCommand($this->tagService->reveal())); - } - - /** @test */ - public function errorIsReturnedWhenNoTagsAreProvided(): void - { - $this->commandTester->execute([]); - - $output = $this->commandTester->getDisplay(); - self::assertStringContainsString('You have to provide at least one tag name', $output); - } - - /** @test */ - public function serviceIsInvokedOnSuccess(): void - { - $tagNames = ['foo', 'bar']; - $createTags = $this->tagService->createTags($tagNames)->willReturn(new ArrayCollection()); - - $this->commandTester->execute([ - '--name' => $tagNames, - ]); - $output = $this->commandTester->getDisplay(); - - self::assertStringContainsString('Tags properly created', $output); - $createTags->shouldHaveBeenCalled(); - } -} diff --git a/module/Core/config/routes.config.php b/module/Core/config/routes.config.php index c3f4b66a..07e33c73 100644 --- a/module/Core/config/routes.config.php +++ b/module/Core/config/routes.config.php @@ -43,16 +43,6 @@ return [ ], 'allowed_methods' => [RequestMethod::METHOD_GET], ], - - // Deprecated - [ - 'name' => 'old_' . Action\QrCodeAction::class, - 'path' => '/{shortCode}/qr-code/{size:[0-9]+}', - 'middleware' => [ - Action\QrCodeAction::class, - ], - 'allowed_methods' => [RequestMethod::METHOD_GET], - ], ], ]; diff --git a/module/Core/src/Action/Model/QrCodeParams.php b/module/Core/src/Action/Model/QrCodeParams.php index 03643e4c..42d643d3 100644 --- a/module/Core/src/Action/Model/QrCodeParams.php +++ b/module/Core/src/Action/Model/QrCodeParams.php @@ -16,7 +16,6 @@ use Endroid\QrCode\Writer\PngWriter; use Endroid\QrCode\Writer\SvgWriter; use Endroid\QrCode\Writer\WriterInterface; use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Message\ServerRequestInterface as Request; use Shlinkio\Shlink\Core\Options\QrCodeOptions; use function Functional\contains; @@ -43,7 +42,7 @@ final class QrCodeParams $query = $request->getQueryParams(); return new self( - self::resolveSize($request, $query, $defaults), + self::resolveSize($query, $defaults), self::resolveMargin($query, $defaults), self::resolveWriter($query, $defaults), self::resolveErrorCorrection($query, $defaults), @@ -51,10 +50,9 @@ final class QrCodeParams ); } - private static function resolveSize(Request $request, array $query, QrCodeOptions $defaults): int + private static function resolveSize(array $query, QrCodeOptions $defaults): int { - // FIXME Size attribute is deprecated. After v3.0.0, always use the query param instead - $size = (int) $request->getAttribute('size', $query['size'] ?? $defaults->size()); + $size = (int) ($query['size'] ?? $defaults->size()); if ($size < self::MIN_SIZE) { return self::MIN_SIZE; } diff --git a/module/Core/src/Config/DeprecatedConfigParser.php b/module/Core/src/Config/DeprecatedConfigParser.php deleted file mode 100644 index b3421146..00000000 --- a/module/Core/src/Config/DeprecatedConfigParser.php +++ /dev/null @@ -1,41 +0,0 @@ - ['tracking', 'disable_track_param'], - 'short_domain_schema' => ['url_shortener', 'domain', 'schema'], - 'short_domain_host' => ['url_shortener', 'domain', 'hostname'], - 'validate_url' => ['url_shortener', 'validate_url'], - 'invalid_short_url_redirect_to' => ['not_found_redirects', 'invalid_short_url'], - 'regular_404_redirect_to' => ['not_found_redirects', 'regular_404'], - 'base_url_redirect_to' => ['not_found_redirects', 'base_url'], - 'db_config' => ['entity_manager', 'connection'], - 'delete_short_url_threshold' => ['delete_short_urls', 'visits_threshold'], - 'redis_servers' => ['cache', 'redis', 'servers'], - 'base_path' => ['router', 'base_path'], - 'web_worker_num' => ['mezzio-swoole', 'swoole-http-server', 'options', 'worker_num'], - 'task_worker_num' => ['mezzio-swoole', 'swoole-http-server', 'options', 'task_worker_num'], - 'visits_webhooks' => ['url_shortener', 'visits_webhooks'], - 'default_short_codes_length' => ['url_shortener', 'default_short_codes_length'], - 'geolite_license_key' => ['geolite2', 'license_key'], - 'mercure_public_hub_url' => ['mercure', 'public_hub_url'], - 'mercure_internal_hub_url' => ['mercure', 'internal_hub_url'], - 'mercure_jwt_secret' => ['mercure', 'jwt_secret'], - 'anonymize_remote_addr' => ['tracking', 'anonymize_remote_addr'], - 'redirect_status_code' => ['url_shortener', 'redirect_status_code'], - 'redirect_cache_lifetime' => ['url_shortener', 'redirect_cache_lifetime'], - 'port' => ['mezzio-swoole', 'swoole-http-server', 'port'], - ]; - private const SIMPLIFIED_CONFIG_SIDE_EFFECTS = [ - 'delete_short_url_threshold' => [ - 'path' => ['delete_short_urls', 'check_visits_threshold'], - 'value' => true, - ], - 'redis_servers' => [ - 'path' => ['dependencies', 'aliases', 'lock_store'], - 'value' => 'redis_lock_store', - ], - ]; - private const SIMPLIFIED_MERGEABLE_CONFIG = ['db_config']; - - public function __invoke(array $config): array - { - $configForExistingKeys = $this->getConfigForKeysInMappingOrderedByMapping($config); - - return reduce_left($configForExistingKeys, function ($value, string $key, $c, PathCollection $collection) { - $path = self::SIMPLIFIED_CONFIG_MAPPING[$key]; - if (contains(self::SIMPLIFIED_MERGEABLE_CONFIG, $key)) { - $value = ArrayUtils::merge($collection->getValueInPath($path), $value); - } - - $collection->setValueInPath($value, $path); - if (array_key_exists($key, self::SIMPLIFIED_CONFIG_SIDE_EFFECTS)) { - ['path' => $sideEffectPath, 'value' => $sideEffectValue] = self::SIMPLIFIED_CONFIG_SIDE_EFFECTS[$key]; - $collection->setValueInPath($sideEffectValue, $sideEffectPath); - } - - return $collection; - }, new PathCollection($config))->toArray(); - } - - private function getConfigForKeysInMappingOrderedByMapping(array $config): array - { - // Ignore any config which is not defined in the mapping - $configForExistingKeys = array_intersect_key($config, self::SIMPLIFIED_CONFIG_MAPPING); - - // Order the config by their key, based on the order it was defined in the mapping. - // This mainly allows deprecating keys and defining new ones that will replace the older and always take - // preference, while the old one keeps working for backwards compatibility if the new one is not provided. - $simplifiedConfigOrder = array_flip(array_keys(self::SIMPLIFIED_CONFIG_MAPPING)); - uksort( - $configForExistingKeys, - fn (string $a, string $b): int => $simplifiedConfigOrder[$a] - $simplifiedConfigOrder[$b], - ); - - return $configForExistingKeys; - } -} diff --git a/module/Core/src/Exception/DeleteShortUrlException.php b/module/Core/src/Exception/DeleteShortUrlException.php index 98919b35..e6f3bd0d 100644 --- a/module/Core/src/Exception/DeleteShortUrlException.php +++ b/module/Core/src/Exception/DeleteShortUrlException.php @@ -16,7 +16,7 @@ class DeleteShortUrlException extends DomainException implements ProblemDetailsE use CommonProblemDetailsExceptionTrait; private const TITLE = 'Cannot delete short URL'; - private const TYPE = 'INVALID_SHORTCODE_DELETION'; // FIXME Deprecated: Should be INVALID_SHORT_URL_DELETION + private const TYPE = 'INVALID_SHORT_URL_DELETION'; public static function fromVisitsThreshold(int $threshold, ShortUrlIdentifier $identifier): self { diff --git a/module/Core/src/Model/ShortUrlsOrdering.php b/module/Core/src/Model/ShortUrlsOrdering.php index 4184fcc6..2466b571 100644 --- a/module/Core/src/Model/ShortUrlsOrdering.php +++ b/module/Core/src/Model/ShortUrlsOrdering.php @@ -8,9 +8,6 @@ use Shlinkio\Shlink\Core\Exception\ValidationException; use function array_pad; use function explode; -use function is_array; -use function is_string; -use function key; final class ShortUrlsOrdering { @@ -41,22 +38,9 @@ final class ShortUrlsOrdering return; } - // FIXME Providing the ordering as array is considered deprecated. To be removed in v3.0.0 - $isArray = is_array($orderBy); - if (! $isArray && ! is_string($orderBy)) { - throw ValidationException::fromArray([ - 'orderBy' => '"Order by" must be an array, string or null', - ]); - } - - if (! $isArray) { - [$field, $dir] = array_pad(explode('-', $orderBy), 2, null); - $this->orderField = $field; - $this->orderDirection = $dir ?? self::DEFAULT_ORDER_DIRECTION; - } else { - $this->orderField = key($orderBy); - $this->orderDirection = $orderBy[$this->orderField]; - } + [$field, $dir] = array_pad(explode('-', $orderBy), 2, null); + $this->orderField = $field; + $this->orderDirection = $dir ?? self::DEFAULT_ORDER_DIRECTION; } public function orderField(): ?string diff --git a/module/Core/src/Options/AppOptions.php b/module/Core/src/Options/AppOptions.php index 8fde2663..e81f9fdb 100644 --- a/module/Core/src/Options/AppOptions.php +++ b/module/Core/src/Options/AppOptions.php @@ -10,8 +10,8 @@ use function sprintf; class AppOptions extends AbstractOptions { - private string $name = ''; - private string $version = '1.0'; + private string $name = 'Shlink'; + private string $version = '3.0.0'; public function getName(): string { @@ -35,13 +35,6 @@ class AppOptions extends AbstractOptions return $this; } - /** @deprecated */ - protected function setDisableTrackParam(?string $disableTrackParam): self - { - // Keep just for backwards compatibility during hydration - return $this; - } - public function __toString(): string { return sprintf('%s:v%s', $this->name, $this->version); diff --git a/module/Core/src/Options/UrlShortenerOptions.php b/module/Core/src/Options/UrlShortenerOptions.php index f760220e..ecbbb590 100644 --- a/module/Core/src/Options/UrlShortenerOptions.php +++ b/module/Core/src/Options/UrlShortenerOptions.php @@ -77,16 +77,4 @@ class UrlShortenerOptions extends AbstractOptions { $this->appendExtraPath = $appendExtraPath; } - - /** @deprecated */ - protected function setAnonymizeRemoteAddr(bool $anonymizeRemoteAddr): void - { - // Keep just for backwards compatibility during hydration - } - - /** @deprecated */ - protected function setTrackOrphanVisits(bool $trackOrphanVisits): void - { - // Keep just for backwards compatibility during hydration - } } diff --git a/module/Core/src/Repository/ShortUrlRepository.php b/module/Core/src/Repository/ShortUrlRepository.php index fb853b96..e1b9c419 100644 --- a/module/Core/src/Repository/ShortUrlRepository.php +++ b/module/Core/src/Repository/ShortUrlRepository.php @@ -56,8 +56,7 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU $fieldName = $orderBy->orderField(); $order = $orderBy->orderDirection(); - // visitsCount and visitCount are deprecated. Only visits should work - if (contains(['visits', 'visitsCount', 'visitCount'], $fieldName)) { + if ($fieldName === 'visits') { // FIXME This query is inefficient. Debug it. $qb->addSelect('COUNT(DISTINCT v) AS totalVisits') ->leftJoin('s.visits', 'v') @@ -67,17 +66,9 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU return array_column($qb->getQuery()->getResult(), 0); } - // Map public field names to column names - $fieldNameMap = [ - 'originalUrl' => 'longUrl', // Deprecated - 'longUrl' => 'longUrl', - 'shortCode' => 'shortCode', - 'dateCreated' => 'dateCreated', - 'title' => 'title', - ]; - $resolvedFieldName = $fieldNameMap[$fieldName] ?? null; - if ($resolvedFieldName !== null) { - $qb->orderBy('s.' . $resolvedFieldName, $order); + $orderableFields = ['longUrl', 'shortCode', 'dateCreated', 'title']; + if (contains($orderableFields, $fieldName)) { + $qb->orderBy('s.' . $fieldName, $order); } return $qb->getQuery()->getResult(); diff --git a/module/Core/src/Tag/TagService.php b/module/Core/src/Tag/TagService.php index 61ed211d..c9248520 100644 --- a/module/Core/src/Tag/TagService.php +++ b/module/Core/src/Tag/TagService.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Tag; -use Doctrine\Common\Collections\Collection; use Doctrine\ORM; use Happyr\DoctrineSpecification\Spec; use Shlinkio\Shlink\Core\Entity\Tag; @@ -15,14 +14,11 @@ use Shlinkio\Shlink\Core\Repository\TagRepository; use Shlinkio\Shlink\Core\Repository\TagRepositoryInterface; use Shlinkio\Shlink\Core\Tag\Model\TagInfo; use Shlinkio\Shlink\Core\Tag\Model\TagRenaming; -use Shlinkio\Shlink\Core\Util\TagManagerTrait; use Shlinkio\Shlink\Rest\ApiKey\Spec\WithApiKeySpecsEnsuringJoin; use Shlinkio\Shlink\Rest\Entity\ApiKey; class TagService implements TagServiceInterface { - use TagManagerTrait; - public function __construct(private ORM\EntityManagerInterface $em) { } @@ -34,12 +30,10 @@ class TagService implements TagServiceInterface { /** @var TagRepository $repo */ $repo = $this->em->getRepository(Tag::class); - /** @var Tag[] $tags */ - $tags = $repo->match(Spec::andX( + return $repo->match(Spec::andX( Spec::orderBy('name'), new WithApiKeySpecsEnsuringJoin($apiKey), )); - return $tags; } /** @@ -67,21 +61,6 @@ class TagService implements TagServiceInterface $repo->deleteByName($tagNames); } - /** - * Provided a list of tag names, creates all that do not exist yet - * - * @deprecated - * @param string[] $tagNames - * @return Collection|Tag[] - */ - public function createTags(array $tagNames): Collection - { - $tags = $this->tagNamesToEntities($this->em, $tagNames); - $this->em->flush(); - - return $tags; - } - /** * @throws TagNotFoundException * @throws TagConflictException diff --git a/module/Core/src/Tag/TagServiceInterface.php b/module/Core/src/Tag/TagServiceInterface.php index 34cf1871..a1aa6122 100644 --- a/module/Core/src/Tag/TagServiceInterface.php +++ b/module/Core/src/Tag/TagServiceInterface.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Tag; -use Doctrine\Common\Collections\Collection; use Shlinkio\Shlink\Core\Entity\Tag; use Shlinkio\Shlink\Core\Exception\ForbiddenTagOperationException; use Shlinkio\Shlink\Core\Exception\TagConflictException; @@ -31,13 +30,6 @@ interface TagServiceInterface */ public function deleteTags(array $tagNames, ?ApiKey $apiKey = null): void; - /** - * @deprecated - * @param string[] $tagNames - * @return Collection|Tag[] - */ - public function createTags(array $tagNames): Collection; - /** * @throws TagNotFoundException * @throws TagConflictException diff --git a/module/Core/src/Util/TagManagerTrait.php b/module/Core/src/Util/TagManagerTrait.php deleted file mode 100644 index 9fac8700..00000000 --- a/module/Core/src/Util/TagManagerTrait.php +++ /dev/null @@ -1,37 +0,0 @@ - $tags, - ])->getValue(ShortUrlInputFilter::TAGS); - - $entities = map($normalizedTags, function (string $tagName) use ($em) { - $tag = $em->getRepository(Tag::class)->findOneBy(['name' => $tagName]) ?? new Tag($tagName); - $em->persist($tag); - - return $tag; - }); - - return new Collections\ArrayCollection($entities); - } -} diff --git a/module/Core/test/Action/QrCodeActionTest.php b/module/Core/test/Action/QrCodeActionTest.php index 664a51a2..419febec 100644 --- a/module/Core/test/Action/QrCodeActionTest.php +++ b/module/Core/test/Action/QrCodeActionTest.php @@ -154,18 +154,12 @@ class QrCodeActionTest extends TestCase ]; yield 'no size' => [[], ServerRequestFactory::fromGlobals(), 300]; yield 'no size, different default' => [['size' => 500], ServerRequestFactory::fromGlobals(), 500]; - yield 'size in attr' => [[], ServerRequestFactory::fromGlobals()->withAttribute('size', '400'), 400]; yield 'size in query' => [[], ServerRequestFactory::fromGlobals()->withQueryParams(['size' => '123']), 123]; yield 'size in query, default margin' => [ ['margin' => 25], ServerRequestFactory::fromGlobals()->withQueryParams(['size' => '123']), 173, ]; - yield 'size in query and attr' => [ - [], - ServerRequestFactory::fromGlobals()->withAttribute('size', '350')->withQueryParams(['size' => '123']), - 350, - ]; yield 'margin' => [[], ServerRequestFactory::fromGlobals()->withQueryParams(['margin' => '35']), 370]; yield 'margin and different default' => [ ['size' => 400], diff --git a/module/Core/test/Config/DeprecatedConfigParserTest.php b/module/Core/test/Config/DeprecatedConfigParserTest.php deleted file mode 100644 index c58d9050..00000000 --- a/module/Core/test/Config/DeprecatedConfigParserTest.php +++ /dev/null @@ -1,111 +0,0 @@ -postProcessor = new DeprecatedConfigParser(); - } - - /** @test */ - public function returnsConfigAsIsIfNewValueIsDefined(): void - { - $config = [ - 'not_found_redirects' => [ - 'invalid_short_url' => 'somewhere', - ], - ]; - - $result = ($this->postProcessor)($config); - - self::assertEquals($config, $result); - } - - /** @test */ - public function doesNotProvideNewConfigIfOldOneIsDefinedButDisabled(): void - { - $config = [ - 'url_shortener' => [ - 'not_found_short_url' => [ - 'enable_redirection' => false, - 'redirect_to' => 'somewhere', - ], - ], - ]; - - $result = ($this->postProcessor)($config); - - self::assertEquals($config, $result); - } - - /** @test */ - public function mapsOldConfigToNewOneWhenOldOneIsEnabled(): void - { - $config = [ - 'url_shortener' => [ - 'not_found_short_url' => [ - 'enable_redirection' => true, - 'redirect_to' => 'somewhere', - ], - ], - ]; - $expected = array_merge($config, [ - 'not_found_redirects' => [ - 'invalid_short_url' => 'somewhere', - ], - ]); - - $result = ($this->postProcessor)($config); - - self::assertEquals($expected, $result); - } - - /** @test */ - public function definesNewConfigAsNullIfOldOneIsEnabledWithNoRedirectValue(): void - { - $config = [ - 'url_shortener' => [ - 'not_found_short_url' => [ - 'enable_redirection' => true, - ], - ], - ]; - $expected = array_merge($config, [ - 'not_found_redirects' => [ - 'invalid_short_url' => null, - ], - ]); - - $result = ($this->postProcessor)($config); - - self::assertEquals($expected, $result); - } - - /** @test */ - public function removesTheOldSecretKey(): void - { - $config = [ - 'app_options' => [ - 'secret_key' => 'foobar', - ], - ]; - $expected = [ - 'app_options' => [], - ]; - - $result = ($this->postProcessor)($config); - - self::assertEquals($expected, $result); - } -} diff --git a/module/Core/test/Config/SimplifiedConfigParserTest.php b/module/Core/test/Config/SimplifiedConfigParserTest.php deleted file mode 100644 index 48d41c00..00000000 --- a/module/Core/test/Config/SimplifiedConfigParserTest.php +++ /dev/null @@ -1,158 +0,0 @@ -postProcessor = new SimplifiedConfigParser(); - } - - /** @test */ - public function properlyMapsSimplifiedConfig(): void - { - $config = [ - 'tracking' => [ - 'disable_track_param' => 'foo', - ], - - 'entity_manager' => [ - 'connection' => [ - 'driver' => 'mysql', - 'host' => 'shlink_db_mysql', - 'port' => '3306', - ], - ], - ]; - $simplified = [ - 'disable_track_param' => 'bar', - 'short_domain_schema' => 'https', - 'short_domain_host' => 'doma.in', - 'validate_url' => true, - 'delete_short_url_threshold' => 50, - 'invalid_short_url_redirect_to' => 'foobar.com', - 'regular_404_redirect_to' => 'bar.com', - 'base_url_redirect_to' => 'foo.com', - 'redis_servers' => [ - 'tcp://1.1.1.1:1111', - 'tcp://1.2.2.2:2222', - ], - 'db_config' => [ - 'dbname' => 'shlink', - 'user' => 'foo', - 'password' => 'bar', - 'port' => '1234', - ], - 'base_path' => '/foo/bar', - 'task_worker_num' => 50, - 'visits_webhooks' => [ - 'http://my-api.com/api/v2.3/notify', - 'https://third-party.io/foo', - ], - 'default_short_codes_length' => 8, - 'geolite_license_key' => 'kjh23ljkbndskj345', - 'mercure_public_hub_url' => 'public_url', - 'mercure_internal_hub_url' => 'internal_url', - 'mercure_jwt_secret' => 'super_secret_value', - 'anonymize_remote_addr' => false, - 'redirect_status_code' => 301, - 'redirect_cache_lifetime' => 90, - 'port' => 8888, - ]; - $expected = [ - 'tracking' => [ - 'disable_track_param' => 'bar', - 'anonymize_remote_addr' => false, - ], - - 'entity_manager' => [ - 'connection' => [ - 'driver' => 'mysql', - 'host' => 'shlink_db_mysql', - 'dbname' => 'shlink', - 'user' => 'foo', - 'password' => 'bar', - 'port' => '1234', - ], - ], - - 'url_shortener' => [ - 'domain' => [ - 'schema' => 'https', - 'hostname' => 'doma.in', - ], - 'validate_url' => true, - 'visits_webhooks' => [ - 'http://my-api.com/api/v2.3/notify', - 'https://third-party.io/foo', - ], - 'default_short_codes_length' => 8, - 'redirect_status_code' => 301, - 'redirect_cache_lifetime' => 90, - ], - - 'delete_short_urls' => [ - 'visits_threshold' => 50, - 'check_visits_threshold' => true, - ], - - 'dependencies' => [ - 'aliases' => [ - 'lock_store' => 'redis_lock_store', - ], - ], - - 'cache' => [ - 'redis' => [ - 'servers' => [ - 'tcp://1.1.1.1:1111', - 'tcp://1.2.2.2:2222', - ], - ], - ], - - 'router' => [ - 'base_path' => '/foo/bar', - ], - - 'not_found_redirects' => [ - 'invalid_short_url' => 'foobar.com', - 'regular_404' => 'bar.com', - 'base_url' => 'foo.com', - ], - - 'mezzio-swoole' => [ - 'swoole-http-server' => [ - 'port' => 8888, - 'options' => [ - 'task_worker_num' => 50, - ], - ], - ], - - 'geolite2' => [ - 'license_key' => 'kjh23ljkbndskj345', - ], - - 'mercure' => [ - 'public_hub_url' => 'public_url', - 'internal_hub_url' => 'internal_url', - 'jwt_secret' => 'super_secret_value', - ], - ]; - - $result = ($this->postProcessor)(array_merge($config, $simplified)); - - self::assertEquals(array_merge($expected, $simplified), $result); - } -} diff --git a/module/Core/test/Exception/DeleteShortUrlExceptionTest.php b/module/Core/test/Exception/DeleteShortUrlExceptionTest.php index 8c616ce1..b331bdc2 100644 --- a/module/Core/test/Exception/DeleteShortUrlExceptionTest.php +++ b/module/Core/test/Exception/DeleteShortUrlExceptionTest.php @@ -37,7 +37,7 @@ class DeleteShortUrlExceptionTest extends TestCase 'threshold' => $threshold, ], $e->getAdditionalData()); self::assertEquals('Cannot delete short URL', $e->getTitle()); - self::assertEquals('INVALID_SHORTCODE_DELETION', $e->getType()); + self::assertEquals('INVALID_SHORT_URL_DELETION', $e->getType()); self::assertEquals(422, $e->getStatus()); } diff --git a/module/Core/test/Service/Tag/TagServiceTest.php b/module/Core/test/Service/Tag/TagServiceTest.php index 33ae7be0..ed8cba29 100644 --- a/module/Core/test/Service/Tag/TagServiceTest.php +++ b/module/Core/test/Service/Tag/TagServiceTest.php @@ -97,21 +97,6 @@ class TagServiceTest extends TestCase ); } - /** @test */ - public function createTagsPersistsEntities(): void - { - $find = $this->repo->findOneBy(Argument::cetera())->willReturn(new Tag('foo')); - $persist = $this->em->persist(Argument::type(Tag::class))->willReturn(null); - $flush = $this->em->flush()->willReturn(null); - - $result = $this->service->createTags(['foo', 'bar']); - - self::assertCount(2, $result); - $find->shouldHaveBeenCalled(); - $persist->shouldHaveBeenCalledTimes(2); - $flush->shouldHaveBeenCalled(); - } - /** * @test * @dataProvider provideAdminApiKeys diff --git a/module/Rest/config/dependencies.config.php b/module/Rest/config/dependencies.config.php index 98b385b0..7e48552e 100644 --- a/module/Rest/config/dependencies.config.php +++ b/module/Rest/config/dependencies.config.php @@ -30,14 +30,12 @@ return [ Action\ShortUrl\DeleteShortUrlAction::class => ConfigAbstractFactory::class, Action\ShortUrl\ResolveShortUrlAction::class => ConfigAbstractFactory::class, Action\ShortUrl\ListShortUrlsAction::class => ConfigAbstractFactory::class, - Action\ShortUrl\EditShortUrlTagsAction::class => ConfigAbstractFactory::class, Action\Visit\ShortUrlVisitsAction::class => ConfigAbstractFactory::class, Action\Visit\TagVisitsAction::class => ConfigAbstractFactory::class, Action\Visit\GlobalVisitsAction::class => ConfigAbstractFactory::class, Action\Visit\OrphanVisitsAction::class => ConfigAbstractFactory::class, Action\Tag\ListTagsAction::class => ConfigAbstractFactory::class, Action\Tag\DeleteTagsAction::class => ConfigAbstractFactory::class, - Action\Tag\CreateTagsAction::class => ConfigAbstractFactory::class, Action\Tag\UpdateTagAction::class => ConfigAbstractFactory::class, Action\Domain\ListDomainsAction::class => ConfigAbstractFactory::class, Action\Domain\DomainRedirectsAction::class => ConfigAbstractFactory::class, @@ -76,10 +74,8 @@ return [ Visit\Transformer\OrphanVisitDataTransformer::class, ], Action\ShortUrl\ListShortUrlsAction::class => [Service\ShortUrlService::class, ShortUrlDataTransformer::class], - Action\ShortUrl\EditShortUrlTagsAction::class => [Service\ShortUrlService::class], Action\Tag\ListTagsAction::class => [TagService::class], Action\Tag\DeleteTagsAction::class => [TagService::class], - Action\Tag\CreateTagsAction::class => [TagService::class], Action\Tag\UpdateTagAction::class => [TagService::class], Action\Domain\ListDomainsAction::class => [DomainService::class, Options\NotFoundRedirectOptions::class], Action\Domain\DomainRedirectsAction::class => [DomainService::class], diff --git a/module/Rest/config/routes.config.php b/module/Rest/config/routes.config.php index 991f4bb3..4af6304d 100644 --- a/module/Rest/config/routes.config.php +++ b/module/Rest/config/routes.config.php @@ -28,7 +28,6 @@ return [ Action\ShortUrl\DeleteShortUrlAction::getRouteDef([$dropDomainMiddleware]), Action\ShortUrl\ResolveShortUrlAction::getRouteDef([$dropDomainMiddleware]), Action\ShortUrl\ListShortUrlsAction::getRouteDef(), - Action\ShortUrl\EditShortUrlTagsAction::getRouteDef([$dropDomainMiddleware]), // Visits Action\Visit\ShortUrlVisitsAction::getRouteDef([$dropDomainMiddleware]), @@ -39,7 +38,6 @@ return [ // Tags Action\Tag\ListTagsAction::getRouteDef(), Action\Tag\DeleteTagsAction::getRouteDef(), - Action\Tag\CreateTagsAction::getRouteDef(), Action\Tag\UpdateTagAction::getRouteDef(), // Domains diff --git a/module/Rest/src/Action/ShortUrl/EditShortUrlTagsAction.php b/module/Rest/src/Action/ShortUrl/EditShortUrlTagsAction.php deleted file mode 100644 index feda3a62..00000000 --- a/module/Rest/src/Action/ShortUrl/EditShortUrlTagsAction.php +++ /dev/null @@ -1,47 +0,0 @@ -getParsedBody(); - - if (! isset($bodyParams['tags'])) { - throw ValidationException::fromArray([ - 'tags' => 'List of tags has to be provided', - ]); - } - ['tags' => $tags] = $bodyParams; - $identifier = ShortUrlIdentifier::fromApiRequest($request); - $apiKey = AuthenticationMiddleware::apiKeyFromRequest($request); - - $shortUrl = $this->shortUrlService->updateShortUrl($identifier, ShortUrlEdit::fromRawData([ - ShortUrlInputFilter::TAGS => $tags, - ]), $apiKey); - return new JsonResponse(['tags' => $shortUrl->getTags()->toArray()]); - } -} diff --git a/module/Rest/src/Action/Tag/CreateTagsAction.php b/module/Rest/src/Action/Tag/CreateTagsAction.php deleted file mode 100644 index 09c860f5..00000000 --- a/module/Rest/src/Action/Tag/CreateTagsAction.php +++ /dev/null @@ -1,35 +0,0 @@ -getParsedBody(); - $tags = $body['tags'] ?? []; - - return new JsonResponse([ - 'tags' => [ - 'data' => $this->tagService->createTags($tags)->toArray(), - ], - ]); - } -} diff --git a/module/Rest/src/Exception/MissingAuthenticationException.php b/module/Rest/src/Exception/MissingAuthenticationException.php index 4e1057bc..99dbc0df 100644 --- a/module/Rest/src/Exception/MissingAuthenticationException.php +++ b/module/Rest/src/Exception/MissingAuthenticationException.php @@ -24,10 +24,7 @@ class MissingAuthenticationException extends RuntimeException implements Problem 'Expected one of the following authentication headers, ["%s"], but none were provided', implode('", "', $expectedHeaders), )); - $e->additional = [ - 'expectedTypes' => $expectedHeaders, // Deprecated - 'expectedHeaders' => $expectedHeaders, - ]; + $e->additional = ['expectedHeaders' => $expectedHeaders]; return $e; } diff --git a/module/Rest/src/Middleware/BodyParserMiddleware.php b/module/Rest/src/Middleware/BodyParserMiddleware.php index 2711d900..8922de03 100644 --- a/module/Rest/src/Middleware/BodyParserMiddleware.php +++ b/module/Rest/src/Middleware/BodyParserMiddleware.php @@ -10,12 +10,8 @@ use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -use function array_shift; -use function explode; use function Functional\contains; -use function parse_str; use function Shlinkio\Shlink\Common\json_decode; -use function trim; class BodyParserMiddleware implements MiddlewareInterface, RequestMethodInterface { @@ -36,20 +32,7 @@ class BodyParserMiddleware implements MiddlewareInterface, RequestMethodInterfac return $handler->handle($request); } - // If the accepted content is JSON, try to parse the body from JSON - $contentType = $this->getRequestContentType($request); - if (contains(['application/json', 'text/json', 'application/x-json'], $contentType)) { - return $handler->handle($this->parseFromJson($request)); - } - - return $handler->handle($this->parseFromUrlEncoded($request)); - } - - private function getRequestContentType(Request $request): string - { - $contentType = $request->getHeaderLine('Content-type'); - $contentTypes = explode(';', $contentType); - return trim(array_shift($contentTypes)); + return $handler->handle($this->parseFromJson($request)); } private function parseFromJson(Request $request): Request @@ -62,20 +45,4 @@ class BodyParserMiddleware implements MiddlewareInterface, RequestMethodInterfac $parsedJson = json_decode($rawBody); return $request->withParsedBody($parsedJson); } - - /** - * @deprecated To be removed on Shlink v3.0.0, supporting only JSON requests. - */ - private function parseFromUrlEncoded(Request $request): Request - { - $rawBody = $request->getBody()->__toString(); - if (empty($rawBody)) { - return $request; - } - - $parsedBody = []; - parse_str($rawBody, $parsedBody); - - return $request->withParsedBody($parsedBody); - } } diff --git a/module/Rest/test-api/Action/DeleteShortUrlTest.php b/module/Rest/test-api/Action/DeleteShortUrlTest.php index bb512832..01b5b7bc 100644 --- a/module/Rest/test-api/Action/DeleteShortUrlTest.php +++ b/module/Rest/test-api/Action/DeleteShortUrlTest.php @@ -48,7 +48,7 @@ class DeleteShortUrlTest extends ApiTestCase self::assertEquals(self::STATUS_UNPROCESSABLE_ENTITY, $resp->getStatusCode()); self::assertEquals(self::STATUS_UNPROCESSABLE_ENTITY, $payload['status']); - self::assertEquals('INVALID_SHORTCODE_DELETION', $payload['type']); + self::assertEquals('INVALID_SHORT_URL_DELETION', $payload['type']); self::assertEquals($expectedDetail, $payload['detail']); self::assertEquals('Cannot delete short URL', $payload['title']); } diff --git a/module/Rest/test-api/Action/EditShortUrlTagsTest.php b/module/Rest/test-api/Action/EditShortUrlTagsTest.php deleted file mode 100644 index f940a52d..00000000 --- a/module/Rest/test-api/Action/EditShortUrlTagsTest.php +++ /dev/null @@ -1,94 +0,0 @@ -callApiWithKey(self::METHOD_PUT, '/short-urls/abc123/tags', [RequestOptions::JSON => []]); - $payload = $this->getJsonResponsePayload($resp); - - self::assertEquals(self::STATUS_BAD_REQUEST, $resp->getStatusCode()); - self::assertEquals(self::STATUS_BAD_REQUEST, $payload['status']); - self::assertEquals('INVALID_ARGUMENT', $payload['type']); - self::assertEquals($expectedDetail, $payload['detail']); - self::assertEquals('Invalid data', $payload['title']); - } - - /** - * @test - * @dataProvider provideInvalidUrls - */ - public function providingInvalidShortCodeReturnsBadRequest( - string $shortCode, - ?string $domain, - string $expectedDetail, - string $apiKey, - ): void { - $url = $this->buildShortUrlPath($shortCode, $domain, '/tags'); - $resp = $this->callApiWithKey(self::METHOD_PUT, $url, [RequestOptions::JSON => [ - 'tags' => ['foo', 'bar'], - ]], $apiKey); - $payload = $this->getJsonResponsePayload($resp); - - self::assertEquals(self::STATUS_NOT_FOUND, $resp->getStatusCode()); - self::assertEquals(self::STATUS_NOT_FOUND, $payload['status']); - self::assertEquals('INVALID_SHORTCODE', $payload['type']); - self::assertEquals($expectedDetail, $payload['detail']); - self::assertEquals('Short URL not found', $payload['title']); - self::assertEquals($shortCode, $payload['shortCode']); - self::assertEquals($domain, $payload['domain'] ?? null); - } - - /** @test */ - public function allowsEditingTagsWithTwoEndpoints(): void - { - $getUrlTagsFromApi = fn () => $this->getJsonResponsePayload( - $this->callApiWithKey(self::METHOD_GET, '/short-urls/abc123'), - )['tags'] ?? null; - self::assertEquals(['foo'], $getUrlTagsFromApi()); - - $this->callApiWithKey(self::METHOD_PUT, '/short-urls/abc123/tags', [RequestOptions::JSON => [ - 'tags' => ['a', 'e'], - ]]); - self::assertEquals(['a', 'e'], $getUrlTagsFromApi()); - - $this->callApiWithKey(self::METHOD_PATCH, '/short-urls/abc123', [RequestOptions::JSON => [ - 'tags' => ['i', 'o', 'u'], - ]]); - self::assertEquals(['i', 'o', 'u'], $getUrlTagsFromApi()); - } - - /** @test */ - public function tagsAreSetOnProperShortUrlBasedOnProvidedDomain(): void - { - $urlWithoutDomain = '/short-urls/ghi789/tags'; - $urlWithDomain = $urlWithoutDomain . '?domain=example.com'; - - $setTagsWithDomain = $this->callApiWithKey(self::METHOD_PUT, $urlWithDomain, [RequestOptions::JSON => [ - 'tags' => ['foo', 'bar'], - ]]); - $fetchWithoutDomain = $this->getJsonResponsePayload( - $this->callApiWithKey(self::METHOD_GET, '/short-urls/ghi789'), - ); - $fetchWithDomain = $this->getJsonResponsePayload( - $this->callApiWithKey(self::METHOD_GET, '/short-urls/ghi789?domain=example.com'), - ); - - self::assertEquals(self::STATUS_OK, $setTagsWithDomain->getStatusCode()); - self::assertEquals([], $fetchWithoutDomain['tags']); - self::assertEquals(['bar', 'foo'], $fetchWithDomain['tags']); - } -} diff --git a/module/Rest/test-api/Action/ListShortUrlsTest.php b/module/Rest/test-api/Action/ListShortUrlsTest.php index fcc07719..e3526756 100644 --- a/module/Rest/test-api/Action/ListShortUrlsTest.php +++ b/module/Rest/test-api/Action/ListShortUrlsTest.php @@ -155,14 +155,6 @@ class ListShortUrlsTest extends ApiTestCase self::SHORT_URL_DOCS, self::SHORT_URL_CUSTOM_DOMAIN, ], 'valid_api_key']; - yield [['orderBy' => ['shortCode' => 'DESC']], [ // Deprecated - 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' => 'shortCode-DESC'], [ self::SHORT_URL_DOCS, self::SHORT_URL_CUSTOM_DOMAIN, diff --git a/module/Rest/test/Action/ShortUrl/EditShortUrlTagsActionTest.php b/module/Rest/test/Action/ShortUrl/EditShortUrlTagsActionTest.php deleted file mode 100644 index 59c55d84..00000000 --- a/module/Rest/test/Action/ShortUrl/EditShortUrlTagsActionTest.php +++ /dev/null @@ -1,63 +0,0 @@ -shortUrlService = $this->prophesize(ShortUrlService::class); - $this->action = new EditShortUrlTagsAction($this->shortUrlService->reveal()); - } - - /** @test */ - public function notProvidingTagsReturnsError(): void - { - $this->expectException(ValidationException::class); - $this->action->handle($this->createRequestWithAPiKey()->withAttribute('shortCode', 'abc123')); - } - - /** @test */ - public function tagsListIsReturnedIfCorrectShortCodeIsProvided(): void - { - $shortCode = 'abc123'; - $this->shortUrlService->updateShortUrl( - new ShortUrlIdentifier($shortCode), - Argument::type(ShortUrlEdit::class), - Argument::type(ApiKey::class), - )->willReturn(ShortUrl::createEmpty()) - ->shouldBeCalledOnce(); - - $response = $this->action->handle( - $this->createRequestWithAPiKey()->withAttribute('shortCode', 'abc123') - ->withParsedBody(['tags' => []]), - ); - self::assertEquals(200, $response->getStatusCode()); - } - - private function createRequestWithAPiKey(): ServerRequestInterface - { - return ServerRequestFactory::fromGlobals()->withAttribute(ApiKey::class, ApiKey::create()); - } -} diff --git a/module/Rest/test/Action/Tag/CreateTagsActionTest.php b/module/Rest/test/Action/Tag/CreateTagsActionTest.php deleted file mode 100644 index f63c0afc..00000000 --- a/module/Rest/test/Action/Tag/CreateTagsActionTest.php +++ /dev/null @@ -1,50 +0,0 @@ -tagService = $this->prophesize(TagServiceInterface::class); - $this->action = new CreateTagsAction($this->tagService->reveal()); - } - - /** - * @test - * @dataProvider provideTags - */ - public function processDelegatesIntoService(?array $tags): void - { - $request = (new ServerRequest())->withParsedBody(['tags' => $tags]); - $deleteTags = $this->tagService->createTags($tags ?: [])->willReturn(new ArrayCollection()); - - $response = $this->action->handle($request); - - self::assertEquals(200, $response->getStatusCode()); - $deleteTags->shouldHaveBeenCalled(); - } - - public function provideTags(): iterable - { - yield 'three tags' => [['foo', 'bar', 'baz']]; - yield 'two tags' => [['some', 'thing']]; - yield 'null tags' => [null]; - yield 'empty tags' => [[]]; - } -} diff --git a/module/Rest/test/Exception/MissingAuthenticationExceptionTest.php b/module/Rest/test/Exception/MissingAuthenticationExceptionTest.php index 1b7730b5..5d80ca17 100644 --- a/module/Rest/test/Exception/MissingAuthenticationExceptionTest.php +++ b/module/Rest/test/Exception/MissingAuthenticationExceptionTest.php @@ -14,7 +14,7 @@ class MissingAuthenticationExceptionTest extends TestCase { /** * @test - * @dataProvider provideExpectedTypes + * @dataProvider provideExpectedHeaders */ public function exceptionIsProperlyCreatedFromExpectedHeaders(array $expectedHeaders): void { @@ -28,13 +28,10 @@ class MissingAuthenticationExceptionTest extends TestCase $this->assertCommonExceptionShape($e); self::assertEquals($expectedMessage, $e->getMessage()); self::assertEquals($expectedMessage, $e->getDetail()); - self::assertEquals([ - 'expectedTypes' => $expectedHeaders, - 'expectedHeaders' => $expectedHeaders, - ], $e->getAdditionalData()); + self::assertEquals(['expectedHeaders' => $expectedHeaders], $e->getAdditionalData()); } - public function provideExpectedTypes(): iterable + public function provideExpectedHeaders(): iterable { yield [['foo', 'bar']]; yield [['something']]; diff --git a/module/Rest/test/Middleware/BodyParserMiddlewareTest.php b/module/Rest/test/Middleware/BodyParserMiddlewareTest.php index 98549e70..04c9478d 100644 --- a/module/Rest/test/Middleware/BodyParserMiddlewareTest.php +++ b/module/Rest/test/Middleware/BodyParserMiddlewareTest.php @@ -78,35 +78,6 @@ class BodyParserMiddlewareTest extends TestCase $test = $this; $body = new Stream('php://temp', 'wr'); $body->write('{"foo": "bar", "bar": ["one", 5]}'); - $request = (new ServerRequest())->withMethod('PUT') - ->withBody($body) - ->withHeader('content-type', 'application/json'); - $delegate = $this->prophesize(RequestHandlerInterface::class); - $process = $delegate->handle(Argument::type(ServerRequestInterface::class))->will( - function (array $args) use ($test) { - /** @var ServerRequestInterface $req */ - $req = array_shift($args); - - $test->assertEquals([ - 'foo' => 'bar', - 'bar' => ['one', 5], - ], $req->getParsedBody()); - - return new Response(); - }, - ); - - $this->middleware->process($request, $delegate->reveal()); - - $process->shouldHaveBeenCalledOnce(); - } - - /** @test */ - public function regularRequestsAreUrlDecoded(): void - { - $test = $this; - $body = new Stream('php://temp', 'wr'); - $body->write('foo=bar&bar[]=one&bar[]=5'); $request = (new ServerRequest())->withMethod('PUT') ->withBody($body); $delegate = $this->prophesize(RequestHandlerInterface::class);