mirror of
https://github.com/shlinkio/shlink.git
synced 2024-11-27 16:26:37 +03:00
commit
bf8e14708b
116 changed files with 1779 additions and 1170 deletions
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -22,3 +22,4 @@ indocker export-ignore
|
|||
phpcs.xml export-ignore
|
||||
phpunit.xml.dist export-ignore
|
||||
phpunit-func.xml export-ignore
|
||||
phpstan.neon
|
||||
|
|
|
@ -2,8 +2,7 @@ language: php
|
|||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- develop
|
||||
- /.*/
|
||||
|
||||
php:
|
||||
- 7
|
||||
|
@ -17,10 +16,12 @@ before_install:
|
|||
before_script:
|
||||
- composer self-update
|
||||
- composer install --no-interaction
|
||||
- if [[ $TRAVIS_PHP_VERSION = 7.1 ]] || [[ $TRAVIS_PHP_VERSION = 7.2 ]]; then composer global require --dev phpstan/phpstan:0.9.*; fi
|
||||
|
||||
script:
|
||||
- mkdir build
|
||||
- composer check
|
||||
- if [[ $TRAVIS_PHP_VERSION = 7.1 ]] || [[ $TRAVIS_PHP_VERSION = 7.2 ]]; then ~/.composer/vendor/bin/phpstan analyse module/*/src/ --level=6 -c phpstan.neon; fi
|
||||
|
||||
after_script:
|
||||
- vendor/bin/phpcov merge build --clover build/clover.xml
|
||||
|
|
18
CHANGELOG.md
18
CHANGELOG.md
|
@ -1,5 +1,23 @@
|
|||
## CHANGELOG
|
||||
|
||||
### 1.7.0
|
||||
|
||||
**Features**
|
||||
|
||||
* [88: Allow to disable tracking of the short URL by including a configurable query param](https://github.com/shlinkio/shlink/issues/88)
|
||||
* [108: Allow to edit metadata in created shortcodes](https://github.com/shlinkio/shlink/issues/108)
|
||||
|
||||
**Enhancements:**
|
||||
|
||||
* [113: Update CLI commands to use SymfonyStyle](https://github.com/shlinkio/shlink/issues/113)
|
||||
* [112: Configure cli commands lazy loading](https://github.com/shlinkio/shlink/issues/112)
|
||||
|
||||
**Tasks**
|
||||
|
||||
* [117: Make every module which throws exceptions have its own ExceptionInterface, and make them all extend Throwable](https://github.com/shlinkio/shlink/issues/117)
|
||||
* [115: Add phpstan to build matrix on PHP >=7.1 envs](https://github.com/shlinkio/shlink/issues/115)
|
||||
* [114: Replace vlucas/phpdotenv dev requirement by symfony/env](https://github.com/shlinkio/shlink/issues/114)
|
||||
|
||||
### 1.6.2
|
||||
|
||||
**Bugs**
|
||||
|
|
2
bin/cli
2
bin/cli
|
@ -1,5 +1,7 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Symfony\Component\Console\Application as CliApp;
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
use Shlinkio\Shlink\CLI\Factory\InstallApplicationFactory;
|
||||
use Shlinkio\Shlink\CLI\Install\Plugin\DatabaseConfigCustomizerPlugin;
|
||||
use Shlinkio\Shlink\CLI\Install\Plugin\DatabaseConfigCustomizer;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||
|
@ -17,12 +17,11 @@ $container = new ServiceManager([
|
|||
'factories' => [
|
||||
Application::class => InstallApplicationFactory::class,
|
||||
Filesystem::class => InvokableFactory::class,
|
||||
QuestionHelper::class => InvokableFactory::class,
|
||||
],
|
||||
'services' => [
|
||||
'config' => [
|
||||
ConfigAbstractFactory::class => [
|
||||
DatabaseConfigCustomizerPlugin::class => [QuestionHelper::class, Filesystem::class]
|
||||
DatabaseConfigCustomizer::class => [Filesystem::class]
|
||||
],
|
||||
],
|
||||
],
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
use Shlinkio\Shlink\CLI\Factory\InstallApplicationFactory;
|
||||
use Shlinkio\Shlink\CLI\Install\Plugin\DatabaseConfigCustomizerPlugin;
|
||||
use Shlinkio\Shlink\CLI\Install\Plugin\DatabaseConfigCustomizer;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||
|
@ -17,12 +17,11 @@ $container = new ServiceManager([
|
|||
'factories' => [
|
||||
Application::class => InstallApplicationFactory::class,
|
||||
Filesystem::class => InvokableFactory::class,
|
||||
QuestionHelper::class => InvokableFactory::class,
|
||||
],
|
||||
'services' => [
|
||||
'config' => [
|
||||
ConfigAbstractFactory::class => [
|
||||
DatabaseConfigCustomizerPlugin::class => [QuestionHelper::class, Filesystem::class]
|
||||
DatabaseConfigCustomizer::class => [Filesystem::class]
|
||||
],
|
||||
],
|
||||
],
|
||||
|
|
|
@ -15,13 +15,13 @@
|
|||
"php": "^7.0",
|
||||
"acelaya/ze-content-based-error-handler": "^2.0",
|
||||
"cocur/slugify": "^3.0",
|
||||
"doctrine/annotations": "^1.4 <1.5",
|
||||
"doctrine/cache": "^1.6 <1.7",
|
||||
"doctrine/collections": "^1.4 <1.5",
|
||||
"doctrine/common": "^2.7 <2.8",
|
||||
"doctrine/dbal": "^2.5 <2.6",
|
||||
"doctrine/annotations": "^1.4",
|
||||
"doctrine/cache": "^1.6",
|
||||
"doctrine/collections": "^1.4",
|
||||
"doctrine/common": "^2.7",
|
||||
"doctrine/dbal": "^2.5",
|
||||
"doctrine/migrations": "^1.4",
|
||||
"doctrine/orm": "^2.5 <2.6",
|
||||
"doctrine/orm": "^2.5",
|
||||
"endroid/qrcode": "^1.7",
|
||||
"firebase/php-jwt": "^4.0",
|
||||
"guzzlehttp/guzzle": "^6.2",
|
||||
|
@ -29,7 +29,7 @@
|
|||
"mikehaertl/phpwkhtmltopdf": "^2.2",
|
||||
"monolog/monolog": "^1.21",
|
||||
"roave/security-advisories": "dev-master",
|
||||
"symfony/console": "^3.0",
|
||||
"symfony/console": "^3.4",
|
||||
"symfony/filesystem": "^3.0",
|
||||
"symfony/process": "^3.0",
|
||||
"theorchard/monolog-cascade": "^0.4",
|
||||
|
@ -40,8 +40,9 @@
|
|||
"zendframework/zend-expressive-helpers": "^4.2",
|
||||
"zendframework/zend-expressive-platesrenderer": "^1.3",
|
||||
"zendframework/zend-i18n": "^2.7",
|
||||
"zendframework/zend-inputfilter": "^2.8",
|
||||
"zendframework/zend-paginator": "^2.6",
|
||||
"zendframework/zend-servicemanager": "^3.0",
|
||||
"zendframework/zend-servicemanager": "^3.2",
|
||||
"zendframework/zend-stdlib": "^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
|
@ -50,9 +51,9 @@
|
|||
"phpunit/phpcov": "^4.0",
|
||||
"phpunit/phpunit": "^6.0",
|
||||
"slevomat/coding-standard": "^4.0",
|
||||
"squizlabs/php_codesniffer": "^3.1",
|
||||
"squizlabs/php_codesniffer": "^3.1 <3.2",
|
||||
"symfony/dotenv": "^3.4",
|
||||
"symfony/var-dumper": "^3.0",
|
||||
"vlucas/phpdotenv": "^2.2",
|
||||
"zendframework/zend-expressive-tooling": "^0.4"
|
||||
},
|
||||
"autoload": {
|
||||
|
@ -102,7 +103,7 @@
|
|||
"process-timeout": 0,
|
||||
"sort-packages": true,
|
||||
"platform": {
|
||||
"php": "7.0"
|
||||
"php": "7.0.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Shlinkio\Shlink\Common;
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
|
||||
return [
|
||||
|
||||
'app_options' => [
|
||||
'name' => 'Shlink',
|
||||
'version' => '1.2.0',
|
||||
'secret_key' => Common\env('SECRET_KEY'),
|
||||
'version' => '1.7.0',
|
||||
'secret_key' => env('SECRET_KEY'),
|
||||
],
|
||||
|
||||
];
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Dotenv\Dotenv;
|
||||
use Symfony\Component\Dotenv\Dotenv;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
chdir(dirname(__DIR__));
|
||||
|
@ -12,8 +12,8 @@ require 'vendor/autoload.php';
|
|||
if (class_exists(Dotenv::class)) {
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '1');
|
||||
$dotenv = new Dotenv(__DIR__ . '/..');
|
||||
$dotenv->load();
|
||||
$dotenv = new Dotenv();
|
||||
$dotenv->load(__DIR__ . '/../.env');
|
||||
}
|
||||
|
||||
// Build container
|
||||
|
|
|
@ -116,6 +116,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
|
||||
"post": {
|
||||
"tags": [
|
||||
"ShortCodes"
|
||||
|
@ -140,6 +141,34 @@
|
|||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "validSince",
|
||||
"in": "formData",
|
||||
"description": "The date (in ISO-8601 format) from which this short code will be valid",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "validUntil",
|
||||
"in": "formData",
|
||||
"description": "The date (in ISO-8601 format) until which this short code will be valid",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "customSlug",
|
||||
"in": "formData",
|
||||
"description": "A unique custom slug to be used instead of the generated short code",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "maxVisits",
|
||||
"in": "formData",
|
||||
"description": "The maximum number of allowed visits for this short code",
|
||||
"required": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"$ref": "../parameters/Authorization.json"
|
||||
}
|
||||
|
|
|
@ -54,5 +54,69 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"put": {
|
||||
"tags": [
|
||||
"ShortCodes"
|
||||
],
|
||||
"summary": "Edit short code",
|
||||
"description": "Update certain meta arguments from an existing short URL.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "shortCode",
|
||||
"in": "path",
|
||||
"type": "string",
|
||||
"description": "The short code to edit.",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "validSince",
|
||||
"in": "formData",
|
||||
"description": "The date (in ISO-8601 format) from which this short code will be valid",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "validUntil",
|
||||
"in": "formData",
|
||||
"description": "The date (in ISO-8601 format) until which this short code will be valid",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "maxVisits",
|
||||
"in": "formData",
|
||||
"description": "The maximum number of allowed visits for this short code",
|
||||
"required": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"$ref": "../parameters/Authorization.json"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "The short code has been properly updated."
|
||||
},
|
||||
"400": {
|
||||
"description": "Provided meta arguments are invalid.",
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "No short URL was found for provided short code.",
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Unexpected error.",
|
||||
"schema": {
|
||||
"$ref": "../definitions/Error.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,21 +9,25 @@ return [
|
|||
'cli' => [
|
||||
'locale' => Common\env('CLI_LOCALE', 'en'),
|
||||
'commands' => [
|
||||
Command\Shortcode\GenerateShortcodeCommand::class,
|
||||
Command\Shortcode\ResolveUrlCommand::class,
|
||||
Command\Shortcode\ListShortcodesCommand::class,
|
||||
Command\Shortcode\GetVisitsCommand::class,
|
||||
Command\Shortcode\GeneratePreviewCommand::class,
|
||||
Command\Visit\ProcessVisitsCommand::class,
|
||||
Command\Config\GenerateCharsetCommand::class,
|
||||
Command\Config\GenerateSecretCommand::class,
|
||||
Command\Api\GenerateKeyCommand::class,
|
||||
Command\Api\DisableKeyCommand::class,
|
||||
Command\Api\ListKeysCommand::class,
|
||||
Command\Tag\ListTagsCommand::class,
|
||||
Command\Tag\CreateTagCommand::class,
|
||||
Command\Tag\RenameTagCommand::class,
|
||||
Command\Tag\DeleteTagsCommand::class,
|
||||
Command\Shortcode\GenerateShortcodeCommand::NAME => Command\Shortcode\GenerateShortcodeCommand::class,
|
||||
Command\Shortcode\ResolveUrlCommand::NAME => Command\Shortcode\ResolveUrlCommand::class,
|
||||
Command\Shortcode\ListShortcodesCommand::NAME => Command\Shortcode\ListShortcodesCommand::class,
|
||||
Command\Shortcode\GetVisitsCommand::NAME => Command\Shortcode\GetVisitsCommand::class,
|
||||
Command\Shortcode\GeneratePreviewCommand::NAME => Command\Shortcode\GeneratePreviewCommand::class,
|
||||
|
||||
Command\Visit\ProcessVisitsCommand::NAME => Command\Visit\ProcessVisitsCommand::class,
|
||||
|
||||
Command\Config\GenerateCharsetCommand::NAME => Command\Config\GenerateCharsetCommand::class,
|
||||
Command\Config\GenerateSecretCommand::NAME => Command\Config\GenerateSecretCommand::class,
|
||||
|
||||
Command\Api\GenerateKeyCommand::NAME => Command\Api\GenerateKeyCommand::class,
|
||||
Command\Api\DisableKeyCommand::NAME => Command\Api\DisableKeyCommand::class,
|
||||
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,
|
||||
],
|
||||
],
|
||||
|
||||
|
|
Binary file not shown.
|
@ -1,15 +1,15 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Shlink 1.0\n"
|
||||
"POT-Creation-Date: 2017-10-21 20:17+0200\n"
|
||||
"PO-Revision-Date: 2017-10-21 20:19+0200\n"
|
||||
"POT-Creation-Date: 2018-01-21 09:36+0100\n"
|
||||
"PO-Revision-Date: 2018-01-21 09:39+0100\n"
|
||||
"Last-Translator: Alejandro Celaya <alejandro@alejandrocelaya.com>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: es_ES\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.0.1\n"
|
||||
"X-Generator: Poedit 2.0.4\n"
|
||||
"X-Poedit-Basepath: ..\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
@ -24,8 +24,8 @@ msgid "The API key to disable"
|
|||
msgstr "La clave de API a deshabilitar"
|
||||
|
||||
#, php-format
|
||||
msgid "API key %s properly disabled"
|
||||
msgstr "Clave de API %s deshabilitada correctamente"
|
||||
msgid "API key \"%s\" properly disabled"
|
||||
msgstr "Clave de API \"%s\" deshabilitada correctamente"
|
||||
|
||||
#, php-format
|
||||
msgid "API key \"%s\" does not exist."
|
||||
|
@ -39,8 +39,9 @@ msgstr ""
|
|||
"La fecha en la que la clave de API debe expirar. Utiliza cualquier valor "
|
||||
"válido en PHP."
|
||||
|
||||
msgid "Generated API key"
|
||||
msgstr "Generada clave de API"
|
||||
#, php-format
|
||||
msgid "Generated API key: \"%s\""
|
||||
msgstr "Generada clave de API. \"%s\""
|
||||
|
||||
msgid "Lists all the available API keys."
|
||||
msgstr "Lista todas las claves de API disponibles."
|
||||
|
@ -51,12 +52,12 @@ msgstr "Define si sólo las claves de API habilitadas deben ser devueltas."
|
|||
msgid "Key"
|
||||
msgstr "Clave"
|
||||
|
||||
msgid "Expiration date"
|
||||
msgstr "Fecha de caducidad"
|
||||
|
||||
msgid "Is enabled"
|
||||
msgstr "Está habilitada"
|
||||
|
||||
msgid "Expiration date"
|
||||
msgstr "Fecha de caducidad"
|
||||
|
||||
#, php-format
|
||||
msgid ""
|
||||
"Generates a character set sample just by shuffling the default one, \"%s\". "
|
||||
|
@ -65,8 +66,9 @@ msgstr ""
|
|||
"Genera un grupo de caracteres simplemente mexclando el grupo por defecto \"%s"
|
||||
"\". Después puede ser utilizado en la variable de entrono SHORTCODE_CHARS"
|
||||
|
||||
msgid "Character set:"
|
||||
msgstr "Grupo de caracteres:"
|
||||
#, php-format
|
||||
msgid "Character set: \"%s\""
|
||||
msgstr "Grupo de caracteres: \"%s\""
|
||||
|
||||
msgid ""
|
||||
"Generates a random secret string that can be used for JWT token encryption"
|
||||
|
@ -74,8 +76,9 @@ msgstr ""
|
|||
"Genera una cadena de caracteres aleatoria que puede ser usada para cifrar "
|
||||
"tokens JWT"
|
||||
|
||||
msgid "Secret key:"
|
||||
msgstr "Clave secreta:"
|
||||
#, php-format
|
||||
msgid "Secret key: \"%s\""
|
||||
msgstr "Clave secreta: \"%s\""
|
||||
|
||||
msgid ""
|
||||
"Processes and generates the previews for every URL, improving performance "
|
||||
|
@ -125,17 +128,22 @@ msgid "If provided, this slug will be used instead of generating a short code"
|
|||
msgstr ""
|
||||
"Si se proporciona, este slug será usado en vez de generar un código corto"
|
||||
|
||||
msgid "A long URL was not provided. Which URL do you want to shorten?:"
|
||||
msgid "This will limit the number of visits for this short URL."
|
||||
msgstr "Esto limitará el número de visitas a esta URL acortada."
|
||||
|
||||
#, fuzzy
|
||||
#| msgid "A long URL was not provided. Which URL do you want to shorten?:"
|
||||
msgid "A long URL was not provided. Which URL do you want to be shortened?"
|
||||
msgstr "No se ha proporcionado una URL larga. ¿Qué URL deseas acortar?"
|
||||
|
||||
msgid "A URL was not provided!"
|
||||
msgstr "¡No se ha proporcionado una URL!"
|
||||
|
||||
msgid "Processed URL:"
|
||||
msgstr "URL procesada:"
|
||||
msgid "Processed long URL:"
|
||||
msgstr "URL larga procesada:"
|
||||
|
||||
msgid "Generated URL:"
|
||||
msgstr "URL generada:"
|
||||
msgid "Generated short URL:"
|
||||
msgstr "URL corta generada:"
|
||||
|
||||
#, php-format
|
||||
msgid "Provided URL \"%s\" is invalid. Try with a different one."
|
||||
|
@ -166,8 +174,8 @@ msgid "Allows to filter visits, returning only those newer than end date"
|
|||
msgstr ""
|
||||
"Permite filtrar las visitas, devolviendo sólo aquellas más nuevas que endDate"
|
||||
|
||||
msgid "A short code was not provided. Which short code do you want to use?:"
|
||||
msgstr "No se prporcionó un código corto. ¿Qué código corto deseas usar?:"
|
||||
msgid "A short code was not provided. Which short code do you want to use?"
|
||||
msgstr "No se proporcionó un código corto. ¿Qué código corto deseas usar?"
|
||||
|
||||
msgid "Referer"
|
||||
msgstr "Origen"
|
||||
|
@ -222,8 +230,8 @@ msgstr "Número de visitas"
|
|||
msgid "Tags"
|
||||
msgstr "Etiquetas"
|
||||
|
||||
msgid "You have reached last page"
|
||||
msgstr "Has alcanzado la última página"
|
||||
msgid "Short codes properly listed"
|
||||
msgstr "Códigos cortos correctamente listados"
|
||||
|
||||
msgid "Continue with page"
|
||||
msgstr "Continuar con la página"
|
||||
|
@ -234,13 +242,9 @@ msgstr "Devuelve la URL larga detrás de un código corto"
|
|||
msgid "The short code to parse"
|
||||
msgstr "El código corto a convertir"
|
||||
|
||||
msgid "A short code was not provided. Which short code do you want to parse?:"
|
||||
msgid "A short code was not provided. Which short code do you want to parse?"
|
||||
msgstr ""
|
||||
"No se proporcionó un código corto. ¿Qué código corto quieres convertir?:"
|
||||
|
||||
#, php-format
|
||||
msgid "No URL found for short code \"%s\""
|
||||
msgstr "No se ha encontrado ninguna URL para el código corto \"%s\""
|
||||
"No se proporcionó un código corto. ¿Qué código corto quieres convertir?"
|
||||
|
||||
msgid "Long URL:"
|
||||
msgstr "URL larga:"
|
||||
|
@ -262,8 +266,8 @@ msgstr "El nombre de las etiquetas a crear"
|
|||
msgid "You have to provide at least one tag name"
|
||||
msgstr "Debes proporcionar al menos un nombre de etiqueta"
|
||||
|
||||
msgid "Created tags"
|
||||
msgstr "Etiquetas creadas"
|
||||
msgid "Tags properly created"
|
||||
msgstr "Etiquetas correctamente creadas"
|
||||
|
||||
msgid "Deletes one or more tags."
|
||||
msgstr "Elimina una o más etiquetas."
|
||||
|
@ -271,8 +275,8 @@ msgstr "Elimina una o más etiquetas."
|
|||
msgid "The name of the tags to delete"
|
||||
msgstr "El nombre de las etiquetas a eliminar"
|
||||
|
||||
msgid "Deleted tags"
|
||||
msgstr "Etiquetas eliminadas"
|
||||
msgid "Tags properly deleted"
|
||||
msgstr "Etiquetas correctamente eliminadas"
|
||||
|
||||
msgid "Lists existing tags."
|
||||
msgstr "Lista las etiquetas existentes."
|
||||
|
@ -315,3 +319,15 @@ msgstr "Dirección localizada en \"%s\""
|
|||
|
||||
msgid "Finished processing all IPs"
|
||||
msgstr "Finalizado el procesado de todas las IPs"
|
||||
|
||||
#~ msgid "You have reached last page"
|
||||
#~ msgstr "Has alcanzado la última página"
|
||||
|
||||
#~ msgid "No URL found for short code \"%s\""
|
||||
#~ msgstr "No se ha encontrado ninguna URL para el código corto \"%s\""
|
||||
|
||||
#~ msgid "Created tags"
|
||||
#~ msgstr "Etiquetas creadas"
|
||||
|
||||
#~ msgid "Deleted tags"
|
||||
#~ msgstr "Etiquetas eliminadas"
|
||||
|
|
|
@ -8,10 +8,13 @@ use Symfony\Component\Console\Command\Command;
|
|||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class DisableKeyCommand extends Command
|
||||
{
|
||||
const NAME = 'api-key:disable';
|
||||
|
||||
/**
|
||||
* @var ApiKeyServiceInterface
|
||||
*/
|
||||
|
@ -30,7 +33,7 @@ class DisableKeyCommand extends Command
|
|||
|
||||
public function configure()
|
||||
{
|
||||
$this->setName('api-key:disable')
|
||||
$this->setName(self::NAME)
|
||||
->setDescription($this->translator->translate('Disables an API key.'))
|
||||
->addArgument('apiKey', InputArgument::REQUIRED, $this->translator->translate('The API key to disable'));
|
||||
}
|
||||
|
@ -38,18 +41,13 @@ class DisableKeyCommand extends Command
|
|||
public function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$apiKey = $input->getArgument('apiKey');
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
try {
|
||||
$this->apiKeyService->disable($apiKey);
|
||||
$output->writeln(sprintf(
|
||||
$this->translator->translate('API key %s properly disabled'),
|
||||
'<info>' . $apiKey . '</info>'
|
||||
));
|
||||
$io->success(sprintf($this->translator->translate('API key "%s" properly disabled'), $apiKey));
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$output->writeln(sprintf(
|
||||
'<error>' . $this->translator->translate('API key "%s" does not exist.') . '</error>',
|
||||
$apiKey
|
||||
));
|
||||
$io->error(sprintf($this->translator->translate('API key "%s" does not exist.'), $apiKey));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,10 +8,13 @@ use Symfony\Component\Console\Command\Command;
|
|||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class GenerateKeyCommand extends Command
|
||||
{
|
||||
const NAME = 'api-key:generate';
|
||||
|
||||
/**
|
||||
* @var ApiKeyServiceInterface
|
||||
*/
|
||||
|
@ -30,7 +33,7 @@ class GenerateKeyCommand extends Command
|
|||
|
||||
public function configure()
|
||||
{
|
||||
$this->setName('api-key:generate')
|
||||
$this->setName(self::NAME)
|
||||
->setDescription($this->translator->translate('Generates a new valid API key.'))
|
||||
->addOption(
|
||||
'expirationDate',
|
||||
|
@ -44,6 +47,9 @@ class GenerateKeyCommand extends Command
|
|||
{
|
||||
$expirationDate = $input->getOption('expirationDate');
|
||||
$apiKey = $this->apiKeyService->create(isset($expirationDate) ? new \DateTime($expirationDate) : null);
|
||||
$output->writeln($this->translator->translate('Generated API key') . sprintf(': <info>%s</info>', $apiKey));
|
||||
|
||||
(new SymfonyStyle($input, $output))->success(
|
||||
sprintf($this->translator->translate('Generated API key: "%s"'), $apiKey)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,14 +6,16 @@ namespace Shlinkio\Shlink\CLI\Command\Api;
|
|||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class ListKeysCommand extends Command
|
||||
{
|
||||
const NAME = 'api-key:list';
|
||||
|
||||
/**
|
||||
* @var ApiKeyServiceInterface
|
||||
*/
|
||||
|
@ -32,7 +34,7 @@ class ListKeysCommand extends Command
|
|||
|
||||
public function configure()
|
||||
{
|
||||
$this->setName('api-key:list')
|
||||
$this->setName(self::NAME)
|
||||
->setDescription($this->translator->translate('Lists all the available API keys.'))
|
||||
->addOption(
|
||||
'enabledOnly',
|
||||
|
@ -44,78 +46,75 @@ class ListKeysCommand extends Command
|
|||
|
||||
public function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$enabledOnly = $input->getOption('enabledOnly');
|
||||
$list = $this->apiKeyService->listKeys($enabledOnly);
|
||||
|
||||
$table = new Table($output);
|
||||
if ($enabledOnly) {
|
||||
$table->setHeaders([
|
||||
$this->translator->translate('Key'),
|
||||
$this->translator->translate('Expiration date'),
|
||||
]);
|
||||
} else {
|
||||
$table->setHeaders([
|
||||
$this->translator->translate('Key'),
|
||||
$this->translator->translate('Is enabled'),
|
||||
$this->translator->translate('Expiration date'),
|
||||
]);
|
||||
}
|
||||
$rows = [];
|
||||
|
||||
/** @var ApiKey $row */
|
||||
foreach ($list as $row) {
|
||||
$key = $row->getKey();
|
||||
$expiration = $row->getExpirationDate();
|
||||
$rowData = [];
|
||||
$formatMethod = ! $row->isEnabled()
|
||||
? 'getErrorString'
|
||||
: ($row->isExpired() ? 'getWarningString' : 'getSuccessString');
|
||||
$formatMethod = $this->determineFormatMethod($row);
|
||||
|
||||
if ($enabledOnly) {
|
||||
$rowData[] = $this->{$formatMethod}($key);
|
||||
} else {
|
||||
$rowData[] = $this->{$formatMethod}($key);
|
||||
$rowData[] = $this->{$formatMethod}($this->getEnabledSymbol($row));
|
||||
// Set columns for this row
|
||||
$rowData = [$formatMethod($key)];
|
||||
if (! $enabledOnly) {
|
||||
$rowData[] = $formatMethod($this->getEnabledSymbol($row));
|
||||
}
|
||||
$rowData[] = $expiration !== null ? $expiration->format(\DateTime::ATOM) : '-';
|
||||
|
||||
$rowData[] = isset($expiration) ? $expiration->format(\DateTime::ATOM) : '-';
|
||||
$table->addRow($rowData);
|
||||
$rows[] = $rowData;
|
||||
}
|
||||
|
||||
$table->render();
|
||||
$io->table(array_filter([
|
||||
$this->translator->translate('Key'),
|
||||
! $enabledOnly ? $this->translator->translate('Is enabled') : null,
|
||||
$this->translator->translate('Expiration date'),
|
||||
]), $rows);
|
||||
}
|
||||
|
||||
private function determineFormatMethod(ApiKey $apiKey): callable
|
||||
{
|
||||
if (! $apiKey->isEnabled()) {
|
||||
return [$this, 'getErrorString'];
|
||||
}
|
||||
|
||||
return $apiKey->isExpired() ? [$this, 'getWarningString'] : [$this, 'getSuccessString'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
protected function getErrorString($string)
|
||||
private function getErrorString(string $value): string
|
||||
{
|
||||
return sprintf('<fg=red>%s</>', $string);
|
||||
return sprintf('<fg=red>%s</>', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
protected function getSuccessString($string)
|
||||
private function getSuccessString(string $value): string
|
||||
{
|
||||
return sprintf('<info>%s</info>', $string);
|
||||
return sprintf('<info>%s</info>', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $string
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
protected function getWarningString($string)
|
||||
private function getWarningString(string $value): string
|
||||
{
|
||||
return sprintf('<comment>%s</comment>', $string);
|
||||
return sprintf('<comment>%s</comment>', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ApiKey $apiKey
|
||||
* @return string
|
||||
*/
|
||||
protected function getEnabledSymbol(ApiKey $apiKey)
|
||||
private function getEnabledSymbol(ApiKey $apiKey): string
|
||||
{
|
||||
return ! $apiKey->isEnabled() || $apiKey->isExpired() ? '---' : '+++';
|
||||
}
|
||||
|
|
|
@ -7,10 +7,13 @@ use Shlinkio\Shlink\Core\Service\UrlShortener;
|
|||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class GenerateCharsetCommand extends Command
|
||||
{
|
||||
const NAME = 'config:generate-charset';
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
|
@ -24,7 +27,7 @@ class GenerateCharsetCommand extends Command
|
|||
|
||||
public function configure()
|
||||
{
|
||||
$this->setName('config:generate-charset')
|
||||
$this->setName(self::NAME)
|
||||
->setDescription(sprintf($this->translator->translate(
|
||||
'Generates a character set sample just by shuffling the default one, "%s". '
|
||||
. 'Then it can be set in the SHORTCODE_CHARS environment variable'
|
||||
|
@ -34,6 +37,8 @@ class GenerateCharsetCommand extends Command
|
|||
public function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$charSet = str_shuffle(UrlShortener::DEFAULT_CHARS);
|
||||
$output->writeln($this->translator->translate('Character set:') . sprintf(' <info>%s</info>', $charSet));
|
||||
(new SymfonyStyle($input, $output))->success(
|
||||
\sprintf($this->translator->translate('Character set: "%s"'), $charSet)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,12 +7,15 @@ use Shlinkio\Shlink\Common\Util\StringUtilsTrait;
|
|||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class GenerateSecretCommand extends Command
|
||||
{
|
||||
use StringUtilsTrait;
|
||||
|
||||
const NAME = 'config:generate-secret';
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
|
@ -26,7 +29,7 @@ class GenerateSecretCommand extends Command
|
|||
|
||||
public function configure()
|
||||
{
|
||||
$this->setName('config:generate-secret')
|
||||
$this->setName(self::NAME)
|
||||
->setDescription($this->translator->translate(
|
||||
'Generates a random secret string that can be used for JWT token encryption'
|
||||
));
|
||||
|
@ -35,6 +38,8 @@ class GenerateSecretCommand extends Command
|
|||
public function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$secret = $this->generateRandomString(32);
|
||||
$output->writeln($this->translator->translate('Secret key:') . sprintf(' <info>%s</info>', $secret));
|
||||
(new SymfonyStyle($input, $output))->success(
|
||||
sprintf($this->translator->translate('Secret key: "%s"'), $secret)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,18 +3,19 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shlinkio\Shlink\CLI\Command\Install;
|
||||
|
||||
use Shlinkio\Shlink\CLI\Install\ConfigCustomizerPluginManagerInterface;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Shlinkio\Shlink\CLI\Install\ConfigCustomizerManagerInterface;
|
||||
use Shlinkio\Shlink\CLI\Install\Plugin;
|
||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Console\Exception\LogicException;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Helper\ProcessHelper;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Filesystem\Exception\IOException;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Zend\Config\Writer\WriterInterface;
|
||||
|
@ -24,17 +25,9 @@ class InstallCommand extends Command
|
|||
const GENERATED_CONFIG_PATH = 'config/params/generated_config.php';
|
||||
|
||||
/**
|
||||
* @var InputInterface
|
||||
* @var SymfonyStyle
|
||||
*/
|
||||
private $input;
|
||||
/**
|
||||
* @var OutputInterface
|
||||
*/
|
||||
private $output;
|
||||
/**
|
||||
* @var QuestionHelper
|
||||
*/
|
||||
private $questionHelper;
|
||||
private $io;
|
||||
/**
|
||||
* @var ProcessHelper
|
||||
*/
|
||||
|
@ -48,7 +41,7 @@ class InstallCommand extends Command
|
|||
*/
|
||||
private $filesystem;
|
||||
/**
|
||||
* @var ConfigCustomizerPluginManagerInterface
|
||||
* @var ConfigCustomizerManagerInterface
|
||||
*/
|
||||
private $configCustomizers;
|
||||
/**
|
||||
|
@ -60,13 +53,14 @@ class InstallCommand extends Command
|
|||
* InstallCommand constructor.
|
||||
* @param WriterInterface $configWriter
|
||||
* @param Filesystem $filesystem
|
||||
* @param ConfigCustomizerManagerInterface $configCustomizers
|
||||
* @param bool $isUpdate
|
||||
* @throws LogicException
|
||||
*/
|
||||
public function __construct(
|
||||
WriterInterface $configWriter,
|
||||
Filesystem $filesystem,
|
||||
ConfigCustomizerPluginManagerInterface $configCustomizers,
|
||||
ConfigCustomizerManagerInterface $configCustomizers,
|
||||
$isUpdate = false
|
||||
) {
|
||||
parent::__construct();
|
||||
|
@ -83,30 +77,34 @@ class InstallCommand extends Command
|
|||
->setDescription('Installs or updates Shlink');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
* @return void
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->input = $input;
|
||||
$this->output = $output;
|
||||
$this->questionHelper = $this->getHelper('question');
|
||||
$this->processHelper = $this->getHelper('process');
|
||||
$this->io = new SymfonyStyle($input, $output);
|
||||
|
||||
$output->writeln([
|
||||
$this->io->writeln([
|
||||
'<info>Welcome to Shlink!!</info>',
|
||||
'This will guide you through the installation process.',
|
||||
]);
|
||||
|
||||
// Check if a cached config file exists and drop it if so
|
||||
if ($this->filesystem->exists('data/cache/app_config.php')) {
|
||||
$output->write('Deleting old cached config...');
|
||||
$this->io->write('Deleting old cached config...');
|
||||
try {
|
||||
$this->filesystem->remove('data/cache/app_config.php');
|
||||
$output->writeln(' <info>Success</info>');
|
||||
$this->io->writeln(' <info>Success</info>');
|
||||
} catch (IOException $e) {
|
||||
$output->writeln(
|
||||
' <error>Failed!</error> You will have to manually delete the data/cache/app_config.php file to get'
|
||||
. ' new config applied.'
|
||||
$this->io->error(
|
||||
'Failed! You will have to manually delete the data/cache/app_config.php file to'
|
||||
. ' get new config applied.'
|
||||
);
|
||||
if ($output->isVerbose()) {
|
||||
if ($this->io->isVerbose()) {
|
||||
$this->getApplication()->renderException($e, $output);
|
||||
}
|
||||
return;
|
||||
|
@ -118,56 +116,65 @@ class InstallCommand extends Command
|
|||
|
||||
// Ask for custom config params
|
||||
foreach ([
|
||||
Plugin\DatabaseConfigCustomizerPlugin::class,
|
||||
Plugin\UrlShortenerConfigCustomizerPlugin::class,
|
||||
Plugin\LanguageConfigCustomizerPlugin::class,
|
||||
Plugin\ApplicationConfigCustomizerPlugin::class,
|
||||
Plugin\DatabaseConfigCustomizer::class,
|
||||
Plugin\UrlShortenerConfigCustomizer::class,
|
||||
Plugin\LanguageConfigCustomizer::class,
|
||||
Plugin\ApplicationConfigCustomizer::class,
|
||||
] as $pluginName) {
|
||||
/** @var Plugin\ConfigCustomizerPluginInterface $configCustomizer */
|
||||
/** @var Plugin\ConfigCustomizerInterface $configCustomizer */
|
||||
$configCustomizer = $this->configCustomizers->get($pluginName);
|
||||
$configCustomizer->process($input, $output, $config);
|
||||
$configCustomizer->process($this->io, $config);
|
||||
}
|
||||
|
||||
// Generate config params files
|
||||
$this->configWriter->toFile(self::GENERATED_CONFIG_PATH, $config->getArrayCopy(), false);
|
||||
$output->writeln(['<info>Custom configuration properly generated!</info>', '']);
|
||||
$this->io->writeln(['<info>Custom configuration properly generated!</info>', '']);
|
||||
|
||||
// If current command is not update, generate database
|
||||
if (! $this->isUpdate) {
|
||||
$this->output->writeln('Initializing database...');
|
||||
$this->io->write('Initializing database...');
|
||||
if (! $this->runCommand(
|
||||
'php vendor/bin/doctrine.php orm:schema-tool:create',
|
||||
'Error generating database.'
|
||||
'Error generating database.',
|
||||
$output
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Run database migrations
|
||||
$output->writeln('Updating database...');
|
||||
if (! $this->runCommand('php vendor/bin/doctrine-migrations migrations:migrate', 'Error updating database.')) {
|
||||
$this->io->write('Updating database...');
|
||||
if (! $this->runCommand(
|
||||
'php vendor/bin/doctrine-migrations migrations:migrate',
|
||||
'Error updating database.',
|
||||
$output
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate proxies
|
||||
$output->writeln('Generating proxies...');
|
||||
if (! $this->runCommand('php vendor/bin/doctrine.php orm:generate-proxies', 'Error generating proxies.')) {
|
||||
$this->io->write('Generating proxies...');
|
||||
if (! $this->runCommand(
|
||||
'php vendor/bin/doctrine.php orm:generate-proxies',
|
||||
'Error generating proxies.',
|
||||
$output
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->io->success('Installation complete!');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CustomizableAppConfig
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private function importConfig()
|
||||
private function importConfig(): CustomizableAppConfig
|
||||
{
|
||||
$config = new CustomizableAppConfig();
|
||||
|
||||
// Ask the user if he/she wants to import an older configuration
|
||||
$importConfig = $this->questionHelper->ask($this->input, $this->output, new ConfirmationQuestion(
|
||||
'<question>Do you want to import previous configuration? (Y/n):</question> '
|
||||
));
|
||||
$importConfig = $this->io->confirm('Do you want to import configuration from previous installation?');
|
||||
if (! $importConfig) {
|
||||
return $config;
|
||||
}
|
||||
|
@ -175,17 +182,16 @@ class InstallCommand extends Command
|
|||
// Ask the user for the older shlink path
|
||||
$keepAsking = true;
|
||||
do {
|
||||
$config->setImportedInstallationPath($this->ask(
|
||||
$config->setImportedInstallationPath($this->io->ask(
|
||||
'Previous shlink installation path from which to import config'
|
||||
));
|
||||
$configFile = $config->getImportedInstallationPath() . '/' . self::GENERATED_CONFIG_PATH;
|
||||
$configExists = $this->filesystem->exists($configFile);
|
||||
|
||||
if (! $configExists) {
|
||||
$keepAsking = $this->questionHelper->ask($this->input, $this->output, new ConfirmationQuestion(
|
||||
'Provided path does not seem to be a valid shlink root path. '
|
||||
. '<question>Do you want to try another path? (Y/n):</question> '
|
||||
));
|
||||
$keepAsking = $this->io->confirm(
|
||||
'Provided path does not seem to be a valid shlink root path. Do you want to try another path?'
|
||||
);
|
||||
}
|
||||
} while (! $configExists && $keepAsking);
|
||||
|
||||
|
@ -199,51 +205,31 @@ class InstallCommand extends Command
|
|||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $text
|
||||
* @param string|null $default
|
||||
* @param bool $allowEmpty
|
||||
* @return string
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private function ask($text, $default = null, $allowEmpty = false)
|
||||
{
|
||||
if ($default !== null) {
|
||||
$text .= ' (defaults to ' . $default . ')';
|
||||
}
|
||||
do {
|
||||
$value = $this->questionHelper->ask($this->input, $this->output, new Question(
|
||||
'<question>' . $text . ':</question> ',
|
||||
$default
|
||||
));
|
||||
if (empty($value) && ! $allowEmpty) {
|
||||
$this->output->writeln('<error>Value can\'t be empty</error>');
|
||||
}
|
||||
} while (empty($value) && $default === null && ! $allowEmpty);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $command
|
||||
* @param string $errorMessage
|
||||
* @param OutputInterface $output
|
||||
* @return bool
|
||||
* @throws LogicException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function runCommand($command, $errorMessage)
|
||||
private function runCommand($command, $errorMessage, OutputInterface $output): bool
|
||||
{
|
||||
$process = $this->processHelper->run($this->output, $command);
|
||||
if ($this->processHelper === null) {
|
||||
$this->processHelper = $this->getHelper('process');
|
||||
}
|
||||
|
||||
$process = $this->processHelper->run($output, $command);
|
||||
if ($process->isSuccessful()) {
|
||||
$this->output->writeln(' <info>Success!</info>');
|
||||
$this->io->writeln(' <info>Success!</info>');
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->output->isVerbose()) {
|
||||
if ($this->io->isVerbose()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->output->writeln(
|
||||
' <error>' . $errorMessage . '</error> Run this command with -vvv to see specific error info.'
|
||||
);
|
||||
$this->io->error($errorMessage . ' Run this command with -vvv to see specific error info.');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,10 +9,13 @@ use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
|||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class GeneratePreviewCommand extends Command
|
||||
{
|
||||
const NAME = 'shortcode:process-previews';
|
||||
|
||||
/**
|
||||
* @var PreviewGeneratorInterface
|
||||
*/
|
||||
|
@ -39,7 +42,7 @@ class GeneratePreviewCommand extends Command
|
|||
|
||||
public function configure()
|
||||
{
|
||||
$this->setName('shortcode:process-previews')
|
||||
$this->setName(self::NAME)
|
||||
->setDescription(
|
||||
$this->translator->translate(
|
||||
'Processes and generates the previews for every URL, improving performance for later web requests.'
|
||||
|
@ -59,22 +62,20 @@ class GeneratePreviewCommand extends Command
|
|||
}
|
||||
} while ($page <= $shortUrls->count());
|
||||
|
||||
$output->writeln('<info>' . $this->translator->translate('Finished processing all URLs') . '</info>');
|
||||
(new SymfonyStyle($input, $output))->success($this->translator->translate('Finished processing all URLs'));
|
||||
}
|
||||
|
||||
protected function processUrl($url, OutputInterface $output)
|
||||
{
|
||||
try {
|
||||
$output->write(sprintf($this->translator->translate('Processing URL %s...'), $url));
|
||||
$output->write(\sprintf($this->translator->translate('Processing URL %s...'), $url));
|
||||
$this->previewGenerator->generatePreview($url);
|
||||
$output->writeln($this->translator->translate(' <info>Success!</info>'));
|
||||
} catch (PreviewGenerationException $e) {
|
||||
$messages = [' <error>' . $this->translator->translate('Error') . '</error>'];
|
||||
$output->writeln(' <error>' . $this->translator->translate('Error') . '</error>');
|
||||
if ($output->isVerbose()) {
|
||||
$messages[] = '<error>' . $e->__toString() . '</error>';
|
||||
$this->getApplication()->renderException($e, $output);
|
||||
}
|
||||
|
||||
$output->writeln($messages);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,17 +7,18 @@ use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
|||
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Zend\Diactoros\Uri;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class GenerateShortcodeCommand extends Command
|
||||
{
|
||||
const NAME = 'shortcode:generate';
|
||||
|
||||
/**
|
||||
* @var UrlShortenerInterface
|
||||
*/
|
||||
|
@ -44,7 +45,7 @@ class GenerateShortcodeCommand extends Command
|
|||
|
||||
public function configure()
|
||||
{
|
||||
$this->setName('shortcode:generate')
|
||||
$this->setName(self::NAME)
|
||||
->setDescription(
|
||||
$this->translator->translate('Generates a short code for provided URL and returns the short URL')
|
||||
)
|
||||
|
@ -73,19 +74,15 @@ class GenerateShortcodeCommand extends Command
|
|||
|
||||
public function interact(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$longUrl = $input->getArgument('longUrl');
|
||||
if (! empty($longUrl)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var QuestionHelper $helper */
|
||||
$helper = $this->getHelper('question');
|
||||
$question = new Question(sprintf(
|
||||
'<question>%s</question> ',
|
||||
$this->translator->translate('A long URL was not provided. Which URL do you want to shorten?:')
|
||||
));
|
||||
|
||||
$longUrl = $helper->ask($input, $output, $question);
|
||||
$longUrl = $io->ask(
|
||||
$this->translator->translate('A long URL was not provided. Which URL do you want to be shortened?')
|
||||
);
|
||||
if (! empty($longUrl)) {
|
||||
$input->setArgument('longUrl', $longUrl);
|
||||
}
|
||||
|
@ -93,23 +90,24 @@ class GenerateShortcodeCommand extends Command
|
|||
|
||||
public function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$longUrl = $input->getArgument('longUrl');
|
||||
if (empty($longUrl)) {
|
||||
$io->error($this->translator->translate('A URL was not provided!'));
|
||||
return;
|
||||
}
|
||||
|
||||
$tags = $input->getOption('tags');
|
||||
$processedTags = [];
|
||||
foreach ($tags as $key => $tag) {
|
||||
$explodedTags = explode(',', $tag);
|
||||
$processedTags = array_merge($processedTags, $explodedTags);
|
||||
$explodedTags = \explode(',', $tag);
|
||||
$processedTags = \array_merge($processedTags, $explodedTags);
|
||||
}
|
||||
$tags = $processedTags;
|
||||
$customSlug = $input->getOption('customSlug');
|
||||
$maxVisits = $input->getOption('maxVisits');
|
||||
|
||||
try {
|
||||
if (! isset($longUrl)) {
|
||||
$output->writeln(sprintf('<error>%s</error>', $this->translator->translate('A URL was not provided!')));
|
||||
return;
|
||||
}
|
||||
|
||||
$shortCode = $this->urlShortener->urlToShortCode(
|
||||
new Uri($longUrl),
|
||||
$tags,
|
||||
|
@ -122,22 +120,20 @@ class GenerateShortcodeCommand extends Command
|
|||
->withScheme($this->domainConfig['schema'])
|
||||
->withHost($this->domainConfig['hostname']);
|
||||
|
||||
$output->writeln([
|
||||
sprintf('%s <info>%s</info>', $this->translator->translate('Processed URL:'), $longUrl),
|
||||
sprintf('%s <info>%s</info>', $this->translator->translate('Generated URL:'), $shortUrl),
|
||||
$io->writeln([
|
||||
\sprintf('%s <info>%s</info>', $this->translator->translate('Processed long URL:'), $longUrl),
|
||||
\sprintf('%s <info>%s</info>', $this->translator->translate('Generated short URL:'), $shortUrl),
|
||||
]);
|
||||
} catch (InvalidUrlException $e) {
|
||||
$output->writeln(sprintf(
|
||||
'<error>' . $this->translator->translate(
|
||||
'Provided URL "%s" is invalid. Try with a different one.'
|
||||
) . '</error>',
|
||||
$io->error(\sprintf(
|
||||
$this->translator->translate('Provided URL "%s" is invalid. Try with a different one.'),
|
||||
$longUrl
|
||||
));
|
||||
} catch (NonUniqueSlugException $e) {
|
||||
$output->writeln(sprintf(
|
||||
'<error>' . $this->translator->translate(
|
||||
$io->error(\sprintf(
|
||||
$this->translator->translate(
|
||||
'Provided slug "%s" is already in use by another URL. Try with a different one.'
|
||||
) . '</error>',
|
||||
),
|
||||
$customSlug
|
||||
));
|
||||
}
|
||||
|
|
|
@ -6,17 +6,17 @@ namespace Shlinkio\Shlink\CLI\Command\Shortcode;
|
|||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class GetVisitsCommand extends Command
|
||||
{
|
||||
const NAME = 'shortcode:visits';
|
||||
|
||||
/**
|
||||
* @var VisitsTrackerInterface
|
||||
*/
|
||||
|
@ -30,12 +30,12 @@ class GetVisitsCommand extends Command
|
|||
{
|
||||
$this->visitsTracker = $visitsTracker;
|
||||
$this->translator = $translator;
|
||||
parent::__construct(null);
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function configure()
|
||||
{
|
||||
$this->setName('shortcode:visits')
|
||||
$this->setName(self::NAME)
|
||||
->setDescription(
|
||||
$this->translator->translate('Returns the detailed visits information for provided short code')
|
||||
)
|
||||
|
@ -65,14 +65,10 @@ class GetVisitsCommand extends Command
|
|||
return;
|
||||
}
|
||||
|
||||
/** @var QuestionHelper $helper */
|
||||
$helper = $this->getHelper('question');
|
||||
$question = new Question(sprintf(
|
||||
'<question>%s</question> ',
|
||||
$this->translator->translate('A short code was not provided. Which short code do you want to use?:')
|
||||
));
|
||||
|
||||
$shortCode = $helper->ask($input, $output, $question);
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$shortCode = $io->ask(
|
||||
$this->translator->translate('A short code was not provided. Which short code do you want to use?')
|
||||
);
|
||||
if (! empty($shortCode)) {
|
||||
$input->setArgument('shortCode', $shortCode);
|
||||
}
|
||||
|
@ -80,33 +76,32 @@ class GetVisitsCommand extends Command
|
|||
|
||||
public function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$shortCode = $input->getArgument('shortCode');
|
||||
$startDate = $this->getDateOption($input, 'startDate');
|
||||
$endDate = $this->getDateOption($input, 'endDate');
|
||||
|
||||
$visits = $this->visitsTracker->info($shortCode, new DateRange($startDate, $endDate));
|
||||
$table = new Table($output);
|
||||
$table->setHeaders([
|
||||
$this->translator->translate('Referer'),
|
||||
$this->translator->translate('Date'),
|
||||
$this->translator->translate('Remote Address'),
|
||||
$this->translator->translate('User agent'),
|
||||
]);
|
||||
|
||||
$rows = [];
|
||||
foreach ($visits as $row) {
|
||||
$rowData = $row->jsonSerialize();
|
||||
// Unset location info
|
||||
unset($rowData['visitLocation']);
|
||||
|
||||
$table->addRow(array_values($rowData));
|
||||
$rows[] = \array_values($rowData);
|
||||
}
|
||||
$table->render();
|
||||
$io->table([
|
||||
$this->translator->translate('Referer'),
|
||||
$this->translator->translate('Date'),
|
||||
$this->translator->translate('Remote Address'),
|
||||
$this->translator->translate('User agent'),
|
||||
], $rows);
|
||||
}
|
||||
|
||||
protected function getDateOption(InputInterface $input, $key)
|
||||
{
|
||||
$value = $input->getOption($key);
|
||||
if (isset($value)) {
|
||||
if (! empty($value)) {
|
||||
$value = new \DateTime($value);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,18 +7,18 @@ use Shlinkio\Shlink\Common\Paginator\Adapter\PaginableRepositoryAdapter;
|
|||
use Shlinkio\Shlink\Common\Paginator\Util\PaginatorUtilsTrait;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class ListShortcodesCommand extends Command
|
||||
{
|
||||
use PaginatorUtilsTrait;
|
||||
|
||||
const NAME = 'shortcode:list';
|
||||
|
||||
/**
|
||||
* @var ShortUrlServiceInterface
|
||||
*/
|
||||
|
@ -32,12 +32,12 @@ class ListShortcodesCommand extends Command
|
|||
{
|
||||
$this->shortUrlService = $shortUrlService;
|
||||
$this->translator = $translator;
|
||||
parent::__construct(null);
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function configure()
|
||||
{
|
||||
$this->setName('shortcode:list')
|
||||
$this->setName(self::NAME)
|
||||
->setDescription($this->translator->translate('List all short URLs'))
|
||||
->addOption(
|
||||
'page',
|
||||
|
@ -81,19 +81,16 @@ class ListShortcodesCommand extends Command
|
|||
|
||||
public function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$page = (int) $input->getOption('page');
|
||||
$searchTerm = $input->getOption('searchTerm');
|
||||
$tags = $input->getOption('tags');
|
||||
$tags = ! empty($tags) ? explode(',', $tags) : [];
|
||||
$tags = ! empty($tags) ? \explode(',', $tags) : [];
|
||||
$showTags = $input->getOption('showTags');
|
||||
|
||||
/** @var QuestionHelper $helper */
|
||||
$helper = $this->getHelper('question');
|
||||
|
||||
do {
|
||||
$result = $this->shortUrlService->listShortUrls($page, $searchTerm, $tags, $this->processOrderBy($input));
|
||||
$page++;
|
||||
$table = new Table($output);
|
||||
|
||||
$headers = [
|
||||
$this->translator->translate('Short code'),
|
||||
|
@ -104,8 +101,8 @@ class ListShortcodesCommand extends Command
|
|||
if ($showTags) {
|
||||
$headers[] = $this->translator->translate('Tags');
|
||||
}
|
||||
$table->setHeaders($headers);
|
||||
|
||||
$rows = [];
|
||||
foreach ($result as $row) {
|
||||
$shortUrl = $row->jsonSerialize();
|
||||
if ($showTags) {
|
||||
|
@ -118,27 +115,23 @@ class ListShortcodesCommand extends Command
|
|||
unset($shortUrl['tags']);
|
||||
}
|
||||
|
||||
$table->addRow(array_values($shortUrl));
|
||||
$rows[] = \array_values($shortUrl);
|
||||
}
|
||||
$table->render();
|
||||
$io->table($headers, $rows);
|
||||
|
||||
if ($this->isLastPage($result)) {
|
||||
$continue = false;
|
||||
$output->writeln(
|
||||
sprintf('<info>%s</info>', $this->translator->translate('You have reached last page'))
|
||||
);
|
||||
$io->success($this->translator->translate('Short codes properly listed'));
|
||||
} else {
|
||||
$continue = $helper->ask($input, $output, new ConfirmationQuestion(
|
||||
sprintf('<question>' . $this->translator->translate(
|
||||
'Continue with page'
|
||||
) . ' <bg=cyan;options=bold>%s</>? (y/N)</question> ', $page),
|
||||
$continue = $io->confirm(
|
||||
\sprintf($this->translator->translate('Continue with page') . ' <options=bold>%s</>?', $page),
|
||||
false
|
||||
));
|
||||
);
|
||||
}
|
||||
} while ($continue);
|
||||
}
|
||||
|
||||
protected function processOrderBy(InputInterface $input)
|
||||
private function processOrderBy(InputInterface $input)
|
||||
{
|
||||
$orderBy = $input->getOption('orderBy');
|
||||
if (empty($orderBy)) {
|
||||
|
|
|
@ -7,15 +7,16 @@ use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
|||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class ResolveUrlCommand extends Command
|
||||
{
|
||||
const NAME = 'shortcode:parse';
|
||||
|
||||
/**
|
||||
* @var UrlShortenerInterface
|
||||
*/
|
||||
|
@ -34,7 +35,7 @@ class ResolveUrlCommand extends Command
|
|||
|
||||
public function configure()
|
||||
{
|
||||
$this->setName('shortcode:parse')
|
||||
$this->setName(self::NAME)
|
||||
->setDescription($this->translator->translate('Returns the long URL behind a short code'))
|
||||
->addArgument(
|
||||
'shortCode',
|
||||
|
@ -50,14 +51,10 @@ class ResolveUrlCommand extends Command
|
|||
return;
|
||||
}
|
||||
|
||||
/** @var QuestionHelper $helper */
|
||||
$helper = $this->getHelper('question');
|
||||
$question = new Question(sprintf(
|
||||
'<question>%s</question> ',
|
||||
$this->translator->translate('A short code was not provided. Which short code do you want to parse?:')
|
||||
));
|
||||
|
||||
$shortCode = $helper->ask($input, $output, $question);
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$shortCode = $io->ask(
|
||||
$this->translator->translate('A short code was not provided. Which short code do you want to parse?')
|
||||
);
|
||||
if (! empty($shortCode)) {
|
||||
$input->setArgument('shortCode', $shortCode);
|
||||
}
|
||||
|
@ -65,27 +62,20 @@ class ResolveUrlCommand extends Command
|
|||
|
||||
public function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$shortCode = $input->getArgument('shortCode');
|
||||
|
||||
try {
|
||||
$longUrl = $this->urlShortener->shortCodeToUrl($shortCode);
|
||||
if (! isset($longUrl)) {
|
||||
$output->writeln(sprintf(
|
||||
'<error>' . $this->translator->translate('No URL found for short code "%s"') . '</error>',
|
||||
$shortCode
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
$output->writeln(sprintf('%s <info>%s</info>', $this->translator->translate('Long URL:'), $longUrl));
|
||||
$output->writeln(\sprintf('%s <info>%s</info>', $this->translator->translate('Long URL:'), $longUrl));
|
||||
} catch (InvalidShortCodeException $e) {
|
||||
$output->writeln(sprintf('<error>' . $this->translator->translate(
|
||||
'Provided short code "%s" has an invalid format.'
|
||||
) . '</error>', $shortCode));
|
||||
$io->error(
|
||||
\sprintf($this->translator->translate('Provided short code "%s" has an invalid format.'), $shortCode)
|
||||
);
|
||||
} catch (EntityDoesNotExistException $e) {
|
||||
$output->writeln(sprintf('<error>' . $this->translator->translate(
|
||||
'Provided short code "%s" could not be found.'
|
||||
) . '</error>', $shortCode));
|
||||
$io->error(
|
||||
\sprintf($this->translator->translate('Provided short code "%s" could not be found.'), $shortCode)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,10 +8,13 @@ use Symfony\Component\Console\Command\Command;
|
|||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class CreateTagCommand extends Command
|
||||
{
|
||||
const NAME = 'tag:create';
|
||||
|
||||
/**
|
||||
* @var TagServiceInterface
|
||||
*/
|
||||
|
@ -31,7 +34,7 @@ class CreateTagCommand extends Command
|
|||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('tag:create')
|
||||
->setName(self::NAME)
|
||||
->setDescription($this->translator->translate('Creates one or more tags.'))
|
||||
->addOption(
|
||||
'name',
|
||||
|
@ -43,19 +46,15 @@ class CreateTagCommand extends Command
|
|||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$tagNames = $input->getOption('name');
|
||||
|
||||
if (empty($tagNames)) {
|
||||
$output->writeln(sprintf(
|
||||
'<comment>%s</comment>',
|
||||
$this->translator->translate('You have to provide at least one tag name')
|
||||
));
|
||||
$io->warning($this->translator->translate('You have to provide at least one tag name'));
|
||||
return;
|
||||
}
|
||||
|
||||
$this->tagService->createTags($tagNames);
|
||||
$output->writeln($this->translator->translate('Created tags') . sprintf(': ["<info>%s</info>"]', implode(
|
||||
'</info>", "<info>',
|
||||
$tagNames
|
||||
)));
|
||||
$io->success($this->translator->translate('Tags properly created'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,10 +8,13 @@ use Symfony\Component\Console\Command\Command;
|
|||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class DeleteTagsCommand extends Command
|
||||
{
|
||||
const NAME = 'tag:delete';
|
||||
|
||||
/**
|
||||
* @var TagServiceInterface
|
||||
*/
|
||||
|
@ -31,7 +34,7 @@ class DeleteTagsCommand extends Command
|
|||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('tag:delete')
|
||||
->setName(self::NAME)
|
||||
->setDescription($this->translator->translate('Deletes one or more tags.'))
|
||||
->addOption(
|
||||
'name',
|
||||
|
@ -43,19 +46,15 @@ class DeleteTagsCommand extends Command
|
|||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$tagNames = $input->getOption('name');
|
||||
|
||||
if (empty($tagNames)) {
|
||||
$output->writeln(sprintf(
|
||||
'<comment>%s</comment>',
|
||||
$this->translator->translate('You have to provide at least one tag name')
|
||||
));
|
||||
$io->warning($this->translator->translate('You have to provide at least one tag name'));
|
||||
return;
|
||||
}
|
||||
|
||||
$this->tagService->deleteTags($tagNames);
|
||||
$output->writeln($this->translator->translate('Deleted tags') . sprintf(': ["<info>%s</info>"]', implode(
|
||||
'</info>", "<info>',
|
||||
$tagNames
|
||||
)));
|
||||
$io->success($this->translator->translate('Tags properly deleted'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,13 +6,15 @@ namespace Shlinkio\Shlink\CLI\Command\Tag;
|
|||
use Shlinkio\Shlink\Core\Entity\Tag;
|
||||
use Shlinkio\Shlink\Core\Service\Tag\TagServiceInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class ListTagsCommand extends Command
|
||||
{
|
||||
const NAME = 'tag:list';
|
||||
|
||||
/**
|
||||
* @var TagServiceInterface
|
||||
*/
|
||||
|
@ -32,17 +34,14 @@ class ListTagsCommand extends Command
|
|||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('tag:list')
|
||||
->setName(self::NAME)
|
||||
->setDescription($this->translator->translate('Lists existing tags.'));
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$table = new Table($output);
|
||||
$table->setHeaders([$this->translator->translate('Name')])
|
||||
->setRows($this->getTagsRows());
|
||||
|
||||
$table->render();
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$io->table([$this->translator->translate('Name')], $this->getTagsRows());
|
||||
}
|
||||
|
||||
private function getTagsRows()
|
||||
|
@ -52,7 +51,7 @@ class ListTagsCommand extends Command
|
|||
return [[$this->translator->translate('No tags yet')]];
|
||||
}
|
||||
|
||||
return array_map(function (Tag $tag) {
|
||||
return \array_map(function (Tag $tag) {
|
||||
return [$tag->getName()];
|
||||
}, $tags);
|
||||
}
|
||||
|
|
|
@ -9,10 +9,13 @@ use Symfony\Component\Console\Command\Command;
|
|||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class RenameTagCommand extends Command
|
||||
{
|
||||
const NAME = 'tag:rename';
|
||||
|
||||
/**
|
||||
* @var TagServiceInterface
|
||||
*/
|
||||
|
@ -32,7 +35,7 @@ class RenameTagCommand extends Command
|
|||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('tag:rename')
|
||||
->setName(self::NAME)
|
||||
->setDescription($this->translator->translate('Renames one existing tag.'))
|
||||
->addArgument('oldName', InputArgument::REQUIRED, $this->translator->translate('Current name of the tag.'))
|
||||
->addArgument('newName', InputArgument::REQUIRED, $this->translator->translate('New name of the tag.'));
|
||||
|
@ -40,16 +43,15 @@ class RenameTagCommand extends Command
|
|||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$oldName = $input->getArgument('oldName');
|
||||
$newName = $input->getArgument('newName');
|
||||
|
||||
try {
|
||||
$this->tagService->renameTag($oldName, $newName);
|
||||
$output->writeln(sprintf('<info>%s</info>', $this->translator->translate('Tag properly renamed.')));
|
||||
$io->success($this->translator->translate('Tag properly renamed.'));
|
||||
} catch (EntityDoesNotExistException $e) {
|
||||
$output->writeln('<error>' . sprintf($this->translator->translate(
|
||||
'A tag with name "%s" was not found'
|
||||
), $oldName) . '</error>');
|
||||
$io->error(\sprintf($this->translator->translate('A tag with name "%s" was not found'), $oldName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,11 +10,13 @@ use Shlinkio\Shlink\Core\Service\VisitServiceInterface;
|
|||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class ProcessVisitsCommand extends Command
|
||||
{
|
||||
const LOCALHOST = '127.0.0.1';
|
||||
const NAME = 'visit:process';
|
||||
|
||||
/**
|
||||
* @var VisitServiceInterface
|
||||
|
@ -42,7 +44,7 @@ class ProcessVisitsCommand extends Command
|
|||
|
||||
public function configure()
|
||||
{
|
||||
$this->setName('visit:process')
|
||||
$this->setName(self::NAME)
|
||||
->setDescription(
|
||||
$this->translator->translate('Processes visits where location is not set yet')
|
||||
);
|
||||
|
@ -50,13 +52,14 @@ class ProcessVisitsCommand extends Command
|
|||
|
||||
public function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$visits = $this->visitService->getUnlocatedVisits();
|
||||
|
||||
foreach ($visits as $visit) {
|
||||
$ipAddr = $visit->getRemoteAddr();
|
||||
$output->write(sprintf('%s <info>%s</info>', $this->translator->translate('Processing IP'), $ipAddr));
|
||||
$io->write(sprintf('%s <info>%s</info>', $this->translator->translate('Processing IP'), $ipAddr));
|
||||
if ($ipAddr === self::LOCALHOST) {
|
||||
$output->writeln(
|
||||
$io->writeln(
|
||||
sprintf(' (<comment>%s</comment>)', $this->translator->translate('Ignored localhost address'))
|
||||
);
|
||||
continue;
|
||||
|
@ -64,11 +67,13 @@ class ProcessVisitsCommand extends Command
|
|||
|
||||
try {
|
||||
$result = $this->ipLocationResolver->resolveIpLocation($ipAddr);
|
||||
|
||||
$location = new VisitLocation();
|
||||
$location->exchangeArray($result);
|
||||
$visit->setVisitLocation($location);
|
||||
$this->visitService->saveVisit($visit);
|
||||
$output->writeln(sprintf(
|
||||
|
||||
$io->writeln(sprintf(
|
||||
' (' . $this->translator->translate('Address located at "%s"') . ')',
|
||||
$location->getCityName()
|
||||
));
|
||||
|
@ -77,6 +82,6 @@ class ProcessVisitsCommand extends Command
|
|||
}
|
||||
}
|
||||
|
||||
$output->writeln($this->translator->translate('Finished processing all IPs'));
|
||||
$io->success($this->translator->translate('Finished processing all IPs'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,11 @@ namespace Shlinkio\Shlink\CLI\Factory;
|
|||
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Interop\Container\Exception\ContainerException;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Shlinkio\Shlink\Core\Options\AppOptions;
|
||||
use Symfony\Component\Console\Application as CliApp;
|
||||
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
||||
|
@ -20,28 +23,23 @@ class ApplicationFactory implements FactoryInterface
|
|||
* @param ContainerInterface $container
|
||||
* @param string $requestedName
|
||||
* @param null|array $options
|
||||
* @return object
|
||||
* @return CliApp
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws ServiceNotFoundException if unable to resolve the service.
|
||||
* @throws ServiceNotCreatedException if an exception is raised when
|
||||
* creating a service.
|
||||
* @throws ServiceNotCreatedException if an exception is raised when creating a service.
|
||||
* @throws ContainerException if any other error occurs
|
||||
*/
|
||||
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
|
||||
public function __invoke(ContainerInterface $container, $requestedName, array $options = null): CliApp
|
||||
{
|
||||
$config = $container->get('config')['cli'];
|
||||
$appOptions = $container->get(AppOptions::class);
|
||||
$translator = $container->get(Translator::class);
|
||||
$translator->setLocale($config['locale']);
|
||||
|
||||
$commands = isset($config['commands']) ? $config['commands'] : [];
|
||||
$commands = $config['commands'] ?? [];
|
||||
$app = new CliApp($appOptions->getName(), $appOptions->getVersion());
|
||||
foreach ($commands as $command) {
|
||||
if (! $container->has($command)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$app->add($container->get($command));
|
||||
}
|
||||
$app->setCommandLoader(new ContainerCommandLoader($container, $commands));
|
||||
|
||||
return $app;
|
||||
}
|
||||
|
|
|
@ -6,9 +6,8 @@ namespace Shlinkio\Shlink\CLI\Factory;
|
|||
use Interop\Container\ContainerInterface;
|
||||
use Interop\Container\Exception\ContainerException;
|
||||
use Shlinkio\Shlink\CLI\Command\Install\InstallCommand;
|
||||
use Shlinkio\Shlink\CLI\Install\ConfigCustomizerPluginManager;
|
||||
use Shlinkio\Shlink\CLI\Install\ConfigCustomizerManager;
|
||||
use Shlinkio\Shlink\CLI\Install\Plugin;
|
||||
use Shlinkio\Shlink\CLI\Install\Plugin\Factory\DefaultConfigCustomizerPluginFactory;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Exception\LogicException;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
|
@ -17,6 +16,7 @@ use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
|||
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
||||
use Zend\ServiceManager\Factory\FactoryInterface;
|
||||
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||
|
||||
class InstallApplicationFactory implements FactoryInterface
|
||||
{
|
||||
|
@ -41,11 +41,11 @@ class InstallApplicationFactory implements FactoryInterface
|
|||
$command = new InstallCommand(
|
||||
new PhpArray(),
|
||||
$container->get(Filesystem::class),
|
||||
new ConfigCustomizerPluginManager($container, ['factories' => [
|
||||
Plugin\DatabaseConfigCustomizerPlugin::class => ConfigAbstractFactory::class,
|
||||
Plugin\UrlShortenerConfigCustomizerPlugin::class => DefaultConfigCustomizerPluginFactory::class,
|
||||
Plugin\LanguageConfigCustomizerPlugin::class => DefaultConfigCustomizerPluginFactory::class,
|
||||
Plugin\ApplicationConfigCustomizerPlugin::class => DefaultConfigCustomizerPluginFactory::class,
|
||||
new ConfigCustomizerManager($container, ['factories' => [
|
||||
Plugin\DatabaseConfigCustomizer::class => ConfigAbstractFactory::class,
|
||||
Plugin\UrlShortenerConfigCustomizer::class => InvokableFactory::class,
|
||||
Plugin\LanguageConfigCustomizer::class => InvokableFactory::class,
|
||||
Plugin\ApplicationConfigCustomizer::class => InvokableFactory::class,
|
||||
]]),
|
||||
$isUpdate
|
||||
);
|
||||
|
|
12
module/CLI/src/Install/ConfigCustomizerManager.php
Normal file
12
module/CLI/src/Install/ConfigCustomizerManager.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\CLI\Install;
|
||||
|
||||
use Shlinkio\Shlink\CLI\Install\Plugin\ConfigCustomizerInterface;
|
||||
use Zend\ServiceManager\AbstractPluginManager;
|
||||
|
||||
class ConfigCustomizerManager extends AbstractPluginManager implements ConfigCustomizerManagerInterface
|
||||
{
|
||||
protected $instanceOf = ConfigCustomizerInterface::class;
|
||||
}
|
|
@ -5,6 +5,6 @@ namespace Shlinkio\Shlink\CLI\Install;
|
|||
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
interface ConfigCustomizerPluginManagerInterface extends ContainerInterface
|
||||
interface ConfigCustomizerManagerInterface extends ContainerInterface
|
||||
{
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\CLI\Install;
|
||||
|
||||
use Shlinkio\Shlink\CLI\Install\Plugin\ConfigCustomizerPluginInterface;
|
||||
use Zend\ServiceManager\AbstractPluginManager;
|
||||
|
||||
class ConfigCustomizerPluginManager extends AbstractPluginManager implements ConfigCustomizerPluginManagerInterface
|
||||
{
|
||||
protected $instanceOf = ConfigCustomizerPluginInterface::class;
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
||||
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
|
||||
abstract class AbstractConfigCustomizerPlugin implements ConfigCustomizerPluginInterface
|
||||
{
|
||||
/**
|
||||
* @var QuestionHelper
|
||||
*/
|
||||
protected $questionHelper;
|
||||
|
||||
public function __construct(QuestionHelper $questionHelper)
|
||||
{
|
||||
$this->questionHelper = $questionHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
* @param string $text
|
||||
* @param string|null $default
|
||||
* @param bool $allowEmpty
|
||||
* @return string
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
protected function ask(InputInterface $input, OutputInterface $output, $text, $default = null, $allowEmpty = false)
|
||||
{
|
||||
if ($default !== null) {
|
||||
$text .= ' (defaults to ' . $default . ')';
|
||||
}
|
||||
do {
|
||||
$value = $this->questionHelper->ask($input, $output, new Question(
|
||||
'<question>' . $text . ':</question> ',
|
||||
$default
|
||||
));
|
||||
if (empty($value) && ! $allowEmpty) {
|
||||
$output->writeln('<error>Value can\'t be empty</error>');
|
||||
}
|
||||
} while (empty($value) && $default === null && ! $allowEmpty);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OutputInterface $output
|
||||
* @param string $text
|
||||
*/
|
||||
protected function printTitle(OutputInterface $output, $text)
|
||||
{
|
||||
$text = trim($text);
|
||||
$length = strlen($text) + 4;
|
||||
$header = str_repeat('*', $length);
|
||||
|
||||
$output->writeln([
|
||||
'',
|
||||
'<info>' . $header . '</info>',
|
||||
'<info>* ' . strtoupper($text) . ' *</info>',
|
||||
'<info>' . $header . '</info>',
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
||||
|
||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||
use Shlinkio\Shlink\Common\Util\StringUtilsTrait;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
class ApplicationConfigCustomizer implements ConfigCustomizerInterface
|
||||
{
|
||||
use StringUtilsTrait;
|
||||
|
||||
/**
|
||||
* @param SymfonyStyle $io
|
||||
* @param CustomizableAppConfig $appConfig
|
||||
* @return void
|
||||
*/
|
||||
public function process(SymfonyStyle $io, CustomizableAppConfig $appConfig)
|
||||
{
|
||||
$io->title('APPLICATION');
|
||||
|
||||
if ($appConfig->hasApp() && $io->confirm('Do you want to keep imported application config?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$validator = function ($value) {
|
||||
return $value;
|
||||
};
|
||||
$appConfig->setApp([
|
||||
'SECRET' => $io->ask(
|
||||
'Define a secret string that will be used to sign API tokens (leave empty to autogenerate one)',
|
||||
null,
|
||||
$validator
|
||||
) ?: $this->generateRandomString(32),
|
||||
'DISABLE_TRACK_PARAM' => $io->ask(
|
||||
'Provide a parameter name that you will be able to use to disable tracking on specific request to '
|
||||
. 'short URLs (leave empty and this feature won\'t be enabled)',
|
||||
null,
|
||||
$validator
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
||||
|
||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||
use Shlinkio\Shlink\Common\Util\StringUtilsTrait;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
|
||||
class ApplicationConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin
|
||||
{
|
||||
use StringUtilsTrait;
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
* @param CustomizableAppConfig $appConfig
|
||||
* @return void
|
||||
* @throws \Symfony\Component\Console\Exception\RuntimeException
|
||||
*/
|
||||
public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig)
|
||||
{
|
||||
$this->printTitle($output, 'APPLICATION');
|
||||
|
||||
if ($appConfig->hasApp() && $this->questionHelper->ask($input, $output, new ConfirmationQuestion(
|
||||
'<question>Do you want to keep imported application config? (Y/n):</question> '
|
||||
))) {
|
||||
return;
|
||||
}
|
||||
|
||||
$appConfig->setApp([
|
||||
'SECRET' => $this->ask(
|
||||
$input,
|
||||
$output,
|
||||
'Define a secret string that will be used to sign API tokens (leave empty to autogenerate one)',
|
||||
null,
|
||||
true
|
||||
) ?: $this->generateRandomString(32),
|
||||
]);
|
||||
}
|
||||
}
|
17
module/CLI/src/Install/Plugin/ConfigCustomizerInterface.php
Normal file
17
module/CLI/src/Install/Plugin/ConfigCustomizerInterface.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
||||
|
||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
interface ConfigCustomizerInterface
|
||||
{
|
||||
/**
|
||||
* @param SymfonyStyle $io
|
||||
* @param CustomizableAppConfig $appConfig
|
||||
* @return void
|
||||
*/
|
||||
public function process(SymfonyStyle $io, CustomizableAppConfig $appConfig);
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
||||
|
||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
interface ConfigCustomizerPluginInterface
|
||||
{
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
* @param CustomizableAppConfig $appConfig
|
||||
* @return void
|
||||
*/
|
||||
public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig);
|
||||
}
|
78
module/CLI/src/Install/Plugin/DatabaseConfigCustomizer.php
Normal file
78
module/CLI/src/Install/Plugin/DatabaseConfigCustomizer.php
Normal file
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
||||
|
||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Filesystem\Exception\IOException;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
|
||||
class DatabaseConfigCustomizer implements ConfigCustomizerInterface
|
||||
{
|
||||
const DATABASE_DRIVERS = [
|
||||
'MySQL' => 'pdo_mysql',
|
||||
'PostgreSQL' => 'pdo_pgsql',
|
||||
'SQLite' => 'pdo_sqlite',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var Filesystem
|
||||
*/
|
||||
private $filesystem;
|
||||
|
||||
public function __construct(Filesystem $filesystem)
|
||||
{
|
||||
$this->filesystem = $filesystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SymfonyStyle $io
|
||||
* @param CustomizableAppConfig $appConfig
|
||||
* @return void
|
||||
* @throws IOException
|
||||
*/
|
||||
public function process(SymfonyStyle $io, CustomizableAppConfig $appConfig)
|
||||
{
|
||||
$io->title('DATABASE');
|
||||
|
||||
if ($appConfig->hasDatabase() && $io->confirm('Do you want to keep imported database config?')) {
|
||||
// If the user selected to keep DB config and is configured to use sqlite, copy DB file
|
||||
if ($appConfig->getDatabase()['DRIVER'] === self::DATABASE_DRIVERS['SQLite']) {
|
||||
try {
|
||||
$this->filesystem->copy(
|
||||
$appConfig->getImportedInstallationPath() . '/' . CustomizableAppConfig::SQLITE_DB_PATH,
|
||||
CustomizableAppConfig::SQLITE_DB_PATH
|
||||
);
|
||||
} catch (IOException $e) {
|
||||
$io->error('It wasn\'t possible to import the SQLite database');
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Select database type
|
||||
$params = [];
|
||||
$databases = \array_keys(self::DATABASE_DRIVERS);
|
||||
$dbType = $io->choice('Select database type', $databases, $databases[0]);
|
||||
$params['DRIVER'] = self::DATABASE_DRIVERS[$dbType];
|
||||
|
||||
// Ask for connection params if database is not SQLite
|
||||
if ($params['DRIVER'] !== self::DATABASE_DRIVERS['SQLite']) {
|
||||
$params['NAME'] = $io->ask('Database name', 'shlink');
|
||||
$params['USER'] = $io->ask('Database username');
|
||||
$params['PASSWORD'] = $io->ask('Database password');
|
||||
$params['HOST'] = $io->ask('Database host', 'localhost');
|
||||
$params['PORT'] = $io->ask('Database port', $this->getDefaultDbPort($params['DRIVER']));
|
||||
}
|
||||
|
||||
$appConfig->setDatabase($params);
|
||||
}
|
||||
|
||||
private function getDefaultDbPort(string $driver): string
|
||||
{
|
||||
return $driver === 'pdo_mysql' ? '3306' : '5432';
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation as DI;
|
||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
use Symfony\Component\Filesystem\Exception\IOException;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
|
||||
class DatabaseConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin
|
||||
{
|
||||
const DATABASE_DRIVERS = [
|
||||
'MySQL' => 'pdo_mysql',
|
||||
'PostgreSQL' => 'pdo_pgsql',
|
||||
'SQLite' => 'pdo_sqlite',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var Filesystem
|
||||
*/
|
||||
private $filesystem;
|
||||
|
||||
/**
|
||||
* DatabaseConfigCustomizerPlugin constructor.
|
||||
* @param QuestionHelper $questionHelper
|
||||
* @param Filesystem $filesystem
|
||||
*
|
||||
* @DI\Inject({QuestionHelper::class, Filesystem::class})
|
||||
*/
|
||||
public function __construct(QuestionHelper $questionHelper, Filesystem $filesystem)
|
||||
{
|
||||
parent::__construct($questionHelper);
|
||||
$this->filesystem = $filesystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
* @param CustomizableAppConfig $appConfig
|
||||
* @return void
|
||||
* @throws IOException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig)
|
||||
{
|
||||
$this->printTitle($output, 'DATABASE');
|
||||
|
||||
if ($appConfig->hasDatabase() && $this->questionHelper->ask($input, $output, new ConfirmationQuestion(
|
||||
'<question>Do you want to keep imported database config? (Y/n):</question> '
|
||||
))) {
|
||||
// If the user selected to keep DB config and is configured to use sqlite, copy DB file
|
||||
if ($appConfig->getDatabase()['DRIVER'] === self::DATABASE_DRIVERS['SQLite']) {
|
||||
try {
|
||||
$this->filesystem->copy(
|
||||
$appConfig->getImportedInstallationPath() . '/' . CustomizableAppConfig::SQLITE_DB_PATH,
|
||||
CustomizableAppConfig::SQLITE_DB_PATH
|
||||
);
|
||||
} catch (IOException $e) {
|
||||
$output->writeln('<error>It wasn\'t possible to import the SQLite database</error>');
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Select database type
|
||||
$params = [];
|
||||
$databases = array_keys(self::DATABASE_DRIVERS);
|
||||
$dbType = $this->questionHelper->ask($input, $output, new ChoiceQuestion(
|
||||
'<question>Select database type (defaults to ' . $databases[0] . '):</question>',
|
||||
$databases,
|
||||
0
|
||||
));
|
||||
$params['DRIVER'] = self::DATABASE_DRIVERS[$dbType];
|
||||
|
||||
// Ask for connection params if database is not SQLite
|
||||
if ($params['DRIVER'] !== self::DATABASE_DRIVERS['SQLite']) {
|
||||
$params['NAME'] = $this->ask($input, $output, 'Database name', 'shlink');
|
||||
$params['USER'] = $this->ask($input, $output, 'Database username');
|
||||
$params['PASSWORD'] = $this->ask($input, $output, 'Database password');
|
||||
$params['HOST'] = $this->ask($input, $output, 'Database host', 'localhost');
|
||||
$params['PORT'] = $this->ask($input, $output, 'Database port', $this->getDefaultDbPort($params['DRIVER']));
|
||||
}
|
||||
|
||||
$appConfig->setDatabase($params);
|
||||
}
|
||||
|
||||
private function getDefaultDbPort($driver)
|
||||
{
|
||||
return $driver === 'pdo_mysql' ? '3306' : '5432';
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\CLI\Install\Plugin\Factory;
|
||||
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Interop\Container\Exception\ContainerException;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
||||
use Zend\ServiceManager\Factory\FactoryInterface;
|
||||
|
||||
class DefaultConfigCustomizerPluginFactory implements FactoryInterface
|
||||
{
|
||||
/**
|
||||
* Create an object
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
* @param string $requestedName
|
||||
* @param null|array $options
|
||||
* @return object
|
||||
* @throws ServiceNotFoundException if unable to resolve the service.
|
||||
* @throws ServiceNotCreatedException if an exception is raised when
|
||||
* creating a service.
|
||||
* @throws ContainerException if any other error occurs
|
||||
*/
|
||||
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
|
||||
{
|
||||
return new $requestedName($container->get(QuestionHelper::class));
|
||||
}
|
||||
}
|
36
module/CLI/src/Install/Plugin/LanguageConfigCustomizer.php
Normal file
36
module/CLI/src/Install/Plugin/LanguageConfigCustomizer.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
||||
|
||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
class LanguageConfigCustomizer implements ConfigCustomizerInterface
|
||||
{
|
||||
const SUPPORTED_LANGUAGES = ['en', 'es'];
|
||||
|
||||
/**
|
||||
* @param SymfonyStyle $io
|
||||
* @param CustomizableAppConfig $appConfig
|
||||
* @return void
|
||||
*/
|
||||
public function process(SymfonyStyle $io, CustomizableAppConfig $appConfig)
|
||||
{
|
||||
$io->title('LANGUAGE');
|
||||
|
||||
if ($appConfig->hasLanguage() && $io->confirm('Do you want to keep imported language?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$appConfig->setLanguage([
|
||||
'DEFAULT' => $this->chooseLanguage('Select default language for the application in general', $io),
|
||||
'CLI' => $this->chooseLanguage('Select default language for CLI executions', $io),
|
||||
]);
|
||||
}
|
||||
|
||||
private function chooseLanguage(string $message, SymfonyStyle $io): string
|
||||
{
|
||||
return $io->choice($message, self::SUPPORTED_LANGUAGES, self::SUPPORTED_LANGUAGES[0]);
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
||||
|
||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
|
||||
class LanguageConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin
|
||||
{
|
||||
const SUPPORTED_LANGUAGES = ['en', 'es'];
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
* @param CustomizableAppConfig $appConfig
|
||||
* @return void
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig)
|
||||
{
|
||||
$this->printTitle($output, 'LANGUAGE');
|
||||
|
||||
if ($appConfig->hasLanguage() && $this->questionHelper->ask($input, $output, new ConfirmationQuestion(
|
||||
'<question>Do you want to keep imported language? (Y/n):</question> '
|
||||
))) {
|
||||
return;
|
||||
}
|
||||
|
||||
$appConfig->setLanguage([
|
||||
'DEFAULT' => $this->questionHelper->ask($input, $output, new ChoiceQuestion(
|
||||
'<question>Select default language for the application in general (defaults to '
|
||||
. self::SUPPORTED_LANGUAGES[0] . '):</question>',
|
||||
self::SUPPORTED_LANGUAGES,
|
||||
0
|
||||
)),
|
||||
'CLI' => $this->questionHelper->ask($input, $output, new ChoiceQuestion(
|
||||
'<question>Select default language for CLI executions (defaults to '
|
||||
. self::SUPPORTED_LANGUAGES[0] . '):</question>',
|
||||
self::SUPPORTED_LANGUAGES,
|
||||
0
|
||||
)),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
||||
|
||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
class UrlShortenerConfigCustomizer implements ConfigCustomizerInterface
|
||||
{
|
||||
/**
|
||||
* @param SymfonyStyle $io
|
||||
* @param CustomizableAppConfig $appConfig
|
||||
* @return void
|
||||
*/
|
||||
public function process(SymfonyStyle $io, CustomizableAppConfig $appConfig)
|
||||
{
|
||||
$io->title('URL SHORTENER');
|
||||
|
||||
if ($appConfig->hasUrlShortener() && $io->confirm('Do you want to keep imported URL shortener config?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ask for URL shortener params
|
||||
$appConfig->setUrlShortener([
|
||||
'SCHEMA' => $io->choice(
|
||||
'Select schema for generated short URLs',
|
||||
['http', 'https'],
|
||||
'http'
|
||||
),
|
||||
'HOSTNAME' => $io->ask('Hostname for generated URLs'),
|
||||
'CHARS' => $io->ask(
|
||||
'Character set for generated short codes (leave empty to autogenerate one)',
|
||||
null,
|
||||
function ($value) {
|
||||
return $value;
|
||||
}
|
||||
) ?: \str_shuffle(UrlShortener::DEFAULT_CHARS),
|
||||
'VALIDATE_URL' => $io->confirm('Do you want to validate long urls by 200 HTTP status code on response'),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
||||
|
||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
|
||||
class UrlShortenerConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin
|
||||
{
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
* @param CustomizableAppConfig $appConfig
|
||||
* @return void
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig)
|
||||
{
|
||||
$this->printTitle($output, 'URL SHORTENER');
|
||||
|
||||
if ($appConfig->hasUrlShortener() && $this->questionHelper->ask($input, $output, new ConfirmationQuestion(
|
||||
'<question>Do you want to keep imported URL shortener config? (Y/n):</question> '
|
||||
))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ask for URL shortener params
|
||||
$appConfig->setUrlShortener([
|
||||
'SCHEMA' => $this->questionHelper->ask($input, $output, new ChoiceQuestion(
|
||||
'<question>Select schema for generated short URLs (defaults to http):</question>',
|
||||
['http', 'https'],
|
||||
0
|
||||
)),
|
||||
'HOSTNAME' => $this->ask($input, $output, 'Hostname for generated URLs'),
|
||||
'CHARS' => $this->ask(
|
||||
$input,
|
||||
$output,
|
||||
'Character set for generated short codes (leave empty to autogenerate one)',
|
||||
null,
|
||||
true
|
||||
) ?: str_shuffle(UrlShortener::DEFAULT_CHARS),
|
||||
'VALIDATE_URL' => $this->questionHelper->ask(
|
||||
$input,
|
||||
$output,
|
||||
new ConfirmationQuestion(
|
||||
'<question>Do you want to validate long urls by 200 HTTP status code on response (Y/n):</question>'
|
||||
)
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -225,6 +225,7 @@ final class CustomizableAppConfig implements ArraySerializableInterface
|
|||
$config = [
|
||||
'app_options' => [
|
||||
'secret_key' => $this->app['SECRET'],
|
||||
'disable_track_param' => $this->app['DISABLE_TRACK_PARAM'],
|
||||
],
|
||||
'entity_manager' => [
|
||||
'connection' => [
|
||||
|
|
|
@ -59,6 +59,6 @@ class DisableKeyCommandTest extends TestCase
|
|||
'apiKey' => $apiKey,
|
||||
]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
$this->assertEquals('API key "abcd1234" does not exist.' . PHP_EOL, $output);
|
||||
$this->assertContains('API key "abcd1234" does not exist.', $output);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ namespace ShlinkioTest\Shlink\CLI\Command\Config;
|
|||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\CLI\Command\Config\GenerateCharsetCommand;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
|
@ -32,7 +31,6 @@ class GenerateCharsetCommandTest extends TestCase
|
|||
public function charactersAreGeneratedFromDefault()
|
||||
{
|
||||
$prefix = 'Character set: ';
|
||||
$prefixLength = strlen($prefix);
|
||||
|
||||
$this->commandTester->execute([
|
||||
'command' => 'config:generate-charset',
|
||||
|
@ -40,13 +38,7 @@ class GenerateCharsetCommandTest extends TestCase
|
|||
$output = $this->commandTester->getDisplay();
|
||||
|
||||
// Both default character set and the new one should have the same length
|
||||
$this->assertEquals($prefixLength + strlen(UrlShortener::DEFAULT_CHARS) + 1, strlen($output));
|
||||
|
||||
// Both default character set and the new one should have the same characters
|
||||
$charset = substr($output, $prefixLength, strlen(UrlShortener::DEFAULT_CHARS));
|
||||
$orderedDefault = $this->orderStringLetters(UrlShortener::DEFAULT_CHARS);
|
||||
$orderedCharset = $this->orderStringLetters($charset);
|
||||
$this->assertEquals($orderedDefault, $orderedCharset);
|
||||
$this->assertContains($prefix, $output);
|
||||
}
|
||||
|
||||
protected function orderStringLetters($string)
|
||||
|
|
|
@ -8,8 +8,8 @@ use Prophecy\Argument;
|
|||
use Prophecy\Prophecy\MethodProphecy;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Command\Install\InstallCommand;
|
||||
use Shlinkio\Shlink\CLI\Install\ConfigCustomizerPluginManagerInterface;
|
||||
use Shlinkio\Shlink\CLI\Install\Plugin\ConfigCustomizerPluginInterface;
|
||||
use Shlinkio\Shlink\CLI\Install\ConfigCustomizerManagerInterface;
|
||||
use Shlinkio\Shlink\CLI\Install\Plugin\ConfigCustomizerInterface;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Helper\ProcessHelper;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
@ -51,8 +51,8 @@ class InstallCommandTest extends TestCase
|
|||
|
||||
$this->configWriter = $this->prophesize(WriterInterface::class);
|
||||
|
||||
$configCustomizer = $this->prophesize(ConfigCustomizerPluginInterface::class);
|
||||
$configCustomizers = $this->prophesize(ConfigCustomizerPluginManagerInterface::class);
|
||||
$configCustomizer = $this->prophesize(ConfigCustomizerInterface::class);
|
||||
$configCustomizers = $this->prophesize(ConfigCustomizerManagerInterface::class);
|
||||
$configCustomizers->get(Argument::cetera())->willReturn($configCustomizer->reveal());
|
||||
|
||||
$app = new Application();
|
||||
|
|
|
@ -65,8 +65,9 @@ class GenerateShortcodeCommandTest extends TestCase
|
|||
'longUrl' => 'http://domain.com/invalid',
|
||||
]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
$this->assertTrue(
|
||||
strpos($output, 'Provided URL "http://domain.com/invalid" is invalid. Try with a different one.') === 0
|
||||
$this->assertContains(
|
||||
'Provided URL "http://domain.com/invalid" is invalid.',
|
||||
$output
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ use Shlinkio\Shlink\CLI\Command\Shortcode\ListShortcodesCommand;
|
|||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
use Zend\Paginator\Adapter\ArrayAdapter;
|
||||
|
@ -22,10 +21,6 @@ class ListShortcodesCommandTest extends TestCase
|
|||
* @var CommandTester
|
||||
*/
|
||||
protected $commandTester;
|
||||
/**
|
||||
* @var QuestionHelper
|
||||
*/
|
||||
protected $questionHelper;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
|
@ -37,8 +32,6 @@ class ListShortcodesCommandTest extends TestCase
|
|||
$app = new Application();
|
||||
$command = new ListShortcodesCommand($this->shortUrlService->reveal(), Translator::factory([]));
|
||||
$app->add($command);
|
||||
|
||||
$this->questionHelper = $command->getHelper('question');
|
||||
$this->commandTester = new CommandTester($command);
|
||||
}
|
||||
|
||||
|
@ -47,10 +40,10 @@ class ListShortcodesCommandTest extends TestCase
|
|||
*/
|
||||
public function noInputCallsListJustOnce()
|
||||
{
|
||||
$this->questionHelper->setInputStream($this->getInputStream('\n'));
|
||||
$this->shortUrlService->listShortUrls(1, null, [], null)->willReturn(new Paginator(new ArrayAdapter()))
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$this->commandTester->setInputs(['n']);
|
||||
$this->commandTester->execute(['command' => 'shortcode:list']);
|
||||
}
|
||||
|
||||
|
@ -61,22 +54,15 @@ class ListShortcodesCommandTest extends TestCase
|
|||
{
|
||||
// The paginator will return more than one page for the first 3 times
|
||||
$data = [];
|
||||
for ($i = 0; $i < 30; $i++) {
|
||||
for ($i = 0; $i < 50; $i++) {
|
||||
$data[] = new ShortUrl();
|
||||
}
|
||||
$data = array_chunk($data, 11);
|
||||
|
||||
$questionHelper = $this->questionHelper;
|
||||
$that = $this;
|
||||
$this->shortUrlService->listShortUrls(Argument::cetera())->will(function () use (
|
||||
&$data,
|
||||
$questionHelper,
|
||||
$that
|
||||
) {
|
||||
$questionHelper->setInputStream($that->getInputStream('y'));
|
||||
return new Paginator(new ArrayAdapter(array_shift($data)));
|
||||
$this->shortUrlService->listShortUrls(Argument::cetera())->will(function () use (&$data) {
|
||||
return new Paginator(new ArrayAdapter($data));
|
||||
})->shouldBeCalledTimes(3);
|
||||
|
||||
$this->commandTester->setInputs(['y', 'y', 'n']);
|
||||
$this->commandTester->execute(['command' => 'shortcode:list']);
|
||||
}
|
||||
|
||||
|
@ -91,10 +77,10 @@ class ListShortcodesCommandTest extends TestCase
|
|||
$data[] = new ShortUrl();
|
||||
}
|
||||
|
||||
$this->questionHelper->setInputStream($this->getInputStream('n'));
|
||||
$this->shortUrlService->listShortUrls(Argument::cetera())->willReturn(new Paginator(new ArrayAdapter($data)))
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$this->commandTester->setInputs(['n']);
|
||||
$this->commandTester->execute(['command' => 'shortcode:list']);
|
||||
}
|
||||
|
||||
|
@ -104,10 +90,10 @@ class ListShortcodesCommandTest extends TestCase
|
|||
public function passingPageWillMakeListStartOnThatPage()
|
||||
{
|
||||
$page = 5;
|
||||
$this->questionHelper->setInputStream($this->getInputStream('\n'));
|
||||
$this->shortUrlService->listShortUrls($page, null, [], null)->willReturn(new Paginator(new ArrayAdapter()))
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$this->commandTester->setInputs(['y']);
|
||||
$this->commandTester->execute([
|
||||
'command' => 'shortcode:list',
|
||||
'--page' => $page,
|
||||
|
@ -119,24 +105,15 @@ class ListShortcodesCommandTest extends TestCase
|
|||
*/
|
||||
public function ifTagsFlagIsProvidedTagsColumnIsIncluded()
|
||||
{
|
||||
$this->questionHelper->setInputStream($this->getInputStream('\n'));
|
||||
$this->shortUrlService->listShortUrls(1, null, [], null)->willReturn(new Paginator(new ArrayAdapter()))
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$this->commandTester->setInputs(['y']);
|
||||
$this->commandTester->execute([
|
||||
'command' => 'shortcode:list',
|
||||
'--showTags' => true,
|
||||
]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
$this->assertTrue(strpos($output, 'Tags') > 0);
|
||||
}
|
||||
|
||||
protected function getInputStream($inputData)
|
||||
{
|
||||
$stream = fopen('php://memory', 'r+', false);
|
||||
fputs($stream, $inputData);
|
||||
rewind($stream);
|
||||
|
||||
return $stream;
|
||||
$this->assertContains('Tags', $output);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ class ResolveUrlCommandTest extends TestCase
|
|||
'shortCode' => $shortCode,
|
||||
]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
$this->assertEquals('Provided short code "' . $shortCode . '" could not be found.' . PHP_EOL, $output);
|
||||
$this->assertContains('Provided short code "' . $shortCode . '" could not be found.', $output);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -83,6 +83,6 @@ class ResolveUrlCommandTest extends TestCase
|
|||
'shortCode' => $shortCode,
|
||||
]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
$this->assertEquals('Provided short code "' . $shortCode . '" has an invalid format.' . PHP_EOL, $output);
|
||||
$this->assertContains('Provided short code "' . $shortCode . '" has an invalid format.', $output);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,10 +14,6 @@ use Zend\I18n\Translator\Translator;
|
|||
|
||||
class CreateTagCommandTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var CreateTagCommand
|
||||
*/
|
||||
private $command;
|
||||
/**
|
||||
* @var CommandTester
|
||||
*/
|
||||
|
@ -63,7 +59,7 @@ class CreateTagCommandTest extends TestCase
|
|||
]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
|
||||
$this->assertContains(sprintf('Created tags: ["%s"]', implode('", "', $tagNames)), $output);
|
||||
$this->assertContains('Tags properly created', $output);
|
||||
$createTags->shouldHaveBeenCalled();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ class DeleteTagsCommandTest extends TestCase
|
|||
]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
|
||||
$this->assertContains(sprintf('Deleted tags: ["%s"]', implode('", "', $tagNames)), $output);
|
||||
$this->assertContains('Tags properly deleted', $output);
|
||||
$deleteTags->shouldHaveBeenCalled();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\CLI\Install\Plugin;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\MethodProphecy;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Install\Plugin\ApplicationConfigCustomizerPlugin;
|
||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Output\NullOutput;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
|
||||
class ApplicationConfigCustomizerPluginTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var ApplicationConfigCustomizerPlugin
|
||||
*/
|
||||
private $plugin;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
private $questionHelper;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->questionHelper = $this->prophesize(QuestionHelper::class);
|
||||
$this->plugin = new ApplicationConfigCustomizerPlugin($this->questionHelper->reveal());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function configIsRequestedToTheUser()
|
||||
{
|
||||
/** @var MethodProphecy $askSecret */
|
||||
$askSecret = $this->questionHelper->ask(Argument::cetera())->willReturn('the_secret');
|
||||
$config = new CustomizableAppConfig();
|
||||
|
||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
||||
|
||||
$this->assertTrue($config->hasApp());
|
||||
$this->assertEquals([
|
||||
'SECRET' => 'the_secret',
|
||||
], $config->getApp());
|
||||
$askSecret->shouldHaveBeenCalledTimes(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function overwriteIsRequestedIfValueIsAlreadySet()
|
||||
{
|
||||
/** @var MethodProphecy $ask */
|
||||
$ask = $this->questionHelper->ask(Argument::cetera())->will(function (array $args) {
|
||||
$last = array_pop($args);
|
||||
return $last instanceof ConfirmationQuestion ? false : 'the_new_secret';
|
||||
});
|
||||
$config = new CustomizableAppConfig();
|
||||
$config->setApp([
|
||||
'SECRET' => 'foo',
|
||||
]);
|
||||
|
||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
||||
|
||||
$this->assertEquals([
|
||||
'SECRET' => 'the_new_secret',
|
||||
], $config->getApp());
|
||||
$ask->shouldHaveBeenCalledTimes(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function existingValueIsKeptIfRequested()
|
||||
{
|
||||
/** @var MethodProphecy $ask */
|
||||
$ask = $this->questionHelper->ask(Argument::cetera())->willReturn(true);
|
||||
|
||||
$config = new CustomizableAppConfig();
|
||||
$config->setApp([
|
||||
'SECRET' => 'foo',
|
||||
]);
|
||||
|
||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
||||
|
||||
$this->assertEquals([
|
||||
'SECRET' => 'foo',
|
||||
], $config->getApp());
|
||||
$ask->shouldHaveBeenCalledTimes(1);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\CLI\Install\Plugin;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Install\Plugin\ApplicationConfigCustomizer;
|
||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
class ApplicationConfigCustomizerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var ApplicationConfigCustomizer
|
||||
*/
|
||||
private $plugin;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
private $io;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->io = $this->prophesize(SymfonyStyle::class);
|
||||
$this->io->title(Argument::any())->willReturn(null);
|
||||
|
||||
$this->plugin = new ApplicationConfigCustomizer();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function configIsRequestedToTheUser()
|
||||
{
|
||||
$ask = $this->io->ask(Argument::cetera())->willReturn('the_secret');
|
||||
$config = new CustomizableAppConfig();
|
||||
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertTrue($config->hasApp());
|
||||
$this->assertEquals([
|
||||
'SECRET' => 'the_secret',
|
||||
'DISABLE_TRACK_PARAM' => 'the_secret',
|
||||
], $config->getApp());
|
||||
$ask->shouldHaveBeenCalledTimes(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function overwriteIsRequestedIfValueIsAlreadySet()
|
||||
{
|
||||
$ask = $this->io->ask(Argument::cetera())->willReturn('the_new_secret');
|
||||
$confirm = $this->io->confirm(Argument::cetera())->willReturn(false);
|
||||
$config = new CustomizableAppConfig();
|
||||
$config->setApp([
|
||||
'SECRET' => 'foo',
|
||||
]);
|
||||
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertEquals([
|
||||
'SECRET' => 'the_new_secret',
|
||||
'DISABLE_TRACK_PARAM' => 'the_new_secret',
|
||||
], $config->getApp());
|
||||
$ask->shouldHaveBeenCalledTimes(2);
|
||||
$confirm->shouldHaveBeenCalledTimes(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function existingValueIsKeptIfRequested()
|
||||
{
|
||||
$confirm = $this->io->confirm(Argument::cetera())->willReturn(true);
|
||||
|
||||
$config = new CustomizableAppConfig();
|
||||
$config->setApp([
|
||||
'SECRET' => 'foo',
|
||||
]);
|
||||
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertEquals([
|
||||
'SECRET' => 'foo',
|
||||
], $config->getApp());
|
||||
$confirm->shouldHaveBeenCalledTimes(1);
|
||||
}
|
||||
}
|
|
@ -5,26 +5,22 @@ namespace ShlinkioTest\Shlink\CLI\Install\Plugin;
|
|||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\MethodProphecy;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Install\Plugin\DatabaseConfigCustomizerPlugin;
|
||||
use Shlinkio\Shlink\CLI\Install\Plugin\DatabaseConfigCustomizer;
|
||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Output\NullOutput;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
|
||||
class DatabaseConfigCustomizerPluginTest extends TestCase
|
||||
class DatabaseConfigCustomizerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var DatabaseConfigCustomizerPlugin
|
||||
* @var DatabaseConfigCustomizer
|
||||
*/
|
||||
private $plugin;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
private $questionHelper;
|
||||
private $io;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
|
@ -32,13 +28,11 @@ class DatabaseConfigCustomizerPluginTest extends TestCase
|
|||
|
||||
public function setUp()
|
||||
{
|
||||
$this->questionHelper = $this->prophesize(QuestionHelper::class);
|
||||
$this->io = $this->prophesize(SymfonyStyle::class);
|
||||
$this->io->title(Argument::any())->willReturn(null);
|
||||
$this->filesystem = $this->prophesize(Filesystem::class);
|
||||
|
||||
$this->plugin = new DatabaseConfigCustomizerPlugin(
|
||||
$this->questionHelper->reveal(),
|
||||
$this->filesystem->reveal()
|
||||
);
|
||||
$this->plugin = new DatabaseConfigCustomizer($this->filesystem->reveal());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,22 +40,23 @@ class DatabaseConfigCustomizerPluginTest extends TestCase
|
|||
*/
|
||||
public function configIsRequestedToTheUser()
|
||||
{
|
||||
/** @var MethodProphecy $askSecret */
|
||||
$askSecret = $this->questionHelper->ask(Argument::cetera())->willReturn('MySQL');
|
||||
$choice = $this->io->choice(Argument::cetera())->willReturn('MySQL');
|
||||
$ask = $this->io->ask(Argument::cetera())->willReturn('param');
|
||||
$config = new CustomizableAppConfig();
|
||||
|
||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertTrue($config->hasDatabase());
|
||||
$this->assertEquals([
|
||||
'DRIVER' => 'pdo_mysql',
|
||||
'NAME' => 'MySQL',
|
||||
'USER' => 'MySQL',
|
||||
'PASSWORD' => 'MySQL',
|
||||
'HOST' => 'MySQL',
|
||||
'PORT' => 'MySQL',
|
||||
'NAME' => 'param',
|
||||
'USER' => 'param',
|
||||
'PASSWORD' => 'param',
|
||||
'HOST' => 'param',
|
||||
'PORT' => 'param',
|
||||
], $config->getDatabase());
|
||||
$askSecret->shouldHaveBeenCalledTimes(6);
|
||||
$choice->shouldHaveBeenCalledTimes(1);
|
||||
$ask->shouldHaveBeenCalledTimes(5);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -69,11 +64,9 @@ class DatabaseConfigCustomizerPluginTest extends TestCase
|
|||
*/
|
||||
public function overwriteIsRequestedIfValueIsAlreadySet()
|
||||
{
|
||||
/** @var MethodProphecy $ask */
|
||||
$ask = $this->questionHelper->ask(Argument::cetera())->will(function (array $args) {
|
||||
$last = array_pop($args);
|
||||
return $last instanceof ConfirmationQuestion ? false : 'MySQL';
|
||||
});
|
||||
$choice = $this->io->choice(Argument::cetera())->willReturn('MySQL');
|
||||
$confirm = $this->io->confirm(Argument::cetera())->willReturn(false);
|
||||
$ask = $this->io->ask(Argument::cetera())->willReturn('MySQL');
|
||||
$config = new CustomizableAppConfig();
|
||||
$config->setDatabase([
|
||||
'DRIVER' => 'pdo_pgsql',
|
||||
|
@ -84,7 +77,7 @@ class DatabaseConfigCustomizerPluginTest extends TestCase
|
|||
'PORT' => 'MySQL',
|
||||
]);
|
||||
|
||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertEquals([
|
||||
'DRIVER' => 'pdo_mysql',
|
||||
|
@ -94,7 +87,9 @@ class DatabaseConfigCustomizerPluginTest extends TestCase
|
|||
'HOST' => 'MySQL',
|
||||
'PORT' => 'MySQL',
|
||||
], $config->getDatabase());
|
||||
$ask->shouldHaveBeenCalledTimes(7);
|
||||
$confirm->shouldHaveBeenCalledTimes(1);
|
||||
$choice->shouldHaveBeenCalledTimes(1);
|
||||
$ask->shouldHaveBeenCalledTimes(5);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -102,8 +97,7 @@ class DatabaseConfigCustomizerPluginTest extends TestCase
|
|||
*/
|
||||
public function existingValueIsKeptIfRequested()
|
||||
{
|
||||
/** @var MethodProphecy $ask */
|
||||
$ask = $this->questionHelper->ask(Argument::cetera())->willReturn(true);
|
||||
$confirm = $this->io->confirm(Argument::cetera())->willReturn(true);
|
||||
|
||||
$config = new CustomizableAppConfig();
|
||||
$config->setDatabase([
|
||||
|
@ -115,7 +109,7 @@ class DatabaseConfigCustomizerPluginTest extends TestCase
|
|||
'PORT' => 'MySQL',
|
||||
]);
|
||||
|
||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertEquals([
|
||||
'DRIVER' => 'pdo_pgsql',
|
||||
|
@ -125,7 +119,7 @@ class DatabaseConfigCustomizerPluginTest extends TestCase
|
|||
'HOST' => 'MySQL',
|
||||
'PORT' => 'MySQL',
|
||||
], $config->getDatabase());
|
||||
$ask->shouldHaveBeenCalledTimes(1);
|
||||
$confirm->shouldHaveBeenCalledTimes(1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -133,9 +127,7 @@ class DatabaseConfigCustomizerPluginTest extends TestCase
|
|||
*/
|
||||
public function sqliteDatabaseIsImportedWhenRequested()
|
||||
{
|
||||
/** @var MethodProphecy $ask */
|
||||
$ask = $this->questionHelper->ask(Argument::cetera())->willReturn(true);
|
||||
/** @var MethodProphecy $copy */
|
||||
$confirm = $this->io->confirm(Argument::cetera())->willReturn(true);
|
||||
$copy = $this->filesystem->copy(Argument::cetera())->willReturn(null);
|
||||
|
||||
$config = new CustomizableAppConfig();
|
||||
|
@ -143,12 +135,12 @@ class DatabaseConfigCustomizerPluginTest extends TestCase
|
|||
'DRIVER' => 'pdo_sqlite',
|
||||
]);
|
||||
|
||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertEquals([
|
||||
'DRIVER' => 'pdo_sqlite',
|
||||
], $config->getDatabase());
|
||||
$ask->shouldHaveBeenCalledTimes(1);
|
||||
$confirm->shouldHaveBeenCalledTimes(1);
|
||||
$copy->shouldHaveBeenCalledTimes(1);
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\CLI\Install\Plugin\Factory;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\CLI\Install\Plugin\ApplicationConfigCustomizerPlugin;
|
||||
use Shlinkio\Shlink\CLI\Install\Plugin\Factory\DefaultConfigCustomizerPluginFactory;
|
||||
use Shlinkio\Shlink\CLI\Install\Plugin\LanguageConfigCustomizerPlugin;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
class DefaultConfigCustomizerPluginFactoryTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var DefaultConfigCustomizerPluginFactory
|
||||
*/
|
||||
protected $factory;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->factory = new DefaultConfigCustomizerPluginFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function createsProperService()
|
||||
{
|
||||
$instance = $this->factory->__invoke(new ServiceManager(['services' => [
|
||||
QuestionHelper::class => $this->prophesize(QuestionHelper::class)->reveal(),
|
||||
]]), ApplicationConfigCustomizerPlugin::class);
|
||||
$this->assertInstanceOf(ApplicationConfigCustomizerPlugin::class, $instance);
|
||||
|
||||
$instance = $this->factory->__invoke(new ServiceManager(['services' => [
|
||||
QuestionHelper::class => $this->prophesize(QuestionHelper::class)->reveal(),
|
||||
]]), LanguageConfigCustomizerPlugin::class);
|
||||
$this->assertInstanceOf(LanguageConfigCustomizerPlugin::class, $instance);
|
||||
}
|
||||
}
|
|
@ -5,30 +5,27 @@ namespace ShlinkioTest\Shlink\CLI\Install\Plugin;
|
|||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\MethodProphecy;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Install\Plugin\LanguageConfigCustomizerPlugin;
|
||||
use Shlinkio\Shlink\CLI\Install\Plugin\LanguageConfigCustomizer;
|
||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Output\NullOutput;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
class LanguageConfigCustomizerPluginTest extends TestCase
|
||||
class LanguageConfigCustomizerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var LanguageConfigCustomizerPlugin
|
||||
* @var LanguageConfigCustomizer
|
||||
*/
|
||||
protected $plugin;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
protected $questionHelper;
|
||||
protected $io;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->questionHelper = $this->prophesize(QuestionHelper::class);
|
||||
$this->plugin = new LanguageConfigCustomizerPlugin($this->questionHelper->reveal());
|
||||
$this->io = $this->prophesize(SymfonyStyle::class);
|
||||
$this->io->title(Argument::any())->willReturn(null);
|
||||
$this->plugin = new LanguageConfigCustomizer();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -36,18 +33,17 @@ class LanguageConfigCustomizerPluginTest extends TestCase
|
|||
*/
|
||||
public function configIsRequestedToTheUser()
|
||||
{
|
||||
/** @var MethodProphecy $askSecret */
|
||||
$askSecret = $this->questionHelper->ask(Argument::cetera())->willReturn('en');
|
||||
$ask = $this->io->choice(Argument::cetera())->willReturn('en');
|
||||
$config = new CustomizableAppConfig();
|
||||
|
||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertTrue($config->hasLanguage());
|
||||
$this->assertEquals([
|
||||
'DEFAULT' => 'en',
|
||||
'CLI' => 'en',
|
||||
], $config->getLanguage());
|
||||
$askSecret->shouldHaveBeenCalledTimes(2);
|
||||
$ask->shouldHaveBeenCalledTimes(2);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -55,24 +51,22 @@ class LanguageConfigCustomizerPluginTest extends TestCase
|
|||
*/
|
||||
public function overwriteIsRequestedIfValueIsAlreadySet()
|
||||
{
|
||||
/** @var MethodProphecy $ask */
|
||||
$ask = $this->questionHelper->ask(Argument::cetera())->will(function (array $args) {
|
||||
$last = array_pop($args);
|
||||
return $last instanceof ConfirmationQuestion ? false : 'es';
|
||||
});
|
||||
$choice = $this->io->choice(Argument::cetera())->willReturn('es');
|
||||
$confirm = $this->io->confirm(Argument::cetera())->willReturn(false);
|
||||
$config = new CustomizableAppConfig();
|
||||
$config->setLanguage([
|
||||
'DEFAULT' => 'en',
|
||||
'CLI' => 'en',
|
||||
]);
|
||||
|
||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertEquals([
|
||||
'DEFAULT' => 'es',
|
||||
'CLI' => 'es',
|
||||
], $config->getLanguage());
|
||||
$ask->shouldHaveBeenCalledTimes(3);
|
||||
$choice->shouldHaveBeenCalledTimes(2);
|
||||
$confirm->shouldHaveBeenCalledTimes(1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -80,8 +74,7 @@ class LanguageConfigCustomizerPluginTest extends TestCase
|
|||
*/
|
||||
public function existingValueIsKeptIfRequested()
|
||||
{
|
||||
/** @var MethodProphecy $ask */
|
||||
$ask = $this->questionHelper->ask(Argument::cetera())->willReturn(true);
|
||||
$ask = $this->io->confirm(Argument::cetera())->willReturn(true);
|
||||
|
||||
$config = new CustomizableAppConfig();
|
||||
$config->setLanguage([
|
||||
|
@ -89,7 +82,7 @@ class LanguageConfigCustomizerPluginTest extends TestCase
|
|||
'CLI' => 'es',
|
||||
]);
|
||||
|
||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertEquals([
|
||||
'DEFAULT' => 'es',
|
|
@ -5,30 +5,27 @@ namespace ShlinkioTest\Shlink\CLI\Install\Plugin;
|
|||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\MethodProphecy;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Install\Plugin\UrlShortenerConfigCustomizerPlugin;
|
||||
use Shlinkio\Shlink\CLI\Install\Plugin\UrlShortenerConfigCustomizer;
|
||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Output\NullOutput;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
class UrlShortenerConfigCustomizerPluginTest extends TestCase
|
||||
class UrlShortenerConfigCustomizerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var UrlShortenerConfigCustomizerPlugin
|
||||
* @var UrlShortenerConfigCustomizer
|
||||
*/
|
||||
private $plugin;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
private $questionHelper;
|
||||
private $io;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->questionHelper = $this->prophesize(QuestionHelper::class);
|
||||
$this->plugin = new UrlShortenerConfigCustomizerPlugin($this->questionHelper->reveal());
|
||||
$this->io = $this->prophesize(SymfonyStyle::class);
|
||||
$this->io->title(Argument::any())->willReturn(null);
|
||||
$this->plugin = new UrlShortenerConfigCustomizer();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -36,20 +33,23 @@ class UrlShortenerConfigCustomizerPluginTest extends TestCase
|
|||
*/
|
||||
public function configIsRequestedToTheUser()
|
||||
{
|
||||
/** @var MethodProphecy $askSecret */
|
||||
$askSecret = $this->questionHelper->ask(Argument::cetera())->willReturn('something');
|
||||
$choice = $this->io->choice(Argument::cetera())->willReturn('something');
|
||||
$ask = $this->io->ask(Argument::cetera())->willReturn('something');
|
||||
$confirm = $this->io->confirm(Argument::cetera())->willReturn(true);
|
||||
$config = new CustomizableAppConfig();
|
||||
|
||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertTrue($config->hasUrlShortener());
|
||||
$this->assertEquals([
|
||||
'SCHEMA' => 'something',
|
||||
'HOSTNAME' => 'something',
|
||||
'CHARS' => 'something',
|
||||
'VALIDATE_URL' => 'something',
|
||||
'VALIDATE_URL' => true,
|
||||
], $config->getUrlShortener());
|
||||
$askSecret->shouldHaveBeenCalledTimes(4);
|
||||
$ask->shouldHaveBeenCalledTimes(2);
|
||||
$choice->shouldHaveBeenCalledTimes(1);
|
||||
$confirm->shouldHaveBeenCalledTimes(1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,20 +57,18 @@ class UrlShortenerConfigCustomizerPluginTest extends TestCase
|
|||
*/
|
||||
public function overwriteIsRequestedIfValueIsAlreadySet()
|
||||
{
|
||||
/** @var MethodProphecy $ask */
|
||||
$ask = $this->questionHelper->ask(Argument::cetera())->will(function (array $args) {
|
||||
$last = array_pop($args);
|
||||
return $last instanceof ConfirmationQuestion ? false : 'foo';
|
||||
});
|
||||
$choice = $this->io->choice(Argument::cetera())->willReturn('foo');
|
||||
$ask = $this->io->ask(Argument::cetera())->willReturn('foo');
|
||||
$confirm = $this->io->confirm(Argument::cetera())->willReturn(false);
|
||||
$config = new CustomizableAppConfig();
|
||||
$config->setUrlShortener([
|
||||
'SCHEMA' => 'bar',
|
||||
'HOSTNAME' => 'bar',
|
||||
'CHARS' => 'bar',
|
||||
'VALIDATE_URL' => 'bar',
|
||||
'VALIDATE_URL' => true,
|
||||
]);
|
||||
|
||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertEquals([
|
||||
'SCHEMA' => 'foo',
|
||||
|
@ -78,7 +76,9 @@ class UrlShortenerConfigCustomizerPluginTest extends TestCase
|
|||
'CHARS' => 'foo',
|
||||
'VALIDATE_URL' => false,
|
||||
], $config->getUrlShortener());
|
||||
$ask->shouldHaveBeenCalledTimes(5);
|
||||
$ask->shouldHaveBeenCalledTimes(2);
|
||||
$choice->shouldHaveBeenCalledTimes(1);
|
||||
$confirm->shouldHaveBeenCalledTimes(2);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -86,8 +86,7 @@ class UrlShortenerConfigCustomizerPluginTest extends TestCase
|
|||
*/
|
||||
public function existingValueIsKeptIfRequested()
|
||||
{
|
||||
/** @var MethodProphecy $ask */
|
||||
$ask = $this->questionHelper->ask(Argument::cetera())->willReturn(true);
|
||||
$confirm = $this->io->confirm(Argument::cetera())->willReturn(true);
|
||||
|
||||
$config = new CustomizableAppConfig();
|
||||
$config->setUrlShortener([
|
||||
|
@ -97,7 +96,7 @@ class UrlShortenerConfigCustomizerPluginTest extends TestCase
|
|||
'VALIDATE_URL' => 'foo',
|
||||
]);
|
||||
|
||||
$this->plugin->process(new ArrayInput([]), new NullOutput(), $config);
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertEquals([
|
||||
'SCHEMA' => 'foo',
|
||||
|
@ -105,6 +104,6 @@ class UrlShortenerConfigCustomizerPluginTest extends TestCase
|
|||
'CHARS' => 'foo',
|
||||
'VALIDATE_URL' => 'foo',
|
||||
], $config->getUrlShortener());
|
||||
$ask->shouldHaveBeenCalledTimes(1);
|
||||
$confirm->shouldHaveBeenCalledTimes(1);
|
||||
}
|
||||
}
|
|
@ -3,6 +3,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shlinkio\Shlink\Common\Exception;
|
||||
|
||||
interface ExceptionInterface
|
||||
interface ExceptionInterface extends \Throwable
|
||||
{
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ namespace Shlinkio\Shlink\Common\Exception;
|
|||
|
||||
class WrongIpException extends RuntimeException
|
||||
{
|
||||
public static function fromIpAddress($ipAddress, \Exception $prev = null)
|
||||
public static function fromIpAddress($ipAddress, \Throwable $prev = null)
|
||||
{
|
||||
return new self(sprintf('Provided IP "%s" is invalid', $ipAddress), 0, $prev);
|
||||
}
|
||||
|
|
|
@ -22,10 +22,11 @@ class IpLocationResolver implements IpLocationResolverInterface
|
|||
}
|
||||
|
||||
/**
|
||||
* @param $ipAddress
|
||||
* @param string $ipAddress
|
||||
* @return array
|
||||
* @throws WrongIpException
|
||||
*/
|
||||
public function resolveIpLocation($ipAddress)
|
||||
public function resolveIpLocation(string $ipAddress): array
|
||||
{
|
||||
try {
|
||||
$response = $this->httpClient->get(sprintf(self::SERVICE_PATTERN, $ipAddress));
|
||||
|
|
|
@ -3,11 +3,14 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shlinkio\Shlink\Common\Service;
|
||||
|
||||
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
||||
|
||||
interface IpLocationResolverInterface
|
||||
{
|
||||
/**
|
||||
* @param $ipAddress
|
||||
* @param string $ipAddress
|
||||
* @return array
|
||||
* @throws WrongIpException
|
||||
*/
|
||||
public function resolveIpLocation($ipAddress);
|
||||
public function resolveIpLocation(string $ipAddress): array;
|
||||
}
|
||||
|
|
|
@ -6,11 +6,11 @@ namespace Shlinkio\Shlink\Common\Util;
|
|||
class DateRange
|
||||
{
|
||||
/**
|
||||
* @var \DateTimeInterface
|
||||
* @var \DateTimeInterface|null
|
||||
*/
|
||||
private $startDate;
|
||||
/**
|
||||
* @var \DateTimeInterface
|
||||
* @var \DateTimeInterface|null
|
||||
*/
|
||||
private $endDate;
|
||||
|
||||
|
@ -21,7 +21,7 @@ class DateRange
|
|||
}
|
||||
|
||||
/**
|
||||
* @return \DateTimeInterface
|
||||
* @return \DateTimeInterface|null
|
||||
*/
|
||||
public function getStartDate()
|
||||
{
|
||||
|
@ -29,7 +29,7 @@ class DateRange
|
|||
}
|
||||
|
||||
/**
|
||||
* @return \DateTimeInterface
|
||||
* @return \DateTimeInterface|null
|
||||
*/
|
||||
public function getEndDate()
|
||||
{
|
||||
|
@ -39,8 +39,8 @@ class DateRange
|
|||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmpty()
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return is_null($this->startDate) && is_null($this->endDate);
|
||||
return $this->startDate === null && $this->endDate === null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,11 @@ return [
|
|||
Service\Tag\TagService::class => ['em'],
|
||||
|
||||
// Middleware
|
||||
Action\RedirectAction::class => [Service\UrlShortener::class, Service\VisitsTracker::class],
|
||||
Action\RedirectAction::class => [
|
||||
Service\UrlShortener::class,
|
||||
Service\VisitsTracker::class,
|
||||
Options\AppOptions::class,
|
||||
],
|
||||
Action\QrCodeAction::class => [RouterInterface::class, Service\UrlShortener::class, 'Logger_Shlink'],
|
||||
Action\PreviewAction::class => [PreviewGenerator::class, Service\UrlShortener::class],
|
||||
Middleware\QrCodeCacheMiddleware::class => [Cache::class],
|
||||
|
|
|
@ -10,6 +10,7 @@ use Psr\Http\Message\ServerRequestInterface as Request;
|
|||
use Shlinkio\Shlink\Core\Action\Util\ErrorResponseBuilderTrait;
|
||||
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Options\AppOptions;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
||||
use Zend\Diactoros\Response\RedirectResponse;
|
||||
|
@ -26,11 +27,19 @@ class RedirectAction implements MiddlewareInterface
|
|||
* @var VisitsTrackerInterface
|
||||
*/
|
||||
private $visitTracker;
|
||||
/**
|
||||
* @var AppOptions
|
||||
*/
|
||||
private $appOptions;
|
||||
|
||||
public function __construct(UrlShortenerInterface $urlShortener, VisitsTrackerInterface $visitTracker)
|
||||
{
|
||||
public function __construct(
|
||||
UrlShortenerInterface $urlShortener,
|
||||
VisitsTrackerInterface $visitTracker,
|
||||
AppOptions $appOptions
|
||||
) {
|
||||
$this->urlShortener = $urlShortener;
|
||||
$this->visitTracker = $visitTracker;
|
||||
$this->appOptions = $appOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,18 +54,16 @@ class RedirectAction implements MiddlewareInterface
|
|||
public function process(Request $request, DelegateInterface $delegate)
|
||||
{
|
||||
$shortCode = $request->getAttribute('shortCode', '');
|
||||
$query = $request->getQueryParams();
|
||||
$disableTrackParam = $this->appOptions->getDisableTrackParam();
|
||||
|
||||
try {
|
||||
$longUrl = $this->urlShortener->shortCodeToUrl($shortCode);
|
||||
|
||||
// If provided shortCode does not belong to a valid long URL, dispatch next middleware, which will trigger
|
||||
// a not-found error
|
||||
if ($longUrl === null) {
|
||||
return $delegate->process($request);
|
||||
}
|
||||
|
||||
// Track visit to this short code
|
||||
$this->visitTracker->track($shortCode, $request);
|
||||
if ($disableTrackParam === null || ! \array_key_exists($disableTrackParam, $query)) {
|
||||
$this->visitTracker->track($shortCode, $request);
|
||||
}
|
||||
|
||||
// Return a redirect response to the long URL.
|
||||
// Use a temporary redirect to make sure browsers always hit the server for analytics purposes
|
||||
|
|
|
@ -3,9 +3,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shlinkio\Shlink\Core\Exception;
|
||||
|
||||
use Shlinkio\Shlink\Common\Exception\ExceptionInterface;
|
||||
|
||||
class EntityDoesNotExistException extends \RuntimeException implements ExceptionInterface
|
||||
class EntityDoesNotExistException extends RuntimeException
|
||||
{
|
||||
public static function createFromEntityAndConditions($entityName, array $conditions)
|
||||
{
|
||||
|
|
8
module/Core/src/Exception/ExceptionInterface.php
Normal file
8
module/Core/src/Exception/ExceptionInterface.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Exception;
|
||||
|
||||
interface ExceptionInterface extends \Throwable
|
||||
{
|
||||
}
|
8
module/Core/src/Exception/InvalidArgumentException.php
Normal file
8
module/Core/src/Exception/InvalidArgumentException.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Exception;
|
||||
|
||||
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
|
||||
{
|
||||
}
|
|
@ -3,8 +3,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shlinkio\Shlink\Core\Exception;
|
||||
|
||||
use Shlinkio\Shlink\Common\Exception\RuntimeException;
|
||||
|
||||
class InvalidShortCodeException extends RuntimeException
|
||||
{
|
||||
public static function fromCharset($shortCode, $charSet, \Exception $previous = null)
|
||||
|
|
|
@ -3,13 +3,11 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shlinkio\Shlink\Core\Exception;
|
||||
|
||||
use Shlinkio\Shlink\Common\Exception\RuntimeException;
|
||||
|
||||
class InvalidUrlException extends RuntimeException
|
||||
{
|
||||
public static function fromUrl($url, \Exception $previous = null)
|
||||
public static function fromUrl($url, \Throwable $previous = null)
|
||||
{
|
||||
$code = isset($previous) ? $previous->getCode() : -1;
|
||||
return new static(sprintf('Provided URL "%s" is not an exisitng and valid URL', $url), $code, $previous);
|
||||
return new static(sprintf('Provided URL "%s" is not an existing and valid URL', $url), $code, $previous);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shlinkio\Shlink\Core\Exception;
|
||||
|
||||
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
||||
|
||||
class NonUniqueSlugException extends InvalidArgumentException
|
||||
{
|
||||
public static function fromSlug(string $slug): self
|
||||
|
|
8
module/Core/src/Exception/RuntimeException.php
Normal file
8
module/Core/src/Exception/RuntimeException.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Exception;
|
||||
|
||||
class RuntimeException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
76
module/Core/src/Exception/ValidationException.php
Normal file
76
module/Core/src/Exception/ValidationException.php
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Exception;
|
||||
|
||||
use Zend\InputFilter\InputFilterInterface;
|
||||
|
||||
class ValidationException extends RuntimeException
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $invalidElements;
|
||||
|
||||
public function __construct(
|
||||
string $message = '',
|
||||
array $invalidElements = [],
|
||||
int $code = 0,
|
||||
\Throwable $previous = null
|
||||
) {
|
||||
$this->invalidElements = $invalidElements;
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputFilterInterface $inputFilter
|
||||
* @param \Throwable|null $prev
|
||||
* @return ValidationException
|
||||
*/
|
||||
public static function fromInputFilter(InputFilterInterface $inputFilter, \Throwable $prev = null): self
|
||||
{
|
||||
return static::fromArray($inputFilter->getMessages(), $prev);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $invalidData
|
||||
* @param \Throwable|null $prev
|
||||
* @return ValidationException
|
||||
*/
|
||||
public static function fromArray(array $invalidData, \Throwable $prev = null): self
|
||||
{
|
||||
return new self(
|
||||
\sprintf(
|
||||
'Provided data is not valid. These are the messages:%s%s%s',
|
||||
PHP_EOL,
|
||||
self::formMessagesToString($invalidData),
|
||||
PHP_EOL
|
||||
),
|
||||
$invalidData,
|
||||
-1,
|
||||
$prev
|
||||
);
|
||||
}
|
||||
|
||||
private static function formMessagesToString(array $messages = [])
|
||||
{
|
||||
$text = '';
|
||||
foreach ($messages as $name => $messageSet) {
|
||||
$text .= \sprintf(
|
||||
"\n\t'%s' => %s",
|
||||
$name,
|
||||
\is_array($messageSet) ? \print_r($messageSet, true) : $messageSet
|
||||
);
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getInvalidElements(): array
|
||||
{
|
||||
return $this->invalidElements;
|
||||
}
|
||||
}
|
141
module/Core/src/Model/ShortUrlMeta.php
Normal file
141
module/Core/src/Model/ShortUrlMeta.php
Normal file
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Model;
|
||||
|
||||
use Shlinkio\Shlink\Core\Exception\ValidationException;
|
||||
use Shlinkio\Shlink\Core\Validation\ShortUrlMetaInputFilter;
|
||||
|
||||
final class ShortUrlMeta
|
||||
{
|
||||
/**
|
||||
* @var \DateTime|null
|
||||
*/
|
||||
private $validSince;
|
||||
/**
|
||||
* @var \DateTime|null
|
||||
*/
|
||||
private $validUntil;
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $customSlug;
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $maxVisits;
|
||||
|
||||
// Force named constructors
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @return ShortUrlMeta
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public static function createFromRawData(array $data): self
|
||||
{
|
||||
$instance = new self();
|
||||
$instance->validate($data);
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|\DateTimeInterface|null $validSince
|
||||
* @param string|\DateTimeInterface|null $validUntil
|
||||
* @param string|null $customSlug
|
||||
* @param int|null $maxVisits
|
||||
* @return ShortUrlMeta
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public static function createFromParams(
|
||||
$validSince = null,
|
||||
$validUntil = null,
|
||||
$customSlug = null,
|
||||
$maxVisits = null
|
||||
): self {
|
||||
// We do not type hint the arguments because that will be done by the validation process
|
||||
$instance = new self();
|
||||
$instance->validate([
|
||||
ShortUrlMetaInputFilter::VALID_SINCE => $validSince,
|
||||
ShortUrlMetaInputFilter::VALID_UNTIL => $validUntil,
|
||||
ShortUrlMetaInputFilter::CUSTOM_SLUG => $customSlug,
|
||||
ShortUrlMetaInputFilter::MAX_VISITS => $maxVisits,
|
||||
]);
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private function validate(array $data)
|
||||
{
|
||||
$inputFilter = new ShortUrlMetaInputFilter($data);
|
||||
if (! $inputFilter->isValid()) {
|
||||
throw ValidationException::fromInputFilter($inputFilter);
|
||||
}
|
||||
|
||||
$this->validSince = $inputFilter->getValue(ShortUrlMetaInputFilter::VALID_SINCE);
|
||||
$this->validSince = $this->validSince !== null ? new \DateTime($this->validSince) : null;
|
||||
$this->validUntil = $inputFilter->getValue(ShortUrlMetaInputFilter::VALID_UNTIL);
|
||||
$this->validUntil = $this->validUntil !== null ? new \DateTime($this->validUntil) : null;
|
||||
$this->customSlug = $inputFilter->getValue(ShortUrlMetaInputFilter::CUSTOM_SLUG);
|
||||
$this->maxVisits = $inputFilter->getValue(ShortUrlMetaInputFilter::MAX_VISITS);
|
||||
$this->maxVisits = $this->maxVisits !== null ? (int) $this->maxVisits : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTime|null
|
||||
*/
|
||||
public function getValidSince()
|
||||
{
|
||||
return $this->validSince;
|
||||
}
|
||||
|
||||
public function hasValidSince(): bool
|
||||
{
|
||||
return $this->validSince !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTime|null
|
||||
*/
|
||||
public function getValidUntil()
|
||||
{
|
||||
return $this->validUntil;
|
||||
}
|
||||
|
||||
public function hasValidUntil(): bool
|
||||
{
|
||||
return $this->validUntil !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|string
|
||||
*/
|
||||
public function getCustomSlug()
|
||||
{
|
||||
return $this->customSlug;
|
||||
}
|
||||
|
||||
public function hasCustomSlug(): bool
|
||||
{
|
||||
return $this->customSlug !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getMaxVisits()
|
||||
{
|
||||
return $this->maxVisits;
|
||||
}
|
||||
|
||||
public function hasMaxVisits(): bool
|
||||
{
|
||||
return $this->maxVisits !== null;
|
||||
}
|
||||
}
|
|
@ -22,6 +22,10 @@ class AppOptions extends AbstractOptions
|
|||
* @var string
|
||||
*/
|
||||
protected $secretKey = '';
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $disableTrackParam;
|
||||
|
||||
/**
|
||||
* AppOptions constructor.
|
||||
|
@ -86,6 +90,24 @@ class AppOptions extends AbstractOptions
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDisableTrackParam()
|
||||
{
|
||||
return $this->disableTrackParam;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $disableTrackParam
|
||||
* @return $this|self
|
||||
*/
|
||||
protected function setDisableTrackParam($disableTrackParam): self
|
||||
{
|
||||
$this->disableTrackParam = $disableTrackParam;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
|
|
|
@ -3,10 +3,11 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shlinkio\Shlink\Core\Service;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM;
|
||||
use Shlinkio\Shlink\Common\Paginator\Adapter\PaginableRepositoryAdapter;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
|
||||
use Shlinkio\Shlink\Core\Util\TagManagerTrait;
|
||||
use Zend\Paginator\Paginator;
|
||||
|
@ -16,11 +17,11 @@ class ShortUrlService implements ShortUrlServiceInterface
|
|||
use TagManagerTrait;
|
||||
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
* @var ORM\EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
public function __construct(ORM\EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
}
|
||||
|
@ -49,9 +50,48 @@ class ShortUrlService implements ShortUrlServiceInterface
|
|||
* @return ShortUrl
|
||||
* @throws InvalidShortCodeException
|
||||
*/
|
||||
public function setTagsByShortCode($shortCode, array $tags = [])
|
||||
public function setTagsByShortCode(string $shortCode, array $tags = []): ShortUrl
|
||||
{
|
||||
/** @var ShortUrl $shortUrl */
|
||||
$shortUrl = $this->findByShortCode($shortCode);
|
||||
$shortUrl->setTags($this->tagNamesToEntities($this->em, $tags));
|
||||
$this->em->flush();
|
||||
|
||||
return $shortUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $shortCode
|
||||
* @param ShortUrlMeta $shortCodeMeta
|
||||
* @return ShortUrl
|
||||
* @throws InvalidShortCodeException
|
||||
*/
|
||||
public function updateMetadataByShortCode(string $shortCode, ShortUrlMeta $shortCodeMeta): ShortUrl
|
||||
{
|
||||
$shortUrl = $this->findByShortCode($shortCode);
|
||||
if ($shortCodeMeta->hasValidSince()) {
|
||||
$shortUrl->setValidSince($shortCodeMeta->getValidSince());
|
||||
}
|
||||
if ($shortCodeMeta->hasValidUntil()) {
|
||||
$shortUrl->setValidUntil($shortCodeMeta->getValidUntil());
|
||||
}
|
||||
if ($shortCodeMeta->hasMaxVisits()) {
|
||||
$shortUrl->setMaxVisits($shortCodeMeta->getMaxVisits());
|
||||
}
|
||||
|
||||
/** @var ORM\EntityManager $em */
|
||||
$em = $this->em;
|
||||
$em->flush($shortUrl);
|
||||
return $shortUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $shortCode
|
||||
* @return ShortUrl
|
||||
* @throws InvalidShortCodeException
|
||||
*/
|
||||
private function findByShortCode(string $shortCode): ShortUrl
|
||||
{
|
||||
/** @var ShortUrl|null $shortUrl */
|
||||
$shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy([
|
||||
'shortCode' => $shortCode,
|
||||
]);
|
||||
|
@ -59,9 +99,6 @@ class ShortUrlService implements ShortUrlServiceInterface
|
|||
throw InvalidShortCodeException::fromNotFoundShortCode($shortCode);
|
||||
}
|
||||
|
||||
$shortUrl->setTags($this->tagNamesToEntities($this->em, $tags));
|
||||
$this->em->flush();
|
||||
|
||||
return $shortUrl;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace Shlinkio\Shlink\Core\Service;
|
|||
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
||||
use Zend\Paginator\Paginator;
|
||||
|
||||
interface ShortUrlServiceInterface
|
||||
|
@ -24,5 +25,13 @@ interface ShortUrlServiceInterface
|
|||
* @return ShortUrl
|
||||
* @throws InvalidShortCodeException
|
||||
*/
|
||||
public function setTagsByShortCode($shortCode, array $tags = []);
|
||||
public function setTagsByShortCode(string $shortCode, array $tags = []): ShortUrl;
|
||||
|
||||
/**
|
||||
* @param string $shortCode
|
||||
* @param ShortUrlMeta $shortCodeMeta
|
||||
* @return ShortUrl
|
||||
* @throws InvalidShortCodeException
|
||||
*/
|
||||
public function updateMetadataByShortCode(string $shortCode, ShortUrlMeta $shortCodeMeta): ShortUrl;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ declare(strict_types=1);
|
|||
namespace Shlinkio\Shlink\Core\Service\Tag;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM;
|
||||
use Shlinkio\Shlink\Core\Entity\Tag;
|
||||
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||
use Shlinkio\Shlink\Core\Repository\TagRepository;
|
||||
|
@ -15,11 +15,11 @@ class TagService implements TagServiceInterface
|
|||
use TagManagerTrait;
|
||||
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
* @var ORM\EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
public function __construct(ORM\EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
}
|
||||
|
@ -63,6 +63,7 @@ class TagService implements TagServiceInterface
|
|||
* @param string $newName
|
||||
* @return Tag
|
||||
* @throws EntityDoesNotExistException
|
||||
* @throws ORM\OptimisticLockException
|
||||
*/
|
||||
public function renameTag($oldName, $newName)
|
||||
{
|
||||
|
@ -74,7 +75,10 @@ class TagService implements TagServiceInterface
|
|||
}
|
||||
|
||||
$tag->setName($newName);
|
||||
$this->em->flush($tag);
|
||||
|
||||
/** @var ORM\EntityManager $em */
|
||||
$em = $this->em;
|
||||
$em->flush($tag);
|
||||
|
||||
return $tag;
|
||||
}
|
||||
|
|
|
@ -7,16 +7,15 @@ use Cocur\Slugify\Slugify;
|
|||
use Cocur\Slugify\SlugifyInterface;
|
||||
use Doctrine\Common\Cache\Cache;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\ORMException;
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use Shlinkio\Shlink\Common\Exception\RuntimeException;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
||||
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
|
||||
use Shlinkio\Shlink\Core\Exception\RuntimeException;
|
||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
|
||||
use Shlinkio\Shlink\Core\Util\TagManagerTrait;
|
||||
|
||||
|
@ -90,6 +89,7 @@ class UrlShortener implements UrlShortenerInterface
|
|||
int $maxVisits = null
|
||||
): string {
|
||||
// If the url already exists in the database, just return its short code
|
||||
/** @var ShortUrl|null $shortUrl */
|
||||
$shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy([
|
||||
'originalUrl' => $url,
|
||||
]);
|
||||
|
@ -118,14 +118,14 @@ class UrlShortener implements UrlShortenerInterface
|
|||
$this->em->flush();
|
||||
|
||||
// Generate the short code and persist it
|
||||
$shortCode = $customSlug ?? $this->convertAutoincrementIdToShortCode($shortUrl->getId());
|
||||
$shortCode = $customSlug ?? $this->convertAutoincrementIdToShortCode((float) $shortUrl->getId());
|
||||
$shortUrl->setShortCode($shortCode)
|
||||
->setTags($this->tagNamesToEntities($this->em, $tags));
|
||||
$this->em->flush();
|
||||
|
||||
$this->em->commit();
|
||||
return $shortCode;
|
||||
} catch (ORMException $e) {
|
||||
} catch (\Throwable $e) {
|
||||
if ($this->em->getConnection()->isTransactionActive()) {
|
||||
$this->em->rollback();
|
||||
$this->em->close();
|
||||
|
@ -155,13 +155,13 @@ class UrlShortener implements UrlShortenerInterface
|
|||
/**
|
||||
* Generates the unique shortcode for an autoincrement ID
|
||||
*
|
||||
* @param int $id
|
||||
* @param float $id
|
||||
* @return string
|
||||
*/
|
||||
private function convertAutoincrementIdToShortCode($id): string
|
||||
private function convertAutoincrementIdToShortCode(float $id): string
|
||||
{
|
||||
$id = ((int) $id) + 200000; // Increment the Id so that the generated shortcode is not too short
|
||||
$length = strlen($this->chars);
|
||||
$id += 200000; // Increment the Id so that the generated shortcode is not too short
|
||||
$length = \strlen($this->chars);
|
||||
$code = '';
|
||||
|
||||
while ($id > 0) {
|
||||
|
|
|
@ -4,11 +4,11 @@ declare(strict_types=1);
|
|||
namespace Shlinkio\Shlink\Core\Service;
|
||||
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use Shlinkio\Shlink\Common\Exception\RuntimeException;
|
||||
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
||||
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
|
||||
use Shlinkio\Shlink\Core\Exception\RuntimeException;
|
||||
|
||||
interface UrlShortenerInterface
|
||||
{
|
||||
|
|
|
@ -3,23 +3,22 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shlinkio\Shlink\Core\Service;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidArgumentException;
|
||||
use Shlinkio\Shlink\Core\Repository\VisitRepository;
|
||||
|
||||
class VisitsTracker implements VisitsTrackerInterface
|
||||
{
|
||||
/**
|
||||
* @var EntityManagerInterface|EntityManager
|
||||
* @var ORM\EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
public function __construct(ORM\EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
}
|
||||
|
@ -29,6 +28,8 @@ class VisitsTracker implements VisitsTrackerInterface
|
|||
*
|
||||
* @param string $shortCode
|
||||
* @param ServerRequestInterface $request
|
||||
* @throws ORM\ORMInvalidArgumentException
|
||||
* @throws ORM\OptimisticLockException
|
||||
*/
|
||||
public function track($shortCode, ServerRequestInterface $request)
|
||||
{
|
||||
|
@ -43,8 +44,10 @@ class VisitsTracker implements VisitsTrackerInterface
|
|||
->setReferer($request->getHeaderLine('Referer'))
|
||||
->setRemoteAddr($this->findOutRemoteAddr($request));
|
||||
|
||||
$this->em->persist($visit);
|
||||
$this->em->flush($visit);
|
||||
/** @var ORM\EntityManager $em */
|
||||
$em = $this->em;
|
||||
$em->persist($visit);
|
||||
$em->flush($visit);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -66,14 +69,14 @@ class VisitsTracker implements VisitsTrackerInterface
|
|||
/**
|
||||
* Returns the visits on certain short code
|
||||
*
|
||||
* @param $shortCode
|
||||
* @param string $shortCode
|
||||
* @param DateRange $dateRange
|
||||
* @return Visit[]
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function info($shortCode, DateRange $dateRange = null): array
|
||||
public function info(string $shortCode, DateRange $dateRange = null): array
|
||||
{
|
||||
/** @var ShortUrl $shortUrl */
|
||||
/** @var ShortUrl|null $shortUrl */
|
||||
$shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy([
|
||||
'shortCode' => $shortCode,
|
||||
]);
|
||||
|
|
|
@ -4,9 +4,9 @@ declare(strict_types=1);
|
|||
namespace Shlinkio\Shlink\Core\Service;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidArgumentException;
|
||||
|
||||
interface VisitsTrackerInterface
|
||||
{
|
||||
|
@ -21,10 +21,10 @@ interface VisitsTrackerInterface
|
|||
/**
|
||||
* Returns the visits on certain short code
|
||||
*
|
||||
* @param $shortCode
|
||||
* @param string $shortCode
|
||||
* @param DateRange $dateRange
|
||||
* @return Visit[]
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function info($shortCode, DateRange $dateRange = null): array;
|
||||
public function info(string $shortCode, DateRange $dateRange = null): array;
|
||||
}
|
||||
|
|
20
module/Core/src/Validation/InputFactoryTrait.php
Normal file
20
module/Core/src/Validation/InputFactoryTrait.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Validation;
|
||||
|
||||
use Zend\Filter\StringTrim;
|
||||
use Zend\Filter\StripTags;
|
||||
use Zend\InputFilter\Input;
|
||||
|
||||
trait InputFactoryTrait
|
||||
{
|
||||
public function createInput($name, $required = true): Input
|
||||
{
|
||||
$input = new Input($name);
|
||||
$input->setRequired($required)
|
||||
->getFilterChain()->attach(new StripTags())
|
||||
->attach(new StringTrim());
|
||||
return $input;
|
||||
}
|
||||
}
|
45
module/Core/src/Validation/ShortUrlMetaInputFilter.php
Normal file
45
module/Core/src/Validation/ShortUrlMetaInputFilter.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Validation;
|
||||
|
||||
use Zend\I18n\Validator\IsInt;
|
||||
use Zend\InputFilter\InputFilter;
|
||||
use Zend\Validator\Date;
|
||||
use Zend\Validator\GreaterThan;
|
||||
|
||||
class ShortUrlMetaInputFilter extends InputFilter
|
||||
{
|
||||
use InputFactoryTrait;
|
||||
|
||||
const VALID_SINCE = 'validSince';
|
||||
const VALID_UNTIL = 'validUntil';
|
||||
const CUSTOM_SLUG = 'customSlug';
|
||||
const MAX_VISITS = 'maxVisits';
|
||||
|
||||
public function __construct(array $data = null)
|
||||
{
|
||||
$this->initialize();
|
||||
if ($data !== null) {
|
||||
$this->setData($data);
|
||||
}
|
||||
}
|
||||
|
||||
private function initialize()
|
||||
{
|
||||
$validSince = $this->createInput(self::VALID_SINCE, false);
|
||||
$validSince->getValidatorChain()->attach(new Date(['format' => \DateTime::ATOM]));
|
||||
$this->add($validSince);
|
||||
|
||||
$validUntil = $this->createInput(self::VALID_UNTIL, false);
|
||||
$validUntil->getValidatorChain()->attach(new Date(['format' => \DateTime::ATOM]));
|
||||
$this->add($validUntil);
|
||||
|
||||
$this->add($this->createInput(self::CUSTOM_SLUG, false));
|
||||
|
||||
$maxVisits = $this->createInput(self::MAX_VISITS, false);
|
||||
$maxVisits->getValidatorChain()->attach(new IsInt())
|
||||
->attach(new GreaterThan(['min' => 1, 'inclusive' => true]));
|
||||
$this->add($maxVisits);
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ use Prophecy\Prophecy\MethodProphecy;
|
|||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Core\Action\RedirectAction;
|
||||
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||
use Shlinkio\Shlink\Core\Options\AppOptions;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTracker;
|
||||
use ShlinkioTest\Shlink\Common\Util\TestUtils;
|
||||
|
@ -26,13 +27,21 @@ class RedirectActionTest extends TestCase
|
|||
* @var ObjectProphecy
|
||||
*/
|
||||
protected $urlShortener;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
protected $visitTracker;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->urlShortener = $this->prophesize(UrlShortener::class);
|
||||
$visitTracker = $this->prophesize(VisitsTracker::class);
|
||||
$visitTracker->track(Argument::any());
|
||||
$this->action = new RedirectAction($this->urlShortener->reveal(), $visitTracker->reveal());
|
||||
$this->visitTracker = $this->prophesize(VisitsTracker::class);
|
||||
|
||||
$this->action = new RedirectAction(
|
||||
$this->urlShortener->reveal(),
|
||||
$this->visitTracker->reveal(),
|
||||
new AppOptions(['disableTrackParam' => 'foobar'])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -44,6 +53,8 @@ class RedirectActionTest extends TestCase
|
|||
$expectedUrl = 'http://domain.com/foo/bar';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willReturn($expectedUrl)
|
||||
->shouldBeCalledTimes(1);
|
||||
$this->visitTracker->track(Argument::cetera())->willReturn(null)
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$request = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode);
|
||||
$response = $this->action->process($request, TestUtils::createDelegateMock()->reveal());
|
||||
|
@ -62,6 +73,9 @@ class RedirectActionTest extends TestCase
|
|||
$shortCode = 'abc123';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willThrow(EntityDoesNotExistException::class)
|
||||
->shouldBeCalledTimes(1);
|
||||
$this->visitTracker->track(Argument::cetera())->willReturn(null)
|
||||
->shouldNotBeCalled();
|
||||
|
||||
$delegate = $this->prophesize(DelegateInterface::class);
|
||||
/** @var MethodProphecy $process */
|
||||
$process = $delegate->process(Argument::any())->willReturn(new Response());
|
||||
|
@ -71,4 +85,26 @@ class RedirectActionTest extends TestCase
|
|||
|
||||
$process->shouldHaveBeenCalledTimes(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function visitIsNotTrackedIfDisableParamIsProvided()
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$expectedUrl = 'http://domain.com/foo/bar';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willReturn($expectedUrl)
|
||||
->shouldBeCalledTimes(1);
|
||||
$this->visitTracker->track(Argument::cetera())->willReturn(null)
|
||||
->shouldNotBeCalled();
|
||||
|
||||
$request = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode)
|
||||
->withQueryParams(['foobar' => true]);
|
||||
$response = $this->action->process($request, TestUtils::createDelegateMock()->reveal());
|
||||
|
||||
$this->assertInstanceOf(Response\RedirectResponse::class, $response);
|
||||
$this->assertEquals(302, $response->getStatusCode());
|
||||
$this->assertTrue($response->hasHeader('Location'));
|
||||
$this->assertEquals($expectedUrl, $response->getHeaderLine('Location'));
|
||||
}
|
||||
}
|
||||
|
|
59
module/Core/test/Model/ShortUrlMetaTest.php
Normal file
59
module/Core/test/Model/ShortUrlMetaTest.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Core\Model;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Core\Exception\ValidationException;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
||||
use Shlinkio\Shlink\Core\Validation\ShortUrlMetaInputFilter;
|
||||
|
||||
class ShortUrlMetaTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @param array $data
|
||||
* @test
|
||||
* @dataProvider provideInvalidData
|
||||
*/
|
||||
public function exceptionIsThrownIfProvidedDataIsInvalid(array $data)
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
ShortUrlMeta::createFromRawData($data);
|
||||
}
|
||||
|
||||
public function provideInvalidData(): array
|
||||
{
|
||||
return [
|
||||
[[
|
||||
ShortUrlMetaInputFilter::VALID_SINCE => '',
|
||||
ShortUrlMetaInputFilter::VALID_UNTIL => '',
|
||||
ShortUrlMetaInputFilter::CUSTOM_SLUG => 'foobar',
|
||||
ShortUrlMetaInputFilter::MAX_VISITS => 'invalid',
|
||||
]],
|
||||
[[
|
||||
ShortUrlMetaInputFilter::VALID_SINCE => '2017',
|
||||
ShortUrlMetaInputFilter::MAX_VISITS => 5,
|
||||
]],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function properlyCreatedInstanceReturnsValues()
|
||||
{
|
||||
$meta = ShortUrlMeta::createFromParams((new \DateTime('2015-01-01'))->format(\DateTime::ATOM), null, 'foobar');
|
||||
|
||||
$this->assertTrue($meta->hasValidSince());
|
||||
$this->assertEquals(new \DateTime('2015-01-01'), $meta->getValidSince());
|
||||
|
||||
$this->assertFalse($meta->hasValidUntil());
|
||||
$this->assertNull($meta->getValidUntil());
|
||||
|
||||
$this->assertTrue($meta->hasCustomSlug());
|
||||
$this->assertEquals('foobar', $meta->getCustomSlug());
|
||||
|
||||
$this->assertFalse($meta->hasMaxVisits());
|
||||
$this->assertNull($meta->getMaxVisits());
|
||||
}
|
||||
}
|
|
@ -10,6 +10,8 @@ use Prophecy\Argument;
|
|||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Entity\Tag;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrlService;
|
||||
|
||||
|
@ -55,7 +57,6 @@ class ShortUrlServiceTest extends TestCase
|
|||
|
||||
/**
|
||||
* @test
|
||||
* @expectedException \Shlinkio\Shlink\Core\Exception\InvalidShortCodeException
|
||||
*/
|
||||
public function exceptionIsThrownWhenSettingTagsOnInvalidShortcode()
|
||||
{
|
||||
|
@ -65,6 +66,7 @@ class ShortUrlServiceTest extends TestCase
|
|||
->shouldBeCalledTimes(1);
|
||||
$this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
|
||||
|
||||
$this->expectException(InvalidShortCodeException::class);
|
||||
$this->service->setTagsByShortCode($shortCode);
|
||||
}
|
||||
|
||||
|
@ -88,4 +90,32 @@ class ShortUrlServiceTest extends TestCase
|
|||
|
||||
$this->service->setTagsByShortCode($shortCode, ['foo', 'bar']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function updateMetadataByShortCodeUpdatesProvidedData()
|
||||
{
|
||||
$shortUrl = new ShortUrl();
|
||||
|
||||
$repo = $this->prophesize(ShortUrlRepository::class);
|
||||
$findShortUrl = $repo->findOneBy(['shortCode' => 'abc123'])->willReturn($shortUrl);
|
||||
$getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
|
||||
$flush = $this->em->flush($shortUrl)->willReturn(null);
|
||||
|
||||
$result = $this->service->updateMetadataByShortCode('abc123', ShortUrlMeta::createFromParams(
|
||||
(new \DateTime('2017-01-01 00:00:00'))->format(\DateTime::ATOM),
|
||||
(new \DateTime('2017-01-05 00:00:00'))->format(\DateTime::ATOM),
|
||||
null,
|
||||
5
|
||||
));
|
||||
|
||||
$this->assertSame($shortUrl, $result);
|
||||
$this->assertEquals(new \DateTime('2017-01-01 00:00:00'), $shortUrl->getValidSince());
|
||||
$this->assertEquals(new \DateTime('2017-01-05 00:00:00'), $shortUrl->getValidUntil());
|
||||
$this->assertEquals(5, $shortUrl->getMaxVisits());
|
||||
$findShortUrl->shouldHaveBeenCalled();
|
||||
$getRepo->shouldHaveBeenCalled();
|
||||
$flush->shouldHaveBeenCalled();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ class UrlShortenerTest extends TestCase
|
|||
|
||||
/**
|
||||
* @test
|
||||
* @expectedException \Shlinkio\Shlink\Common\Exception\RuntimeException
|
||||
* @expectedException \Shlinkio\Shlink\Core\Exception\RuntimeException
|
||||
*/
|
||||
public function exceptionIsThrownWhenOrmThrowsException()
|
||||
{
|
||||
|
|
|
@ -21,6 +21,7 @@ return [
|
|||
|
||||
Action\AuthenticateAction::class => ConfigAbstractFactory::class,
|
||||
Action\CreateShortcodeAction::class => ConfigAbstractFactory::class,
|
||||
Action\EditShortCodeAction::class => ConfigAbstractFactory::class,
|
||||
Action\ResolveUrlAction::class => ConfigAbstractFactory::class,
|
||||
Action\GetVisitsAction::class => ConfigAbstractFactory::class,
|
||||
Action\ListShortcodesAction::class => ConfigAbstractFactory::class,
|
||||
|
@ -48,6 +49,7 @@ return [
|
|||
'config.url_shortener.domain',
|
||||
'Logger_Shlink',
|
||||
],
|
||||
Action\EditShortCodeAction::class => [Service\ShortUrlService::class, 'translator', 'Logger_Shlink',],
|
||||
Action\ResolveUrlAction::class => [Service\UrlShortener::class, 'translator'],
|
||||
Action\GetVisitsAction::class => [Service\VisitsTracker::class, 'translator', 'Logger_Shlink'],
|
||||
Action\ListShortcodesAction::class => [Service\ShortUrlService::class, 'translator', 'Logger_Shlink'],
|
||||
|
|
|
@ -9,7 +9,7 @@ return [
|
|||
'routes' => [
|
||||
[
|
||||
'name' => Action\AuthenticateAction::class,
|
||||
'path' => '/rest/v{version:1}/authenticate',
|
||||
'path' => '/authenticate',
|
||||
'middleware' => Action\AuthenticateAction::class,
|
||||
'allowed_methods' => [RequestMethod::METHOD_POST],
|
||||
],
|
||||
|
@ -17,25 +17,31 @@ return [
|
|||
// Short codes
|
||||
[
|
||||
'name' => Action\CreateShortcodeAction::class,
|
||||
'path' => '/rest/v{version:1}/short-codes',
|
||||
'path' => '/short-codes',
|
||||
'middleware' => Action\CreateShortcodeAction::class,
|
||||
'allowed_methods' => [RequestMethod::METHOD_POST],
|
||||
],
|
||||
[
|
||||
'name' => Action\EditShortCodeAction::class,
|
||||
'path' => '/short-codes/{shortCode}',
|
||||
'middleware' => Action\EditShortCodeAction::class,
|
||||
'allowed_methods' => [RequestMethod::METHOD_PUT],
|
||||
],
|
||||
[
|
||||
'name' => Action\ResolveUrlAction::class,
|
||||
'path' => '/rest/v{version:1}/short-codes/{shortCode}',
|
||||
'path' => '/short-codes/{shortCode}',
|
||||
'middleware' => Action\ResolveUrlAction::class,
|
||||
'allowed_methods' => [RequestMethod::METHOD_GET],
|
||||
],
|
||||
[
|
||||
'name' => Action\ListShortcodesAction::class,
|
||||
'path' => '/rest/v{version:1}/short-codes',
|
||||
'path' => '/short-codes',
|
||||
'middleware' => Action\ListShortcodesAction::class,
|
||||
'allowed_methods' => [RequestMethod::METHOD_GET],
|
||||
],
|
||||
[
|
||||
'name' => Action\EditShortcodeTagsAction::class,
|
||||
'path' => '/rest/v{version:1}/short-codes/{shortCode}/tags',
|
||||
'path' => '/short-codes/{shortCode}/tags',
|
||||
'middleware' => Action\EditShortcodeTagsAction::class,
|
||||
'allowed_methods' => [RequestMethod::METHOD_PUT],
|
||||
],
|
||||
|
@ -43,7 +49,7 @@ return [
|
|||
// Visits
|
||||
[
|
||||
'name' => Action\GetVisitsAction::class,
|
||||
'path' => '/rest/v{version:1}/short-codes/{shortCode}/visits',
|
||||
'path' => '/short-codes/{shortCode}/visits',
|
||||
'middleware' => Action\GetVisitsAction::class,
|
||||
'allowed_methods' => [RequestMethod::METHOD_GET],
|
||||
],
|
||||
|
@ -51,25 +57,25 @@ return [
|
|||
// Tags
|
||||
[
|
||||
'name' => Action\Tag\ListTagsAction::class,
|
||||
'path' => '/rest/v{version:1}/tags',
|
||||
'path' => '/tags',
|
||||
'middleware' => Action\Tag\ListTagsAction::class,
|
||||
'allowed_methods' => [RequestMethod::METHOD_GET],
|
||||
],
|
||||
[
|
||||
'name' => Action\Tag\DeleteTagsAction::class,
|
||||
'path' => '/rest/v{version:1}/tags',
|
||||
'path' => '/tags',
|
||||
'middleware' => Action\Tag\DeleteTagsAction::class,
|
||||
'allowed_methods' => [RequestMethod::METHOD_DELETE],
|
||||
],
|
||||
[
|
||||
'name' => Action\Tag\CreateTagsAction::class,
|
||||
'path' => '/rest/v{version:1}/tags',
|
||||
'path' => '/tags',
|
||||
'middleware' => Action\Tag\CreateTagsAction::class,
|
||||
'allowed_methods' => [RequestMethod::METHOD_POST],
|
||||
],
|
||||
[
|
||||
'name' => Action\Tag\UpdateTagAction::class,
|
||||
'path' => '/rest/v{version:1}/tags',
|
||||
'path' => '/tags',
|
||||
'middleware' => Action\Tag\UpdateTagAction::class,
|
||||
'allowed_methods' => [RequestMethod::METHOD_PUT],
|
||||
],
|
||||
|
|
Binary file not shown.
|
@ -1,15 +1,15 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Shlink 1.0\n"
|
||||
"POT-Creation-Date: 2017-10-21 20:20+0200\n"
|
||||
"PO-Revision-Date: 2017-10-21 20:20+0200\n"
|
||||
"POT-Creation-Date: 2018-01-21 09:40+0100\n"
|
||||
"PO-Revision-Date: 2018-01-21 09:40+0100\n"
|
||||
"Last-Translator: Alejandro Celaya <alejandro@alejandrocelaya.com>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: es_ES\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.0.1\n"
|
||||
"X-Generator: Poedit 2.0.4\n"
|
||||
"X-Poedit-Basepath: ..\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
@ -39,13 +39,16 @@ msgstr "El slug proporcionado \"%s\" ya está en uso. Prueba con uno diferente."
|
|||
msgid "Unexpected error occurred"
|
||||
msgstr "Ocurrió un error inesperado"
|
||||
|
||||
msgid "A list of tags was not provided"
|
||||
msgstr "No se ha proporcionado una lista de etiquetas"
|
||||
|
||||
#, php-format
|
||||
msgid "No URL found for short code \"%s\""
|
||||
msgstr "No se ha encontrado ninguna URL para el código corto \"%s\""
|
||||
|
||||
msgid "Provided data is invalid."
|
||||
msgstr "Los datos proporcionados son inválidos."
|
||||
|
||||
msgid "A list of tags was not provided"
|
||||
msgstr "No se ha proporcionado una lista de etiquetas"
|
||||
|
||||
#, php-format
|
||||
msgid "Provided short code %s does not exist"
|
||||
msgstr "El código corto \"%s\" proporcionado no existe"
|
||||
|
|
|
@ -50,7 +50,7 @@ class CreateShortcodeAction extends AbstractRestAction
|
|||
*/
|
||||
public function process(Request $request, DelegateInterface $delegate)
|
||||
{
|
||||
$postData = $request->getParsedBody();
|
||||
$postData = (array) $request->getParsedBody();
|
||||
if (! isset($postData['longUrl'])) {
|
||||
return new JsonResponse([
|
||||
'error' => RestUtils::INVALID_ARGUMENT_ERROR,
|
||||
|
|
74
module/Rest/src/Action/EditShortCodeAction.php
Normal file
74
module/Rest/src/Action/EditShortCodeAction.php
Normal file
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Rest\Action;
|
||||
|
||||
use Interop\Http\ServerMiddleware\DelegateInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Shlinkio\Shlink\Core\Exception;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||
use Zend\Diactoros\Response\EmptyResponse;
|
||||
use Zend\Diactoros\Response\JsonResponse;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class EditShortCodeAction extends AbstractRestAction
|
||||
{
|
||||
/**
|
||||
* @var ShortUrlServiceInterface
|
||||
*/
|
||||
private $shortUrlService;
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
public function __construct(
|
||||
ShortUrlServiceInterface $shortUrlService,
|
||||
TranslatorInterface $translator,
|
||||
LoggerInterface $logger = null
|
||||
) {
|
||||
parent::__construct($logger);
|
||||
$this->shortUrlService = $shortUrlService;
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an incoming server request and return a response, optionally delegating
|
||||
* to the next middleware component to create the response.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param DelegateInterface $delegate
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, DelegateInterface $delegate): ResponseInterface
|
||||
{
|
||||
$postData = (array) $request->getParsedBody();
|
||||
$shortCode = $request->getAttribute('shortCode', '');
|
||||
|
||||
try {
|
||||
$this->shortUrlService->updateMetadataByShortCode(
|
||||
$shortCode,
|
||||
ShortUrlMeta::createFromRawData($postData)
|
||||
);
|
||||
return new EmptyResponse();
|
||||
} catch (Exception\InvalidShortCodeException $e) {
|
||||
$this->logger->warning('Provided data is invalid.' . PHP_EOL . $e);
|
||||
return new JsonResponse([
|
||||
'error' => RestUtils::getRestErrorCodeFromException($e),
|
||||
'message' => \sprintf($this->translator->translate('No URL found for short code "%s"'), $shortCode),
|
||||
], self::STATUS_NOT_FOUND);
|
||||
} catch (Exception\ValidationException $e) {
|
||||
$this->logger->warning('Provided data is invalid.' . PHP_EOL . $e);
|
||||
return new JsonResponse([
|
||||
'error' => RestUtils::getRestErrorCodeFromException($e),
|
||||
'message' => $this->translator->translate('Provided data is invalid.'),
|
||||
], self::STATUS_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -75,10 +75,10 @@ class GetVisitsAction extends AbstractRestAction
|
|||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param $key
|
||||
* @param string $key
|
||||
* @return \DateTime|null
|
||||
*/
|
||||
protected function getDateQueryParam(Request $request, $key)
|
||||
private function getDateQueryParam(Request $request, string $key)
|
||||
{
|
||||
$query = $request->getQueryParams();
|
||||
if (! isset($query[$key]) || empty($query[$key])) {
|
||||
|
|
|
@ -27,7 +27,7 @@ class JWTService implements JWTServiceInterface
|
|||
* @param int $lifetime
|
||||
* @return string
|
||||
*/
|
||||
public function create(ApiKey $apiKey, $lifetime = self::DEFAULT_LIFETIME)
|
||||
public function create(ApiKey $apiKey, $lifetime = self::DEFAULT_LIFETIME): string
|
||||
{
|
||||
$currentTimestamp = time();
|
||||
|
||||
|
@ -48,7 +48,7 @@ class JWTService implements JWTServiceInterface
|
|||
* @return string
|
||||
* @throws AuthenticationException If the token has expired
|
||||
*/
|
||||
public function refresh($jwt, $lifetime = self::DEFAULT_LIFETIME)
|
||||
public function refresh(string $jwt, $lifetime = self::DEFAULT_LIFETIME): string
|
||||
{
|
||||
$payload = $this->getPayload($jwt);
|
||||
$payload['exp'] = time() + $lifetime;
|
||||
|
@ -61,7 +61,7 @@ class JWTService implements JWTServiceInterface
|
|||
* @param string $jwt
|
||||
* @return bool
|
||||
*/
|
||||
public function verify($jwt)
|
||||
public function verify(string $jwt): bool
|
||||
{
|
||||
try {
|
||||
// If no exception is thrown while decoding the token, it is considered valid
|
||||
|
@ -79,7 +79,7 @@ class JWTService implements JWTServiceInterface
|
|||
* @return array
|
||||
* @throws AuthenticationException If the token has expired
|
||||
*/
|
||||
public function getPayload($jwt)
|
||||
public function getPayload(string $jwt): array
|
||||
{
|
||||
try {
|
||||
return $this->decode($jwt);
|
||||
|
@ -92,16 +92,16 @@ class JWTService implements JWTServiceInterface
|
|||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
protected function encode(array $data)
|
||||
protected function encode(array $data): string
|
||||
{
|
||||
return JWT::encode($data, $this->appOptions->getSecretKey(), self::DEFAULT_ENCRYPTION_ALG);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $jwt
|
||||
* @param string $jwt
|
||||
* @return array
|
||||
*/
|
||||
protected function decode($jwt)
|
||||
protected function decode(string $jwt): array
|
||||
{
|
||||
return (array) JWT::decode($jwt, $this->appOptions->getSecretKey(), [self::DEFAULT_ENCRYPTION_ALG]);
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue