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);