mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-05 06:43:12 +08:00
Compare commits
141 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
065cddc4b1 | ||
|
|
3a3a16f46f | ||
|
|
ba5bd6d98c | ||
|
|
a1aa9c2031 | ||
|
|
43c6b56e42 | ||
|
|
39d2f5a38f | ||
|
|
69cc30bce7 | ||
|
|
5913550eec | ||
|
|
3140ab2ad7 | ||
|
|
090479fa62 | ||
|
|
9ee2064ba1 | ||
|
|
90cef7d4d9 | ||
|
|
12410e82d8 | ||
|
|
18084433c7 | ||
|
|
8eb279fd28 | ||
|
|
99b7c77997 | ||
|
|
166c94cac7 | ||
|
|
7c5d8cf244 | ||
|
|
73a236b3d0 | ||
|
|
34753ca7d3 | ||
|
|
fff058f44b | ||
|
|
b7f3c332e4 | ||
|
|
cff9b7c0b5 | ||
|
|
63e867cf4b | ||
|
|
f49e9064cd | ||
|
|
3bd4f506e0 | ||
|
|
93713689d7 | ||
|
|
ecd2e6e759 | ||
|
|
a65003803b | ||
|
|
0a4f8c3b0a | ||
|
|
80d8c32881 | ||
|
|
57bc681b9e | ||
|
|
2a089f05b1 | ||
|
|
258f954a38 | ||
|
|
7b0beb3b8c | ||
|
|
9573e9f4ef | ||
|
|
a60080b1ce | ||
|
|
1d92e87d50 | ||
|
|
289db45f27 | ||
|
|
c5382b2a7f | ||
|
|
dd1bc49b79 | ||
|
|
74777c2234 | ||
|
|
99d7e6dd7d | ||
|
|
7b746f76b0 | ||
|
|
2767a14101 | ||
|
|
270dbc6028 | ||
|
|
7b1b00901a | ||
|
|
9ab4b9ab43 | ||
|
|
5e493b435a | ||
|
|
21dc9ac5a0 | ||
|
|
40a3ba6203 | ||
|
|
307dfc64b4 | ||
|
|
9b9b1415fe | ||
|
|
ce2b28a0b4 | ||
|
|
7b98527f2e | ||
|
|
30988b10d1 | ||
|
|
d73d3049b7 | ||
|
|
7b1e855e7f | ||
|
|
a957f66ed0 | ||
|
|
ce00874dd1 | ||
|
|
3d5e5d5df9 | ||
|
|
08f6d2de78 | ||
|
|
c6b7515285 | ||
|
|
04e0a192ad | ||
|
|
878518ced7 | ||
|
|
6f7e4f7e7f | ||
|
|
f701e65f75 | ||
|
|
ef6f4fba66 | ||
|
|
f904f79c18 | ||
|
|
41939b790d | ||
|
|
8c446f0f3b | ||
|
|
9c6420fe26 | ||
|
|
ce4877d4ac | ||
|
|
ebeaa3c64a | ||
|
|
fcdcfde04f | ||
|
|
2ce6c1f44b | ||
|
|
00db8a7ea5 | ||
|
|
4c6cc9cd11 | ||
|
|
50f1549457 | ||
|
|
3923bf0604 | ||
|
|
bb8404040a | ||
|
|
5cdd782ce1 | ||
|
|
e345c2bbfe | ||
|
|
2a018f5415 | ||
|
|
7394424a43 | ||
|
|
2f5119d0b3 | ||
|
|
c569cef239 | ||
|
|
ab6aa99a6d | ||
|
|
f4532c3015 | ||
|
|
af9193f721 | ||
|
|
36259588db | ||
|
|
a3554db1c3 | ||
|
|
75e744838c | ||
|
|
f3d2cf5e15 | ||
|
|
a81dba58bd | ||
|
|
83f29080c6 | ||
|
|
0ef1e416c6 | ||
|
|
57d81115de | ||
|
|
5c8353da02 | ||
|
|
95346ebb7e | ||
|
|
cd5bbcd60a | ||
|
|
fb9c7f8eec | ||
|
|
e42469b090 | ||
|
|
06868f782b | ||
|
|
73a35a8f44 | ||
|
|
6a05265a48 | ||
|
|
8e51b51cae | ||
|
|
545fe7da70 | ||
|
|
cb99130c1e | ||
|
|
b413b16c86 | ||
|
|
3ba51c5390 | ||
|
|
0ef9db0bdf | ||
|
|
bdd2d6f8b2 | ||
|
|
45d194aced | ||
|
|
0a57f52309 | ||
|
|
d97287ab0c | ||
|
|
84c4021b24 | ||
|
|
d39a949992 | ||
|
|
dbe1281d2a | ||
|
|
d3c2f4ed2a | ||
|
|
06fa33877b | ||
|
|
c290bed354 | ||
|
|
31af0eea04 | ||
|
|
aaf4f1dfe5 | ||
|
|
e28e984278 | ||
|
|
839329d627 | ||
|
|
0daa24739d | ||
|
|
7ca52ecff9 | ||
|
|
39598d8608 | ||
|
|
923abdf4d2 | ||
|
|
f917697b8e | ||
|
|
fe8ef2030f | ||
|
|
ab9c2f728a | ||
|
|
ba06ad44bd | ||
|
|
8fc88171ee | ||
|
|
7efb3b3a86 | ||
|
|
170c96b8ef | ||
|
|
55f954f50f | ||
|
|
95d0beea3c | ||
|
|
5eefaf3071 | ||
|
|
5536d05e99 |
@@ -1,14 +1,15 @@
|
||||
# Application
|
||||
APP_ENV=
|
||||
SECRET_KEY=
|
||||
SHORTENED_URL_SCHEMA=
|
||||
SHORTENED_URL_HOSTNAME=
|
||||
SHORTCODE_CHARS=
|
||||
|
||||
# Language
|
||||
DEFAULT_LOCALE=
|
||||
CLI_LOCALE=
|
||||
|
||||
# Database
|
||||
DB_USER=
|
||||
DB_PASSWORD=
|
||||
DB_NAME=
|
||||
|
||||
# Rest authentication
|
||||
REST_USER=
|
||||
REST_PASSWORD=
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
.idea
|
||||
build
|
||||
composer.lock
|
||||
vendor/
|
||||
|
||||
19
.phpstorm.meta.php
Normal file
19
.phpstorm.meta.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
namespace PHPSTORM_META;
|
||||
|
||||
use Interop\Container\ContainerInterface;
|
||||
|
||||
/**
|
||||
* PhpStorm Container Interop code completion
|
||||
*
|
||||
* Add code completion for container-interop.
|
||||
*
|
||||
* \App\ClassName::class will automatically resolve to it's own name.
|
||||
*
|
||||
* Custom strings like ``"cache"`` or ``"logger"`` need to be added manually.
|
||||
*/
|
||||
$STATIC_METHOD_TYPES = [
|
||||
ContainerInterface::get('') => [
|
||||
'' == '@',
|
||||
],
|
||||
];
|
||||
1
.travis-php.ini
Normal file
1
.travis-php.ini
Normal file
@@ -0,0 +1 @@
|
||||
extension="memcached.so"
|
||||
@@ -8,7 +8,9 @@ branches:
|
||||
php:
|
||||
- 5.6
|
||||
- 7
|
||||
- hhvm
|
||||
- 7.1
|
||||
|
||||
before_install: phpenv config-add .travis-php.ini
|
||||
|
||||
before_script:
|
||||
- composer self-update
|
||||
|
||||
50
CHANGELOG.md
50
CHANGELOG.md
@@ -1,5 +1,55 @@
|
||||
## CHANGELOG
|
||||
|
||||
### 1.1.0
|
||||
|
||||
**Features**
|
||||
|
||||
* [46: Define a route that returns a QR code representing the shortened URL](https://github.com/acelaya/url-shortener/issues/46)
|
||||
|
||||
**Enhancements:**
|
||||
|
||||
* [32: Add support for other cache adapters by improving the Cache factory](https://github.com/acelaya/url-shortener/issues/32)
|
||||
* [14: https://github.com/shlinkio/shlink/issues/14](https://github.com/acelaya/url-shortener/issues/14)
|
||||
* [41: Cache the "short code" => "URL" map to prevent extra DB hits](https://github.com/acelaya/url-shortener/issues/41)
|
||||
* [13: Improve REST authentication](https://github.com/acelaya/url-shortener/issues/13)
|
||||
|
||||
**Tasks**
|
||||
|
||||
* [39: Change copyright from "Alejandro Celaya" to "Shlink" in error pages](https://github.com/acelaya/url-shortener/issues/39)
|
||||
* [42: Make REST endpoints that need to find something return a 404 when "something" is not found](https://github.com/acelaya/url-shortener/issues/42)
|
||||
* [35: Make CLI commands to use the same PHP namespace as the one used for the command name](https://github.com/acelaya/url-shortener/issues/35)
|
||||
|
||||
**Bugs**
|
||||
|
||||
* [40: Take into account the X-Forwarded-For header in order to get the visitor information, in case the server is behind a load balancer or proxy](https://github.com/acelaya/url-shortener/issues/40)
|
||||
|
||||
### 1.0.0
|
||||
|
||||
**Enhancements:**
|
||||
|
||||
* [33: Create a command to generate a short code charset by randomizing the default one](https://github.com/acelaya/url-shortener/issues/33)
|
||||
* [15: Return JSON/HTML responses for errors (4xx and 5xx) based on accept header (content negotiation)](https://github.com/acelaya/url-shortener/issues/15)
|
||||
* [23: Translate application literals](https://github.com/acelaya/url-shortener/issues/23)
|
||||
* [21: Allow to filter visits by date range](https://github.com/acelaya/url-shortener/issues/21)
|
||||
* [22: Save visits locations data on a visit_locations table](https://github.com/acelaya/url-shortener/issues/22)
|
||||
* [20: Inject cross domain headers in response only if the Origin header is present in the request](https://github.com/acelaya/url-shortener/issues/20)
|
||||
* [11: Separate code into multiple modules](https://github.com/acelaya/url-shortener/issues/11)
|
||||
* [18: Group routable middleware in an Action namespace](https://github.com/acelaya/url-shortener/issues/18)
|
||||
|
||||
**Tasks**
|
||||
|
||||
* [36: Remove hhvm from the CI matrix since it doesn't support array constants and will fail](https://github.com/acelaya/url-shortener/issues/36)
|
||||
* [4: Installation steps](https://github.com/acelaya/url-shortener/issues/4)
|
||||
* [6: Remove dependency on expressive helpers package](https://github.com/acelaya/url-shortener/issues/6)
|
||||
* [30: Replace the "services" first level config entry by "dependencies", in order to fulfill default Expressive name](https://github.com/acelaya/url-shortener/issues/30)
|
||||
* [12: Improve code coverage](https://github.com/acelaya/url-shortener/issues/12)
|
||||
* [25: Replace "Middleware" suffix on routable middlewares by "Action"](https://github.com/acelaya/url-shortener/issues/25)
|
||||
* [19: Update the vendor and app namespace from Acelaya\UrlShortener to Shlinkio\Shlink](https://github.com/acelaya/url-shortener/issues/19)
|
||||
|
||||
**Bugs**
|
||||
|
||||
* [24: Prevent duplicated shortcodes errors because of the case insensitive behavior on MySQL](https://github.com/acelaya/url-shortener/issues/24)
|
||||
|
||||
### 0.2.0
|
||||
|
||||
**Enhancements:**
|
||||
|
||||
@@ -1,2 +1,9 @@
|
||||
# url-shortener
|
||||
# Shlink
|
||||
|
||||
[](https://travis-ci.org/shlinkio/shlink)
|
||||
[](https://scrutinizer-ci.com/g/shlinkio/shlink/?branch=master)
|
||||
[](https://scrutinizer-ci.com/g/shlinkio/shlink/?branch=master)
|
||||
[](https://packagist.org/packages/shlinkio/shlink)
|
||||
[](https://packagist.org/packages/shlinkio/shlink)
|
||||
|
||||
A PHP-based URL shortener application with analytics and management
|
||||
|
||||
7
bin/cli
7
bin/cli
@@ -2,9 +2,16 @@
|
||||
<?php
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Symfony\Component\Console\Application as CliApp;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
|
||||
/** @var ContainerInterface $container */
|
||||
$container = include __DIR__ . '/../config/container.php';
|
||||
|
||||
/** @var Translator $translator */
|
||||
$translator = $container->get('translator');
|
||||
$translator->setLocale(env('CLI_LOCALE', 'en'));
|
||||
|
||||
/** @var Application $app */
|
||||
$app = $container->get(CliApp::class);
|
||||
$app->run();
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"name": "acelaya/url-shortener",
|
||||
"name": "shlinkio/shlink",
|
||||
"type": "project",
|
||||
"homepage": "https://github.com/acelaya/url-shortener",
|
||||
"homepage": "http://shlink.io",
|
||||
"description": "A self-hosted and PHP-based URL shortener application with CLI and REST interfaces",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Alejandro Celaya ALastrué",
|
||||
"name": "Alejandro Celaya Alastrué",
|
||||
"homepage": "http://www.alejandrocelaya.com",
|
||||
"email": "alejandro@alejandrocelaya.com"
|
||||
}
|
||||
@@ -13,16 +14,22 @@
|
||||
"require": {
|
||||
"php": "^5.6 || ^7.0",
|
||||
"zendframework/zend-expressive": "^1.0",
|
||||
"zendframework/zend-expressive-helpers": "^2.0",
|
||||
"zendframework/zend-expressive-fastroute": "^1.1",
|
||||
"zendframework/zend-expressive-twigrenderer": "^1.0",
|
||||
"zendframework/zend-stdlib": "^2.7",
|
||||
"zendframework/zend-servicemanager": "^3.0",
|
||||
"zendframework/zend-paginator": "^2.6",
|
||||
"zendframework/zend-config": "^2.6",
|
||||
"zendframework/zend-i18n": "^2.7",
|
||||
"mtymek/expressive-config-manager": "^0.4",
|
||||
"acelaya/zsm-annotated-services": "^0.2.0",
|
||||
"doctrine/orm": "^2.5",
|
||||
"guzzlehttp/guzzle": "^6.2",
|
||||
"acelaya/zsm-annotated-services": "^0.2.0",
|
||||
"symfony/console": "^3.0"
|
||||
"symfony/console": "^3.0",
|
||||
"firebase/php-jwt": "^4.0",
|
||||
"monolog/monolog": "^1.21",
|
||||
"theorchard/monolog-cascade": "^0.4",
|
||||
"endroid/qrcode": "^1.7"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^5.0",
|
||||
@@ -34,12 +41,21 @@
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Acelaya\\UrlShortener\\": "src"
|
||||
}
|
||||
"Shlinkio\\Shlink\\CLI\\": "module/CLI/src",
|
||||
"Shlinkio\\Shlink\\Rest\\": "module/Rest/src",
|
||||
"Shlinkio\\Shlink\\Core\\": "module/Core/src",
|
||||
"Shlinkio\\Shlink\\Common\\": "module/Common/src"
|
||||
},
|
||||
"files": [
|
||||
"module/Common/functions/functions.php"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"AcelayaTest\\UrlShortener\\": "tests"
|
||||
"ShlinkioTest\\Shlink\\CLI\\": "module/CLI/test",
|
||||
"ShlinkioTest\\Shlink\\Rest\\": "module/Rest/test",
|
||||
"ShlinkioTest\\Shlink\\Core\\": "module/Core/test",
|
||||
"ShlinkioTest\\Shlink\\Common\\": "module/Common/test"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
10
config/autoload/app_options.global.php
Normal file
10
config/autoload/app_options.global.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
return [
|
||||
|
||||
'app_options' => [
|
||||
'name' => 'Shlink',
|
||||
'version' => '1.1.0',
|
||||
'secret_key' => env('SECRET_KEY'),
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,15 +0,0 @@
|
||||
<?php
|
||||
use Acelaya\UrlShortener\CLI\Command;
|
||||
|
||||
return [
|
||||
|
||||
'cli' => [
|
||||
'commands' => [
|
||||
Command\GenerateShortcodeCommand::class,
|
||||
Command\ResolveUrlCommand::class,
|
||||
Command\ListShortcodesCommand::class,
|
||||
Command\GetVisitsCommand::class,
|
||||
]
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,15 +0,0 @@
|
||||
<?php
|
||||
return [
|
||||
|
||||
'database' => [
|
||||
'driver' => 'pdo_mysql',
|
||||
'user' => getenv('DB_USER'),
|
||||
'password' => getenv('DB_PASSWORD'),
|
||||
'dbname' => getenv('DB_NAME') ?: 'acelaya_url_shortener',
|
||||
'charset' => 'utf8',
|
||||
'driverOptions' => [
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
24
config/autoload/dependencies.global.php
Normal file
24
config/autoload/dependencies.global.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
use Shlinkio\Shlink\Common\ErrorHandler\ContentBasedErrorHandler;
|
||||
use Zend\Expressive;
|
||||
use Zend\Expressive\Container;
|
||||
use Zend\Expressive\Router;
|
||||
use Zend\Expressive\Template;
|
||||
use Zend\Expressive\Twig;
|
||||
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||
|
||||
return [
|
||||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
Expressive\Application::class => Container\ApplicationFactory::class,
|
||||
Router\FastRouteRouter::class => InvokableFactory::class,
|
||||
Template\TemplateRendererInterface::class => Twig\TwigRendererFactory::class,
|
||||
],
|
||||
'aliases' => [
|
||||
Router\RouterInterface::class => Router\FastRouteRouter::class,
|
||||
'Zend\Expressive\FinalHandler' => ContentBasedErrorHandler::class,
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
20
config/autoload/entity-manager.global.php
Normal file
20
config/autoload/entity-manager.global.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
return [
|
||||
|
||||
'entity_manager' => [
|
||||
'orm' => [
|
||||
'proxies_dir' => 'data/proxies',
|
||||
],
|
||||
'connection' => [
|
||||
'driver' => 'pdo_mysql',
|
||||
'user' => env('DB_USER'),
|
||||
'password' => env('DB_PASSWORD'),
|
||||
'dbname' => env('DB_NAME', 'shlink'),
|
||||
'charset' => 'utf8',
|
||||
'driverOptions' => [
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,14 +1,13 @@
|
||||
<?php
|
||||
use Shlinkio\Shlink\Common\ErrorHandler\ContentBasedErrorHandler;
|
||||
use Zend\Expressive\Container\WhoopsErrorHandlerFactory;
|
||||
|
||||
return [
|
||||
'services' => [
|
||||
'dependencies' => [
|
||||
'invokables' => [
|
||||
'Zend\Expressive\Whoops' => Whoops\Run::class,
|
||||
'Zend\Expressive\WhoopsPageHandler' => Whoops\Handler\PrettyPageHandler::class,
|
||||
],
|
||||
'factories' => [
|
||||
'Zend\Expressive\FinalHandler' => Zend\Expressive\Container\WhoopsErrorHandlerFactory::class,
|
||||
],
|
||||
],
|
||||
|
||||
'whoops' => [
|
||||
@@ -18,4 +17,12 @@ return [
|
||||
'ajax_only' => true,
|
||||
],
|
||||
],
|
||||
|
||||
'error_handler' => [
|
||||
'plugins' => [
|
||||
'factories' => [
|
||||
ContentBasedErrorHandler::DEFAULT_CONTENT => WhoopsErrorHandlerFactory::class,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'debug' => true,
|
||||
|
||||
'debug' => true,
|
||||
'config_cache_enabled' => false,
|
||||
|
||||
];
|
||||
|
||||
32
config/autoload/logger.global.php
Normal file
32
config/autoload/logger.global.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
use Monolog\Handler\RotatingFileHandler;
|
||||
use Monolog\Logger;
|
||||
|
||||
return [
|
||||
|
||||
'logger' => [
|
||||
'formatters' => [
|
||||
'dashed' => [
|
||||
'format' => '[%datetime%] %channel%.%level_name% - %message% %context%' . PHP_EOL,
|
||||
'include_stacktraces' => true,
|
||||
],
|
||||
],
|
||||
|
||||
'handlers' => [
|
||||
'rotating_file_handler' => [
|
||||
'class' => RotatingFileHandler::class,
|
||||
'level' => Logger::INFO,
|
||||
'filename' => 'data/log/shlink_log.log',
|
||||
'max_files' => 30,
|
||||
'formatter' => 'dashed',
|
||||
],
|
||||
],
|
||||
|
||||
'loggers' => [
|
||||
'Shlink' => [
|
||||
'handlers' => ['rotating_file_handler'],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
14
config/autoload/logger.local.php.dist
Normal file
14
config/autoload/logger.local.php.dist
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
use Monolog\Logger;
|
||||
|
||||
return [
|
||||
|
||||
'logger' => [
|
||||
'handlers' => [
|
||||
'rotating_file_handler' => [
|
||||
'level' => Logger::DEBUG,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,18 +1,9 @@
|
||||
<?php
|
||||
use Acelaya\UrlShortener\Middleware;
|
||||
use Zend\Expressive\Container\ApplicationFactory;
|
||||
use Zend\Expressive\Helper;
|
||||
|
||||
return [
|
||||
|
||||
'middleware_pipeline' => [
|
||||
'always' => [
|
||||
'middleware' => [
|
||||
Helper\ServerUrlMiddleware::class,
|
||||
],
|
||||
'priority' => 10000,
|
||||
],
|
||||
|
||||
'routing' => [
|
||||
'middleware' => [
|
||||
ApplicationFactory::ROUTING_MIDDLEWARE,
|
||||
@@ -20,27 +11,11 @@ return [
|
||||
'priority' => 10,
|
||||
],
|
||||
|
||||
'rest' => [
|
||||
'path' => '/rest',
|
||||
'middleware' => [
|
||||
Middleware\CheckAuthenticationMiddleware::class,
|
||||
Middleware\CrossDomainMiddleware::class,
|
||||
],
|
||||
'priority' => 5,
|
||||
],
|
||||
|
||||
'post-routing' => [
|
||||
'middleware' => [
|
||||
Helper\UrlHelperMiddleware::class,
|
||||
ApplicationFactory::DISPATCH_MIDDLEWARE,
|
||||
],
|
||||
'priority' => 1,
|
||||
],
|
||||
|
||||
'error' => [
|
||||
'middleware' => [],
|
||||
'error' => true,
|
||||
'priority' => -10000,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
return [
|
||||
|
||||
'rest' => [
|
||||
'username' => getenv('REST_USER'),
|
||||
'password' => getenv('REST_PASSWORD'),
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,70 +0,0 @@
|
||||
<?php
|
||||
use Acelaya\UrlShortener\CLI;
|
||||
use Acelaya\UrlShortener\Factory\CacheFactory;
|
||||
use Acelaya\UrlShortener\Factory\EntityManagerFactory;
|
||||
use Acelaya\UrlShortener\Middleware;
|
||||
use Acelaya\UrlShortener\Service;
|
||||
use Acelaya\ZsmAnnotatedServices\Factory\V3\AnnotatedFactory;
|
||||
use Doctrine\Common\Cache\Cache;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Symfony\Component\Console;
|
||||
use Zend\Expressive;
|
||||
use Zend\Expressive\Container;
|
||||
use Zend\Expressive\Helper;
|
||||
use Zend\Expressive\Router;
|
||||
use Zend\Expressive\Template;
|
||||
use Zend\Expressive\Twig;
|
||||
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||
|
||||
return [
|
||||
|
||||
'services' => [
|
||||
'factories' => [
|
||||
Expressive\Application::class => Container\ApplicationFactory::class,
|
||||
Console\Application::class => CLI\Factory\ApplicationFactory::class,
|
||||
|
||||
// Url helpers
|
||||
Helper\UrlHelper::class => Helper\UrlHelperFactory::class,
|
||||
Helper\ServerUrlMiddleware::class => Helper\ServerUrlMiddlewareFactory::class,
|
||||
Helper\UrlHelperMiddleware::class => Helper\UrlHelperMiddlewareFactory::class,
|
||||
Helper\ServerUrlHelper::class => InvokableFactory::class,
|
||||
Router\FastRouteRouter::class => InvokableFactory::class,
|
||||
|
||||
// View
|
||||
'Zend\Expressive\FinalHandler' => Container\TemplatedErrorHandlerFactory::class,
|
||||
Template\TemplateRendererInterface::class => Twig\TwigRendererFactory::class,
|
||||
|
||||
// Services
|
||||
EntityManager::class => EntityManagerFactory::class,
|
||||
GuzzleHttp\Client::class => InvokableFactory::class,
|
||||
Service\UrlShortener::class => AnnotatedFactory::class,
|
||||
Service\VisitsTracker::class => AnnotatedFactory::class,
|
||||
Service\ShortUrlService::class => AnnotatedFactory::class,
|
||||
Service\RestTokenService::class => AnnotatedFactory::class,
|
||||
Cache::class => CacheFactory::class,
|
||||
|
||||
// Cli commands
|
||||
CLI\Command\GenerateShortcodeCommand::class => AnnotatedFactory::class,
|
||||
CLI\Command\ResolveUrlCommand::class => AnnotatedFactory::class,
|
||||
CLI\Command\ListShortcodesCommand::class => AnnotatedFactory::class,
|
||||
CLI\Command\GetVisitsCommand::class => AnnotatedFactory::class,
|
||||
|
||||
// Middleware
|
||||
Middleware\Routable\RedirectMiddleware::class => AnnotatedFactory::class,
|
||||
Middleware\Rest\AuthenticateMiddleware::class => AnnotatedFactory::class,
|
||||
Middleware\Rest\CreateShortcodeMiddleware::class => AnnotatedFactory::class,
|
||||
Middleware\Rest\ResolveUrlMiddleware::class => AnnotatedFactory::class,
|
||||
Middleware\Rest\GetVisitsMiddleware::class => AnnotatedFactory::class,
|
||||
Middleware\Rest\ListShortcodesMiddleware::class => AnnotatedFactory::class,
|
||||
Middleware\CrossDomainMiddleware::class => InvokableFactory::class,
|
||||
Middleware\CheckAuthenticationMiddleware::class => AnnotatedFactory::class,
|
||||
],
|
||||
'aliases' => [
|
||||
'em' => EntityManager::class,
|
||||
'httpClient' => GuzzleHttp\Client::class,
|
||||
Router\RouterInterface::class => Router\FastRouteRouter::class,
|
||||
AnnotatedFactory::CACHE_SERVICE => Cache::class,
|
||||
]
|
||||
],
|
||||
|
||||
];
|
||||
@@ -2,12 +2,6 @@
|
||||
|
||||
return [
|
||||
|
||||
'templates' => [
|
||||
'paths' => [
|
||||
'templates'
|
||||
],
|
||||
],
|
||||
|
||||
'twig' => [
|
||||
'cache_dir' => 'data/cache/twig',
|
||||
'extensions' => [
|
||||
|
||||
8
config/autoload/translator.global.php
Normal file
8
config/autoload/translator.global.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
return [
|
||||
|
||||
'translator' => [
|
||||
'locale' => env('DEFAULT_LOCALE', 'en'),
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,12 +1,14 @@
|
||||
<?php
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
|
||||
return [
|
||||
|
||||
'url_shortener' => [
|
||||
'domain' => [
|
||||
'schema' => getenv('SHORTENED_URL_SCHEMA') ?: 'http',
|
||||
'hostname' => getenv('SHORTENED_URL_HOSTNAME'),
|
||||
'schema' => env('SHORTENED_URL_SCHEMA', 'http'),
|
||||
'hostname' => env('SHORTENED_URL_HOSTNAME'),
|
||||
],
|
||||
'shortcode_chars' => getenv('SHORTCODE_CHARS'),
|
||||
'shortcode_chars' => env('SHORTCODE_CHARS', UrlShortener::DEFAULT_CHARS),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'debug' => false,
|
||||
'config_cache_enabled' => true,
|
||||
|
||||
'config_cache_enabled' => false,
|
||||
|
||||
'zend-expressive' => [
|
||||
'error_handler' => [
|
||||
'template_404' => 'error/404.html.twig',
|
||||
'template_error' => 'error/error.html.twig',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
@@ -4,7 +4,7 @@ use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||
use Interop\Container\ContainerInterface;
|
||||
|
||||
/** @var ContainerInterface $container */
|
||||
$container = include __DIR__ . '/config/container.php';
|
||||
$container = include __DIR__ . '/container.php';
|
||||
/** @var EntityManager $em */
|
||||
$em = $container->get(EntityManager::class);
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<?php
|
||||
use Zend\Stdlib\ArrayUtils;
|
||||
use Zend\Stdlib\Glob;
|
||||
use Shlinkio\Shlink\CLI;
|
||||
use Shlinkio\Shlink\Common;
|
||||
use Shlinkio\Shlink\Core;
|
||||
use Shlinkio\Shlink\Rest;
|
||||
use Zend\Expressive\ConfigManager\ConfigManager;
|
||||
use Zend\Expressive\ConfigManager\ZendConfigProvider;
|
||||
|
||||
/**
|
||||
* Configuration files are loaded in a specific order. First ``global.php``, then ``*.global.php``.
|
||||
@@ -11,22 +15,10 @@ use Zend\Stdlib\Glob;
|
||||
* Obviously, if you use closures in your config you can't cache it.
|
||||
*/
|
||||
|
||||
$cachedConfigFile = 'data/cache/app_config.php';
|
||||
|
||||
$config = [];
|
||||
if (is_file($cachedConfigFile)) {
|
||||
// Try to load the cached config
|
||||
$config = include $cachedConfigFile;
|
||||
} else {
|
||||
// Load configuration from autoload path
|
||||
foreach (Glob::glob('config/autoload/{{,*.}global,{,*.}local}.php', Glob::GLOB_BRACE) as $file) {
|
||||
$config = ArrayUtils::merge($config, include $file);
|
||||
}
|
||||
|
||||
// Cache config if enabled
|
||||
if (isset($config['config_cache_enabled']) && $config['config_cache_enabled'] === true) {
|
||||
file_put_contents($cachedConfigFile, '<?php return ' . var_export($config, true) . ';');
|
||||
}
|
||||
}
|
||||
|
||||
return $config;
|
||||
return (new ConfigManager([
|
||||
Common\ConfigProvider::class,
|
||||
Core\ConfigProvider::class,
|
||||
CLI\ConfigProvider::class,
|
||||
Rest\ConfigProvider::class,
|
||||
new ZendConfigProvider('config/autoload/{{,*.}global,{,*.}local}.php'),
|
||||
], 'data/cache/app_config.php'))->getMergedConfig();
|
||||
|
||||
@@ -16,6 +16,6 @@ if (class_exists(Dotenv::class)) {
|
||||
|
||||
// Build container
|
||||
$config = require __DIR__ . '/config.php';
|
||||
$container = new ServiceManager($config['services']);
|
||||
$container = new ServiceManager($config['dependencies']);
|
||||
$container->setService('config', $config);
|
||||
return $container;
|
||||
|
||||
@@ -1,278 +0,0 @@
|
||||
|
||||
# REST API documentation
|
||||
|
||||
## Error management
|
||||
|
||||
Statuses:
|
||||
|
||||
* 400 -> controlled error
|
||||
* 401 -> authentication error
|
||||
* 500 -> unexpected error
|
||||
|
||||
[TODO]
|
||||
|
||||
## Authentication
|
||||
|
||||
[TODO]
|
||||
|
||||
## Endpoints
|
||||
|
||||
#### Authenticate
|
||||
|
||||
**REQUEST**
|
||||
|
||||
* `POST` -> `/rest/authenticate`
|
||||
* Params:
|
||||
* username: `string`
|
||||
* password: `string`
|
||||
|
||||
**SUCCESS RESPONSE**
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "9f741eb0-33d7-4c56-b8f7-3719e9929946"
|
||||
}
|
||||
```
|
||||
|
||||
**ERROR RESPONSE**
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "INVALID_ARGUMENT",
|
||||
"message": "You have to provide both \"username\" and \"password\""
|
||||
}
|
||||
```
|
||||
|
||||
Posible errors:
|
||||
|
||||
* **INVALID_ARGUMENT**: Username or password were not provided.
|
||||
* **INVALID_CREDENTIALS**: Username or password are incorrect.
|
||||
|
||||
|
||||
#### Create shortcode
|
||||
|
||||
**REQUEST**
|
||||
|
||||
* `POST` -> `/rest/short-codes`
|
||||
* Params:
|
||||
* longUrl: `string` -> The URL to shorten
|
||||
* Headers:
|
||||
* X-Auth-Token: `string` -> The token provided in the authentication request
|
||||
|
||||
**SUCCESS RESPONSE**
|
||||
|
||||
```json
|
||||
{
|
||||
"longUrl": "https://www.facebook.com/something/something",
|
||||
"shortUrl": "https://doma.in/rY9Kr",
|
||||
"shortCode": "rY9Kr"
|
||||
}
|
||||
```
|
||||
|
||||
**ERROR RESPONSE**
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "INVALID_URL",
|
||||
"message": "Provided URL \"wfwef\" is invalid. Try with a different one."
|
||||
}
|
||||
```
|
||||
|
||||
Posible errors:
|
||||
|
||||
* **INVALID_ARGUMENT**: The longUrl was not provided.
|
||||
* **INVALID_URL**: Provided longUrl has an invalid format or does not resolve.
|
||||
* **UNKNOWN_ERROR**: Something unexpected happened.
|
||||
|
||||
|
||||
#### Resolve URL
|
||||
|
||||
**REQUEST**
|
||||
|
||||
* `GET` -> `/rest/short-codes/{shortCode}`
|
||||
* Route params:
|
||||
* shortCode: `string` -> The short code we want to resolve
|
||||
* Headers:
|
||||
* X-Auth-Token: `string` -> The token provided in the authentication request
|
||||
|
||||
**SUCCESS RESPONSE**
|
||||
|
||||
```json
|
||||
{
|
||||
"longUrl": "https://www.facebook.com/something/something"
|
||||
}
|
||||
```
|
||||
|
||||
**ERROR RESPONSE**
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "INVALID_SHORTCODE",
|
||||
"message": "Provided short code \"abc123\" has an invalid format"
|
||||
}
|
||||
```
|
||||
|
||||
Posible errors:
|
||||
|
||||
* **INVALID_ARGUMENT**: No longUrl was found for provided shortCode.
|
||||
* **INVALID_SHORTCODE**: Provided shortCode does not match the character set used by the app to generate short codes.
|
||||
* **UNKNOWN_ERROR**: Something unexpected happened.
|
||||
|
||||
|
||||
#### List shortened URLs
|
||||
|
||||
**REQUEST**
|
||||
|
||||
* `GET` -> `/rest/short-codes`
|
||||
* Query params:
|
||||
* page: `integer` -> The page to list. Defaults to 1 if not provided.
|
||||
* Headers:
|
||||
* X-Auth-Token: `string` -> The token provided in the authentication request
|
||||
|
||||
**SUCCESS RESPONSE**
|
||||
|
||||
```json
|
||||
{
|
||||
"shortUrls": {
|
||||
"data": [
|
||||
{
|
||||
"shortCode": "abc123",
|
||||
"originalUrl": "http://www.alejandrocelaya.com",
|
||||
"dateCreated": "2016-04-30T18:01:47+0200",
|
||||
"visitsCount": 4
|
||||
},
|
||||
{
|
||||
"shortCode": "def456",
|
||||
"originalUrl": "http://www.alejandrocelaya.com/en",
|
||||
"dateCreated": "2016-04-30T18:03:43+0200",
|
||||
"visitsCount": 0
|
||||
},
|
||||
{
|
||||
"shortCode": "ghi789",
|
||||
"originalUrl": "http://www.alejandrocelaya.com/es",
|
||||
"dateCreated": "2016-04-30T18:10:38+0200",
|
||||
"visitsCount": 0
|
||||
},
|
||||
{
|
||||
"shortCode": "jkl987",
|
||||
"originalUrl": "http://www.alejandrocelaya.com/es/",
|
||||
"dateCreated": "2016-04-30T18:10:57+0200",
|
||||
"visitsCount": 0
|
||||
},
|
||||
{
|
||||
"shortCode": "mno654",
|
||||
"originalUrl": "http://blog.alejandrocelaya.com/2016/04/09/improving-zend-service-manager-workflow-with-annotations/",
|
||||
"dateCreated": "2016-04-30T19:21:05+0200",
|
||||
"visitsCount": 1
|
||||
},
|
||||
{
|
||||
"shortCode": "pqr321",
|
||||
"originalUrl": "http://www.google.com",
|
||||
"dateCreated": "2016-05-01T11:19:53+0200",
|
||||
"visitsCount": 0
|
||||
},
|
||||
{
|
||||
"shortCode": "stv159",
|
||||
"originalUrl": "http://www.acelaya.com",
|
||||
"dateCreated": "2016-06-12T17:49:21+0200",
|
||||
"visitsCount": 0
|
||||
},
|
||||
{
|
||||
"shortCode": "wxy753",
|
||||
"originalUrl": "http://www.atomic-reader.com",
|
||||
"dateCreated": "2016-06-12T17:50:27+0200",
|
||||
"visitsCount": 0
|
||||
},
|
||||
{
|
||||
"shortCode": "zab852",
|
||||
"originalUrl": "http://foo.com",
|
||||
"dateCreated": "2016-07-03T09:07:36+0200",
|
||||
"visitsCount": 0
|
||||
},
|
||||
{
|
||||
"shortCode": "cde963",
|
||||
"originalUrl": "https://www.facebook.com.com",
|
||||
"dateCreated": "2016-07-03T09:12:35+0200",
|
||||
"visitsCount": 0
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"currentPage": 4,
|
||||
"pagesCount": 15
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**ERROR RESPONSE**
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "UNKNOWN_ERROR",
|
||||
"message": "Unexpected error occured"
|
||||
}
|
||||
```
|
||||
|
||||
Posible errors:
|
||||
|
||||
* **UNKNOWN_ERROR**: Something unexpected happened.
|
||||
|
||||
|
||||
#### Get visits
|
||||
|
||||
**REQUEST**
|
||||
|
||||
* `GET` -> `/rest/visits/{shortCode}`
|
||||
* Route params:
|
||||
* shortCode: `string` -> The shortCode from which we eant to get the visits.
|
||||
* Headers:
|
||||
* X-Auth-Token: `string` -> The token provided in the authentication request
|
||||
|
||||
**SUCCESS RESPONSE**
|
||||
|
||||
```json
|
||||
{
|
||||
"shortUrls": {
|
||||
"data": [
|
||||
{
|
||||
"referer": null,
|
||||
"date": "2016-06-18T09:32:22+0200",
|
||||
"remoteAddr": "127.0.0.1",
|
||||
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36"
|
||||
},
|
||||
{
|
||||
"referer": null,
|
||||
"date": "2016-04-30T19:20:06+0200",
|
||||
"remoteAddr": "127.0.0.1",
|
||||
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36"
|
||||
},
|
||||
{
|
||||
"referer": "google.com",
|
||||
"date": "2016-04-30T19:19:57+0200",
|
||||
"remoteAddr": "1.2.3.4",
|
||||
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36"
|
||||
},
|
||||
{
|
||||
"referer": null,
|
||||
"date": "2016-04-30T19:17:35+0200",
|
||||
"remoteAddr": "127.0.0.1",
|
||||
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36"
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**ERROR RESPONSE**
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "INVALID_ARGUMENT",
|
||||
"message": "Provided short code \"abc123\" is invalid"
|
||||
}
|
||||
```
|
||||
|
||||
Posible errors:
|
||||
|
||||
* **INVALID_ARGUMENT**: The shortcode does not belong to any short URL
|
||||
* **UNKNOWN_ERROR**: Something unexpected happened.
|
||||
2
data/log/.gitignore
vendored
Normal file
2
data/log/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
21
module/CLI/config/cli.config.php
Normal file
21
module/CLI/config/cli.config.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
use Shlinkio\Shlink\CLI\Command;
|
||||
|
||||
return [
|
||||
|
||||
'cli' => [
|
||||
'commands' => [
|
||||
Command\Shortcode\GenerateShortcodeCommand::class,
|
||||
Command\Shortcode\ResolveUrlCommand::class,
|
||||
Command\Shortcode\ListShortcodesCommand::class,
|
||||
Command\Shortcode\GetVisitsCommand::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,
|
||||
]
|
||||
],
|
||||
|
||||
];
|
||||
26
module/CLI/config/dependencies.config.php
Normal file
26
module/CLI/config/dependencies.config.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
use Acelaya\ZsmAnnotatedServices\Factory\V3\AnnotatedFactory;
|
||||
use Shlinkio\Shlink\CLI\Command;
|
||||
use Shlinkio\Shlink\CLI\Factory\ApplicationFactory;
|
||||
use Symfony\Component\Console\Application;
|
||||
|
||||
return [
|
||||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
Application::class => ApplicationFactory::class,
|
||||
|
||||
Command\Shortcode\GenerateShortcodeCommand::class => AnnotatedFactory::class,
|
||||
Command\Shortcode\ResolveUrlCommand::class => AnnotatedFactory::class,
|
||||
Command\Shortcode\ListShortcodesCommand::class => AnnotatedFactory::class,
|
||||
Command\Shortcode\GetVisitsCommand::class => AnnotatedFactory::class,
|
||||
Command\Visit\ProcessVisitsCommand::class => AnnotatedFactory::class,
|
||||
Command\Config\GenerateCharsetCommand::class => AnnotatedFactory::class,
|
||||
Command\Config\GenerateSecretCommand::class => AnnotatedFactory::class,
|
||||
Command\Api\GenerateKeyCommand::class => AnnotatedFactory::class,
|
||||
Command\Api\DisableKeyCommand::class => AnnotatedFactory::class,
|
||||
Command\Api\ListKeysCommand::class => AnnotatedFactory::class,
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
14
module/CLI/config/translator.config.php
Normal file
14
module/CLI/config/translator.config.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
return [
|
||||
|
||||
'translator' => [
|
||||
'translation_file_patterns' => [
|
||||
[
|
||||
'type' => 'gettext',
|
||||
'base_dir' => __DIR__ . '/../lang',
|
||||
'pattern' => '%s.mo',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
BIN
module/CLI/lang/es.mo
Normal file
BIN
module/CLI/lang/es.mo
Normal file
Binary file not shown.
187
module/CLI/lang/es.po
Normal file
187
module/CLI/lang/es.po
Normal file
@@ -0,0 +1,187 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Shlink 1.0\n"
|
||||
"POT-Creation-Date: 2016-08-07 20:16+0200\n"
|
||||
"PO-Revision-Date: 2016-08-07 20:18+0200\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 1.8.7.1\n"
|
||||
"X-Poedit-Basepath: ..\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
"X-Poedit-KeywordsList: translate;translatePlural\n"
|
||||
"X-Poedit-SearchPath-0: src\n"
|
||||
"X-Poedit-SearchPath-1: config\n"
|
||||
|
||||
msgid "Disables an API key."
|
||||
msgstr "Desahbilita una clave de API."
|
||||
|
||||
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"
|
||||
|
||||
#, php-format
|
||||
msgid "API key \"%s\" does not exist."
|
||||
msgstr "La clave de API \"%s\" no existe."
|
||||
|
||||
msgid "Generates a new valid API key."
|
||||
msgstr "Genera una nueva clave de API válida."
|
||||
|
||||
msgid "The date in which the API key should expire. Use any valid PHP format."
|
||||
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"
|
||||
|
||||
msgid "Lists all the available API keys."
|
||||
msgstr "Lista todas las claves de API disponibles."
|
||||
|
||||
msgid "Tells if only enabled API keys should be returned."
|
||||
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"
|
||||
|
||||
#, php-format
|
||||
msgid ""
|
||||
"Generates a character set sample just by shuffling the default one, \"%s\". "
|
||||
"Then it can be set in the SHORTCODE_CHARS environment variable"
|
||||
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:"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Generates a short code for provided URL and returns the short URL"
|
||||
msgstr ""
|
||||
"Genera un código corto para la URL proporcionada y devuelve la URL acortada"
|
||||
|
||||
msgid "The long URL to parse"
|
||||
msgstr "La URL larga a procesar"
|
||||
|
||||
msgid "A long URL was not provided. Which URL do you want to shorten?:"
|
||||
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 "Generated URL:"
|
||||
msgstr "URL generada:"
|
||||
|
||||
#, php-format
|
||||
msgid "Provided URL \"%s\" is invalid. Try with a different one."
|
||||
msgstr "La URL proporcionada \"%s\" e inválida. Prueba con una diferente."
|
||||
|
||||
msgid "Returns the detailed visits information for provided short code"
|
||||
msgstr ""
|
||||
"Devuelve la información detallada de visitas para el código corto "
|
||||
"proporcionado"
|
||||
|
||||
msgid "The short code which visits we want to get"
|
||||
msgstr "El código corto del cual queremos obtener las visitas"
|
||||
|
||||
msgid "Allows to filter visits, returning only those older than start date"
|
||||
msgstr ""
|
||||
"Permite filtrar las visitas, devolviendo sólo aquellas más antiguas que "
|
||||
"startDate"
|
||||
|
||||
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 "Referer"
|
||||
msgstr "Origen"
|
||||
|
||||
msgid "Date"
|
||||
msgstr "Fecha"
|
||||
|
||||
msgid "Remote Address"
|
||||
msgstr "Dirección remota"
|
||||
|
||||
msgid "User agent"
|
||||
msgstr "Agente de usuario"
|
||||
|
||||
msgid "List all short URLs"
|
||||
msgstr "Listar todas las URLs cortas"
|
||||
|
||||
#, php-format
|
||||
msgid "The first page to list (%s items per page)"
|
||||
msgstr "La primera página a listar (%s elementos por página)"
|
||||
|
||||
msgid "Short code"
|
||||
msgstr "Código corto"
|
||||
|
||||
msgid "Original URL"
|
||||
msgstr "URL original"
|
||||
|
||||
msgid "Date created"
|
||||
msgstr "Fecha de creación"
|
||||
|
||||
msgid "Visits count"
|
||||
msgstr "Número de visitas"
|
||||
|
||||
msgid "You have reached last page"
|
||||
msgstr "Has alcanzado la última página"
|
||||
|
||||
msgid "Continue with page"
|
||||
msgstr "Continuar con la página"
|
||||
|
||||
msgid "Processes visits where location is not set yet"
|
||||
msgstr "Procesa las visitas donde la localización no ha sido establecida aún"
|
||||
|
||||
msgid "Processing IP"
|
||||
msgstr "Procesando IP"
|
||||
|
||||
msgid "Ignored localhost address"
|
||||
msgstr "Ignorada IP de localhost"
|
||||
|
||||
#, php-format
|
||||
msgid "Address located at \"%s\""
|
||||
msgstr "Dirección localizada en \"%s\""
|
||||
|
||||
msgid "Finished processing all IPs"
|
||||
msgstr "Finalizado el procesado de todas las IPs"
|
||||
|
||||
msgid "Returns the long URL behind a short code"
|
||||
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?:"
|
||||
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\""
|
||||
|
||||
msgid "Long URL:"
|
||||
msgstr "URL larga:"
|
||||
|
||||
#, php-format
|
||||
msgid "Provided short code \"%s\" has an invalid format."
|
||||
msgstr "El código corto proporcionado \"%s\" tiene un formato inválido."
|
||||
62
module/CLI/src/Command/Api/DisableKeyCommand.php
Normal file
62
module/CLI/src/Command/Api/DisableKeyCommand.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\CLI\Command\Api;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Shlinkio\Shlink\Rest\Service\ApiKeyService;
|
||||
use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface;
|
||||
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 Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class DisableKeyCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var ApiKeyServiceInterface
|
||||
*/
|
||||
private $apiKeyService;
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
/**
|
||||
* DisableKeyCommand constructor.
|
||||
* @param ApiKeyServiceInterface|ApiKeyService $apiKeyService
|
||||
* @param TranslatorInterface $translator
|
||||
*
|
||||
* @Inject({ApiKeyService::class, "translator"})
|
||||
*/
|
||||
public function __construct(ApiKeyServiceInterface $apiKeyService, TranslatorInterface $translator)
|
||||
{
|
||||
$this->apiKeyService = $apiKeyService;
|
||||
$this->translator = $translator;
|
||||
parent::__construct(null);
|
||||
}
|
||||
|
||||
public function configure()
|
||||
{
|
||||
$this->setName('api-key:disable')
|
||||
->setDescription($this->translator->translate('Disables an API key.'))
|
||||
->addArgument('apiKey', InputArgument::REQUIRED, $this->translator->translate('The API key to disable'));
|
||||
}
|
||||
|
||||
public function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$apiKey = $input->getArgument('apiKey');
|
||||
|
||||
try {
|
||||
$this->apiKeyService->disable($apiKey);
|
||||
$output->writeln(sprintf(
|
||||
$this->translator->translate('API key %s properly disabled'),
|
||||
'<info>' . $apiKey . '</info>'
|
||||
));
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$output->writeln(sprintf(
|
||||
'<error>' . $this->translator->translate('API key "%s" does not exist.') . '</error>',
|
||||
$apiKey
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
56
module/CLI/src/Command/Api/GenerateKeyCommand.php
Normal file
56
module/CLI/src/Command/Api/GenerateKeyCommand.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\CLI\Command\Api;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Shlinkio\Shlink\Rest\Service\ApiKeyService;
|
||||
use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class GenerateKeyCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var ApiKeyServiceInterface
|
||||
*/
|
||||
private $apiKeyService;
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
/**
|
||||
* GenerateKeyCommand constructor.
|
||||
* @param ApiKeyServiceInterface|ApiKeyService $apiKeyService
|
||||
* @param TranslatorInterface $translator
|
||||
*
|
||||
* @Inject({ApiKeyService::class, "translator"})
|
||||
*/
|
||||
public function __construct(ApiKeyServiceInterface $apiKeyService, TranslatorInterface $translator)
|
||||
{
|
||||
$this->apiKeyService = $apiKeyService;
|
||||
$this->translator = $translator;
|
||||
parent::__construct(null);
|
||||
}
|
||||
|
||||
public function configure()
|
||||
{
|
||||
$this->setName('api-key:generate')
|
||||
->setDescription($this->translator->translate('Generates a new valid API key.'))
|
||||
->addOption(
|
||||
'expirationDate',
|
||||
'e',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
$this->translator->translate('The date in which the API key should expire. Use any valid PHP format.')
|
||||
);
|
||||
}
|
||||
|
||||
public function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$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));
|
||||
}
|
||||
}
|
||||
108
module/CLI/src/Command/Api/ListKeysCommand.php
Normal file
108
module/CLI/src/Command/Api/ListKeysCommand.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\CLI\Command\Api;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
use Shlinkio\Shlink\Rest\Service\ApiKeyService;
|
||||
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 Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class ListKeysCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var ApiKeyServiceInterface
|
||||
*/
|
||||
private $apiKeyService;
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
/**
|
||||
* ListKeysCommand constructor.
|
||||
* @param ApiKeyServiceInterface|ApiKeyService $apiKeyService
|
||||
* @param TranslatorInterface $translator
|
||||
*
|
||||
* @Inject({ApiKeyService::class, "translator"})
|
||||
*/
|
||||
public function __construct(ApiKeyServiceInterface $apiKeyService, TranslatorInterface $translator)
|
||||
{
|
||||
$this->apiKeyService = $apiKeyService;
|
||||
$this->translator = $translator;
|
||||
parent::__construct(null);
|
||||
}
|
||||
|
||||
public function configure()
|
||||
{
|
||||
$this->setName('api-key:list')
|
||||
->setDescription($this->translator->translate('Lists all the available API keys.'))
|
||||
->addOption(
|
||||
'enabledOnly',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
$this->translator->translate('Tells if only enabled API keys should be returned.')
|
||||
);
|
||||
}
|
||||
|
||||
public function execute(InputInterface $input, OutputInterface $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'),
|
||||
]);
|
||||
}
|
||||
|
||||
/** @var ApiKey $row */
|
||||
foreach ($list as $row) {
|
||||
$key = $row->getKey();
|
||||
$expiration = $row->getExpirationDate();
|
||||
$rowData = [];
|
||||
|
||||
if ($enabledOnly) {
|
||||
$rowData[] = $key;
|
||||
} else {
|
||||
$rowData[] = $row->isEnabled() ? $this->getSuccessString($key) : $this->getErrorString($key);
|
||||
$rowData[] = $row->isEnabled() ? $this->getSuccessString('+++') : $this->getErrorString('---');
|
||||
}
|
||||
|
||||
$rowData[] = isset($expiration) ? $expiration->format(\DateTime::ISO8601) : '-';
|
||||
$table->addRow($rowData);
|
||||
}
|
||||
|
||||
$table->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @return string
|
||||
*/
|
||||
protected function getErrorString($string)
|
||||
{
|
||||
return sprintf('<fg=red>%s</>', $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @return string
|
||||
*/
|
||||
protected function getSuccessString($string)
|
||||
{
|
||||
return sprintf('<info>%s</info>', $string);
|
||||
}
|
||||
}
|
||||
44
module/CLI/src/Command/Config/GenerateCharsetCommand.php
Normal file
44
module/CLI/src/Command/Config/GenerateCharsetCommand.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\CLI\Command\Config;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
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 Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class GenerateCharsetCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
/**
|
||||
* GenerateCharsetCommand constructor.
|
||||
* @param TranslatorInterface $translator
|
||||
*
|
||||
* @Inject({"translator"})
|
||||
*/
|
||||
public function __construct(TranslatorInterface $translator)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
parent::__construct(null);
|
||||
}
|
||||
|
||||
public function configure()
|
||||
{
|
||||
$this->setName('config:generate-charset')
|
||||
->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'
|
||||
), UrlShortener::DEFAULT_CHARS));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
45
module/CLI/src/Command/Config/GenerateSecretCommand.php
Normal file
45
module/CLI/src/Command/Config/GenerateSecretCommand.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\CLI\Command\Config;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
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 Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class GenerateSecretCommand extends Command
|
||||
{
|
||||
use StringUtilsTrait;
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
/**
|
||||
* GenerateCharsetCommand constructor.
|
||||
* @param TranslatorInterface $translator
|
||||
*
|
||||
* @Inject({"translator"})
|
||||
*/
|
||||
public function __construct(TranslatorInterface $translator)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
parent::__construct(null);
|
||||
}
|
||||
|
||||
public function configure()
|
||||
{
|
||||
$this->setName('config:generate-secret')
|
||||
->setDescription($this->translator->translate(
|
||||
'Generates a random secret string that can be used for JWT token encryption'
|
||||
));
|
||||
}
|
||||
|
||||
public function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$secret = $this->generateRandomString(32);
|
||||
$output->writeln($this->translator->translate('Secret key:') . sprintf(' <info>%s</info>', $secret));
|
||||
}
|
||||
}
|
||||
108
module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php
Normal file
108
module/CLI/src/Command/Shortcode/GenerateShortcodeCommand.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\CLI\Command\Shortcode;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
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 Zend\Diactoros\Uri;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class GenerateShortcodeCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var UrlShortenerInterface
|
||||
*/
|
||||
private $urlShortener;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $domainConfig;
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
/**
|
||||
* GenerateShortcodeCommand constructor.
|
||||
* @param UrlShortenerInterface $urlShortener
|
||||
* @param TranslatorInterface $translator
|
||||
* @param array $domainConfig
|
||||
*
|
||||
* @Inject({UrlShortener::class, "translator", "config.url_shortener.domain"})
|
||||
*/
|
||||
public function __construct(
|
||||
UrlShortenerInterface $urlShortener,
|
||||
TranslatorInterface $translator,
|
||||
array $domainConfig
|
||||
) {
|
||||
$this->urlShortener = $urlShortener;
|
||||
$this->translator = $translator;
|
||||
$this->domainConfig = $domainConfig;
|
||||
parent::__construct(null);
|
||||
}
|
||||
|
||||
public function configure()
|
||||
{
|
||||
$this->setName('shortcode:generate')
|
||||
->setDescription(
|
||||
$this->translator->translate('Generates a short code for provided URL and returns the short URL')
|
||||
)
|
||||
->addArgument('longUrl', InputArgument::REQUIRED, $this->translator->translate('The long URL to parse'));
|
||||
}
|
||||
|
||||
public function interact(InputInterface $input, OutputInterface $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);
|
||||
if (! empty($longUrl)) {
|
||||
$input->setArgument('longUrl', $longUrl);
|
||||
}
|
||||
}
|
||||
|
||||
public function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$longUrl = $input->getArgument('longUrl');
|
||||
|
||||
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));
|
||||
$shortUrl = (new Uri())->withPath($shortCode)
|
||||
->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),
|
||||
]);
|
||||
} catch (InvalidUrlException $e) {
|
||||
$output->writeln(sprintf(
|
||||
'<error>' . $this->translator->translate(
|
||||
'Provided URL "%s" is invalid. Try with a different one.'
|
||||
) . '</error>',
|
||||
$longUrl
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
122
module/CLI/src/Command/Shortcode/GetVisitsCommand.php
Normal file
122
module/CLI/src/Command/Shortcode/GetVisitsCommand.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\CLI\Command\Shortcode;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTracker;
|
||||
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 Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class GetVisitsCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var VisitsTrackerInterface
|
||||
*/
|
||||
private $visitsTracker;
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
/**
|
||||
* GetVisitsCommand constructor.
|
||||
* @param VisitsTrackerInterface $visitsTracker
|
||||
* @param TranslatorInterface $translator
|
||||
*
|
||||
* @Inject({VisitsTracker::class, "translator"})
|
||||
*/
|
||||
public function __construct(VisitsTrackerInterface $visitsTracker, TranslatorInterface $translator)
|
||||
{
|
||||
$this->visitsTracker = $visitsTracker;
|
||||
$this->translator = $translator;
|
||||
parent::__construct(null);
|
||||
}
|
||||
|
||||
public function configure()
|
||||
{
|
||||
$this->setName('shortcode:visits')
|
||||
->setDescription(
|
||||
$this->translator->translate('Returns the detailed visits information for provided short code')
|
||||
)
|
||||
->addArgument(
|
||||
'shortCode',
|
||||
InputArgument::REQUIRED,
|
||||
$this->translator->translate('The short code which visits we want to get')
|
||||
)
|
||||
->addOption(
|
||||
'startDate',
|
||||
's',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
$this->translator->translate('Allows to filter visits, returning only those older than start date')
|
||||
)
|
||||
->addOption(
|
||||
'endDate',
|
||||
'e',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
$this->translator->translate('Allows to filter visits, returning only those newer than end date')
|
||||
);
|
||||
}
|
||||
|
||||
public function interact(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$shortCode = $input->getArgument('shortCode');
|
||||
if (! empty($shortCode)) {
|
||||
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);
|
||||
if (! empty($shortCode)) {
|
||||
$input->setArgument('shortCode', $shortCode);
|
||||
}
|
||||
}
|
||||
|
||||
public function execute(InputInterface $input, OutputInterface $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'),
|
||||
]);
|
||||
|
||||
foreach ($visits as $row) {
|
||||
$rowData = $row->jsonSerialize();
|
||||
// Unset location info
|
||||
unset($rowData['visitLocation']);
|
||||
|
||||
$table->addRow(array_values($rowData));
|
||||
}
|
||||
$table->render();
|
||||
}
|
||||
|
||||
protected function getDateOption(InputInterface $input, $key)
|
||||
{
|
||||
$value = $input->getOption($key);
|
||||
if (isset($value)) {
|
||||
$value = new \DateTime($value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
namespace Acelaya\UrlShortener\CLI\Command;
|
||||
namespace Shlinkio\Shlink\CLI\Command\Shortcode;
|
||||
|
||||
use Acelaya\UrlShortener\Paginator\Adapter\PaginableRepositoryAdapter;
|
||||
use Acelaya\UrlShortener\Paginator\Util\PaginatorUtilsTrait;
|
||||
use Acelaya\UrlShortener\Service\ShortUrlService;
|
||||
use Acelaya\UrlShortener\Service\ShortUrlServiceInterface;
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Shlinkio\Shlink\Common\Paginator\Adapter\PaginableRepositoryAdapter;
|
||||
use Shlinkio\Shlink\Common\Paginator\Util\PaginatorUtilsTrait;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrlService;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
@@ -13,6 +13,7 @@ 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 Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class ListShortcodesCommand extends Command
|
||||
{
|
||||
@@ -22,28 +23,37 @@ class ListShortcodesCommand extends Command
|
||||
* @var ShortUrlServiceInterface
|
||||
*/
|
||||
private $shortUrlService;
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
/**
|
||||
* ListShortcodesCommand constructor.
|
||||
* @param ShortUrlServiceInterface|ShortUrlService $shortUrlService
|
||||
* @param ShortUrlServiceInterface $shortUrlService
|
||||
* @param TranslatorInterface $translator
|
||||
*
|
||||
* @Inject({ShortUrlService::class})
|
||||
* @Inject({ShortUrlService::class, "translator"})
|
||||
*/
|
||||
public function __construct(ShortUrlServiceInterface $shortUrlService)
|
||||
public function __construct(ShortUrlServiceInterface $shortUrlService, TranslatorInterface $translator)
|
||||
{
|
||||
parent::__construct(null);
|
||||
$this->shortUrlService = $shortUrlService;
|
||||
$this->translator = $translator;
|
||||
parent::__construct(null);
|
||||
}
|
||||
|
||||
public function configure()
|
||||
{
|
||||
$this->setName('shortcode:list')
|
||||
->setDescription('List all short URLs')
|
||||
->setDescription($this->translator->translate('List all short URLs'))
|
||||
->addOption(
|
||||
'page',
|
||||
'p',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
sprintf('The first page to list (%s items per page)', PaginableRepositoryAdapter::ITEMS_PER_PAGE),
|
||||
sprintf(
|
||||
$this->translator->translate('The first page to list (%s items per page)'),
|
||||
PaginableRepositoryAdapter::ITEMS_PER_PAGE
|
||||
),
|
||||
1
|
||||
);
|
||||
}
|
||||
@@ -59,10 +69,10 @@ class ListShortcodesCommand extends Command
|
||||
$page++;
|
||||
$table = new Table($output);
|
||||
$table->setHeaders([
|
||||
'Short code',
|
||||
'Original URL',
|
||||
'Date created',
|
||||
'Visits count',
|
||||
$this->translator->translate('Short code'),
|
||||
$this->translator->translate('Original URL'),
|
||||
$this->translator->translate('Date created'),
|
||||
$this->translator->translate('Visits count'),
|
||||
]);
|
||||
|
||||
foreach ($result as $row) {
|
||||
@@ -72,10 +82,14 @@ class ListShortcodesCommand extends Command
|
||||
|
||||
if ($this->isLastPage($result)) {
|
||||
$continue = false;
|
||||
$output->writeln('<info>You have reached last page</info>');
|
||||
$output->writeln(
|
||||
sprintf('<info>%s</info>', $this->translator->translate('You have reached last page'))
|
||||
);
|
||||
} else {
|
||||
$continue = $helper->ask($input, $output, new ConfirmationQuestion(
|
||||
sprintf('<question>Continue with page <bg=cyan;options=bold>%s</>? (y/N)</question> ', $page),
|
||||
sprintf('<question>' . $this->translator->translate(
|
||||
'Continue with page'
|
||||
) . ' <bg=cyan;options=bold>%s</>? (y/N)</question> ', $page),
|
||||
false
|
||||
));
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
<?php
|
||||
namespace Acelaya\UrlShortener\CLI\Command;
|
||||
namespace Shlinkio\Shlink\CLI\Command\Shortcode;
|
||||
|
||||
use Acelaya\UrlShortener\Exception\InvalidShortCodeException;
|
||||
use Acelaya\UrlShortener\Service\UrlShortener;
|
||||
use Acelaya\UrlShortener\Service\UrlShortenerInterface;
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
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 Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class ResolveUrlCommand extends Command
|
||||
{
|
||||
@@ -18,24 +19,34 @@ class ResolveUrlCommand extends Command
|
||||
* @var UrlShortenerInterface
|
||||
*/
|
||||
private $urlShortener;
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
/**
|
||||
* ResolveUrlCommand constructor.
|
||||
* @param UrlShortenerInterface|UrlShortener $urlShortener
|
||||
* @param UrlShortenerInterface $urlShortener
|
||||
* @param TranslatorInterface $translator
|
||||
*
|
||||
* @Inject({UrlShortener::class})
|
||||
* @Inject({UrlShortener::class, "translator"})
|
||||
*/
|
||||
public function __construct(UrlShortenerInterface $urlShortener)
|
||||
public function __construct(UrlShortenerInterface $urlShortener, TranslatorInterface $translator)
|
||||
{
|
||||
parent::__construct(null);
|
||||
$this->urlShortener = $urlShortener;
|
||||
$this->translator = $translator;
|
||||
parent::__construct(null);
|
||||
}
|
||||
|
||||
public function configure()
|
||||
{
|
||||
$this->setName('shortcode:parse')
|
||||
->setDescription('Returns the long URL behind a short code')
|
||||
->addArgument('shortCode', InputArgument::REQUIRED, 'The short code to parse');
|
||||
->setDescription($this->translator->translate('Returns the long URL behind a short code'))
|
||||
->addArgument(
|
||||
'shortCode',
|
||||
InputArgument::REQUIRED,
|
||||
$this->translator->translate('The short code to parse')
|
||||
);
|
||||
}
|
||||
|
||||
public function interact(InputInterface $input, OutputInterface $output)
|
||||
@@ -47,9 +58,10 @@ class ResolveUrlCommand extends Command
|
||||
|
||||
/** @var QuestionHelper $helper */
|
||||
$helper = $this->getHelper('question');
|
||||
$question = new Question(
|
||||
'<question>A short code was not provided. Which short code do you want to parse?:</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);
|
||||
if (! empty($shortCode)) {
|
||||
@@ -64,15 +76,18 @@ class ResolveUrlCommand extends Command
|
||||
try {
|
||||
$longUrl = $this->urlShortener->shortCodeToUrl($shortCode);
|
||||
if (! isset($longUrl)) {
|
||||
$output->writeln(sprintf('<error>No URL found for short code "%s"</error>', $shortCode));
|
||||
$output->writeln(sprintf(
|
||||
'<error>' . $this->translator->translate('No URL found for short code "%s"') . '</error>',
|
||||
$shortCode
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
$output->writeln(sprintf('Long URL <info>%s</info>', $longUrl));
|
||||
$output->writeln(sprintf('%s <info>%s</info>', $this->translator->translate('Long URL:'), $longUrl));
|
||||
} catch (InvalidShortCodeException $e) {
|
||||
$output->writeln(
|
||||
sprintf('<error>Provided short code "%s" has an invalid format.</error>', $shortCode)
|
||||
);
|
||||
$output->writeln(sprintf('<error>' . $this->translator->translate(
|
||||
'Provided short code "%s" has an invalid format.'
|
||||
) . '</error>', $shortCode));
|
||||
}
|
||||
}
|
||||
}
|
||||
91
module/CLI/src/Command/Visit/ProcessVisitsCommand.php
Normal file
91
module/CLI/src/Command/Visit/ProcessVisitsCommand.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\CLI\Command\Visit;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\Common\Service\IpLocationResolver;
|
||||
use Shlinkio\Shlink\Common\Service\IpLocationResolverInterface;
|
||||
use Shlinkio\Shlink\Core\Entity\VisitLocation;
|
||||
use Shlinkio\Shlink\Core\Service\VisitService;
|
||||
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 Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class ProcessVisitsCommand extends Command
|
||||
{
|
||||
const LOCALHOST = '127.0.0.1';
|
||||
|
||||
/**
|
||||
* @var VisitServiceInterface
|
||||
*/
|
||||
private $visitService;
|
||||
/**
|
||||
* @var IpLocationResolverInterface
|
||||
*/
|
||||
private $ipLocationResolver;
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
/**
|
||||
* ProcessVisitsCommand constructor.
|
||||
* @param VisitServiceInterface $visitService
|
||||
* @param IpLocationResolverInterface $ipLocationResolver
|
||||
* @param TranslatorInterface $translator
|
||||
*
|
||||
* @Inject({VisitService::class, IpLocationResolver::class, "translator"})
|
||||
*/
|
||||
public function __construct(
|
||||
VisitServiceInterface $visitService,
|
||||
IpLocationResolverInterface $ipLocationResolver,
|
||||
TranslatorInterface $translator
|
||||
) {
|
||||
$this->visitService = $visitService;
|
||||
$this->ipLocationResolver = $ipLocationResolver;
|
||||
$this->translator = $translator;
|
||||
parent::__construct(null);
|
||||
}
|
||||
|
||||
public function configure()
|
||||
{
|
||||
$this->setName('visit:process')
|
||||
->setDescription(
|
||||
$this->translator->translate('Processes visits where location is not set yet')
|
||||
);
|
||||
}
|
||||
|
||||
public function execute(InputInterface $input, OutputInterface $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));
|
||||
if ($ipAddr === self::LOCALHOST) {
|
||||
$output->writeln(
|
||||
sprintf(' (<comment>%s</comment>)', $this->translator->translate('Ignored localhost address'))
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->ipLocationResolver->resolveIpLocation($ipAddr);
|
||||
$location = new VisitLocation();
|
||||
$location->exchangeArray($result);
|
||||
$visit->setVisitLocation($location);
|
||||
$this->visitService->saveVisit($visit);
|
||||
$output->writeln(sprintf(
|
||||
' (' . $this->translator->translate('Address located at "%s"') . ')',
|
||||
$location->getCityName()
|
||||
));
|
||||
} catch (WrongIpException $e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$output->writeln($this->translator->translate('Finished processing all IPs'));
|
||||
}
|
||||
}
|
||||
13
module/CLI/src/ConfigProvider.php
Normal file
13
module/CLI/src/ConfigProvider.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\CLI;
|
||||
|
||||
use Zend\Config\Factory;
|
||||
use Zend\Stdlib\Glob;
|
||||
|
||||
class ConfigProvider
|
||||
{
|
||||
public function __invoke()
|
||||
{
|
||||
return Factory::fromFiles(Glob::glob(__DIR__ . '/../config/{,*.}config.php', Glob::GLOB_BRACE));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace Acelaya\UrlShortener\CLI\Factory;
|
||||
namespace Shlinkio\Shlink\CLI\Factory;
|
||||
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Interop\Container\Exception\ContainerException;
|
||||
@@ -25,7 +25,7 @@ class ApplicationFactory implements FactoryInterface
|
||||
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
|
||||
{
|
||||
$config = $container->get('config')['cli'];
|
||||
$app = new CliApp();
|
||||
$app = new CliApp('Shlink', '1.0.0');
|
||||
|
||||
$commands = isset($config['commands']) ? $config['commands'] : [];
|
||||
foreach ($commands as $command) {
|
||||
62
module/CLI/test/Command/Api/DisableKeyCommandTest.php
Normal file
62
module/CLI/test/Command/Api/DisableKeyCommandTest.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\CLI\Command\Api;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Command\Api\DisableKeyCommand;
|
||||
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
||||
use Shlinkio\Shlink\Rest\Service\ApiKeyService;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
|
||||
class DisableKeyCommandTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var CommandTester
|
||||
*/
|
||||
protected $commandTester;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
protected $apiKeyService;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->apiKeyService = $this->prophesize(ApiKeyService::class);
|
||||
$command = new DisableKeyCommand($this->apiKeyService->reveal(), Translator::factory([]));
|
||||
$app = new Application();
|
||||
$app->add($command);
|
||||
$this->commandTester = new CommandTester($command);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function providedApiKeyIsDisabled()
|
||||
{
|
||||
$apiKey = 'abcd1234';
|
||||
$this->apiKeyService->disable($apiKey)->shouldBeCalledTimes(1);
|
||||
$this->commandTester->execute([
|
||||
'command' => 'api-key:disable',
|
||||
'apiKey' => $apiKey,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function errorIsReturnedIfServiceThrowsException()
|
||||
{
|
||||
$apiKey = 'abcd1234';
|
||||
$this->apiKeyService->disable($apiKey)->willThrow(InvalidArgumentException::class)
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$this->commandTester->execute([
|
||||
'command' => 'api-key:disable',
|
||||
'apiKey' => $apiKey,
|
||||
]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
$this->assertEquals('API key "abcd1234" does not exist.' . PHP_EOL, $output);
|
||||
}
|
||||
}
|
||||
55
module/CLI/test/Command/Api/GenerateKeyCommandTest.php
Normal file
55
module/CLI/test/Command/Api/GenerateKeyCommandTest.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\CLI\Command\Api;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Command\Api\GenerateKeyCommand;
|
||||
use Shlinkio\Shlink\Rest\Service\ApiKeyService;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
|
||||
class GenerateKeyCommandTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var CommandTester
|
||||
*/
|
||||
protected $commandTester;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
protected $apiKeyService;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->apiKeyService = $this->prophesize(ApiKeyService::class);
|
||||
$command = new GenerateKeyCommand($this->apiKeyService->reveal(), Translator::factory([]));
|
||||
$app = new Application();
|
||||
$app->add($command);
|
||||
$this->commandTester = new CommandTester($command);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function noExpirationDateIsDefinedIfNotProvided()
|
||||
{
|
||||
$this->apiKeyService->create(null)->shouldBeCalledTimes(1);
|
||||
$this->commandTester->execute([
|
||||
'command' => 'api-key:generate',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function expirationDateIsDefinedIfWhenProvided()
|
||||
{
|
||||
$this->apiKeyService->create(Argument::type(\DateTime::class))->shouldBeCalledTimes(1);
|
||||
$this->commandTester->execute([
|
||||
'command' => 'api-key:generate',
|
||||
'--expirationDate' => '2016-01-01',
|
||||
]);
|
||||
}
|
||||
}
|
||||
62
module/CLI/test/Command/Api/ListKeysCommandTest.php
Normal file
62
module/CLI/test/Command/Api/ListKeysCommandTest.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\CLI\Command\Api;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Command\Api\ListKeysCommand;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
use Shlinkio\Shlink\Rest\Service\ApiKeyService;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
|
||||
class ListKeysCommandTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var CommandTester
|
||||
*/
|
||||
protected $commandTester;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
protected $apiKeyService;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->apiKeyService = $this->prophesize(ApiKeyService::class);
|
||||
$command = new ListKeysCommand($this->apiKeyService->reveal(), Translator::factory([]));
|
||||
$app = new Application();
|
||||
$app->add($command);
|
||||
$this->commandTester = new CommandTester($command);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function ifEnabledOnlyIsNotProvidedEverythingIsListed()
|
||||
{
|
||||
$this->apiKeyService->listKeys(false)->willReturn([
|
||||
new ApiKey(),
|
||||
new ApiKey(),
|
||||
new ApiKey(),
|
||||
])->shouldBeCalledTimes(1);
|
||||
$this->commandTester->execute([
|
||||
'command' => 'api-key:list',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function ifEnabledOnlyIsProvidedOnlyThoseKeysAreListed()
|
||||
{
|
||||
$this->apiKeyService->listKeys(true)->willReturn([
|
||||
new ApiKey(),
|
||||
new ApiKey(),
|
||||
])->shouldBeCalledTimes(1);
|
||||
$this->commandTester->execute([
|
||||
'command' => 'api-key:list',
|
||||
'--enabledOnly' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\CLI\Command\Config;
|
||||
|
||||
use PHPUnit_Framework_TestCase as 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;
|
||||
|
||||
class GenerateCharsetCommandTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var CommandTester
|
||||
*/
|
||||
protected $commandTester;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$command = new GenerateCharsetCommand(Translator::factory([]));
|
||||
$app = new Application();
|
||||
$app->add($command);
|
||||
|
||||
$this->commandTester = new CommandTester($command);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function charactersAreGeneratedFromDefault()
|
||||
{
|
||||
$prefix = 'Character set: ';
|
||||
$prefixLength = strlen($prefix);
|
||||
|
||||
$this->commandTester->execute([
|
||||
'command' => 'config:generate-charset',
|
||||
]);
|
||||
$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);
|
||||
}
|
||||
|
||||
protected function orderStringLetters($string)
|
||||
{
|
||||
$letters = str_split($string);
|
||||
sort($letters);
|
||||
return implode('', $letters);
|
||||
}
|
||||
}
|
||||
70
module/CLI/test/Command/GenerateShortcodeCommandTest.php
Normal file
70
module/CLI/test/Command/GenerateShortcodeCommandTest.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\CLI\Command;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Command\Shortcode\GenerateShortcodeCommand;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
|
||||
class GenerateShortcodeCommandTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var CommandTester
|
||||
*/
|
||||
protected $commandTester;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
protected $urlShortener;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->urlShortener = $this->prophesize(UrlShortener::class);
|
||||
$command = new GenerateShortcodeCommand($this->urlShortener->reveal(), Translator::factory([]), [
|
||||
'schema' => 'http',
|
||||
'hostname' => 'foo.com'
|
||||
]);
|
||||
$app = new Application();
|
||||
$app->add($command);
|
||||
$this->commandTester = new CommandTester($command);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function properShortCodeIsCreatedIfLongUrlIsCorrect()
|
||||
{
|
||||
$this->urlShortener->urlToShortCode(Argument::any())->willReturn('abc123')
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$this->commandTester->execute([
|
||||
'command' => 'shortcode:generate',
|
||||
'longUrl' => 'http://domain.com/foo/bar'
|
||||
]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
$this->assertTrue(strpos($output, 'http://foo.com/abc123') > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function exceptionWhileParsingLongUrlOutputsError()
|
||||
{
|
||||
$this->urlShortener->urlToShortCode(Argument::any())->willThrow(new InvalidUrlException())
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$this->commandTester->execute([
|
||||
'command' => 'shortcode:generate',
|
||||
'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
|
||||
);
|
||||
}
|
||||
}
|
||||
91
module/CLI/test/Command/GetVisitsCommandTest.php
Normal file
91
module/CLI/test/Command/GetVisitsCommandTest.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\CLI\Command;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Command\Shortcode\GetVisitsCommand;
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
|
||||
class GetVisitsCommandTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var CommandTester
|
||||
*/
|
||||
protected $commandTester;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
protected $visitsTracker;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->visitsTracker = $this->prophesize(VisitsTrackerInterface::class);
|
||||
$command = new GetVisitsCommand($this->visitsTracker->reveal(), Translator::factory([]));
|
||||
$app = new Application();
|
||||
$app->add($command);
|
||||
$this->commandTester = new CommandTester($command);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function noDateFlagsTriesToListWithoutDateRange()
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->visitsTracker->info($shortCode, new DateRange(null, null))->willReturn([])
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$this->commandTester->execute([
|
||||
'command' => 'shortcode:visits',
|
||||
'shortCode' => $shortCode,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function providingDateFlagsTheListGetsFiltered()
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$startDate = '2016-01-01';
|
||||
$endDate = '2016-02-01';
|
||||
$this->visitsTracker->info($shortCode, new DateRange(new \DateTime($startDate), new \DateTime($endDate)))
|
||||
->willReturn([])
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$this->commandTester->execute([
|
||||
'command' => 'shortcode:visits',
|
||||
'shortCode' => $shortCode,
|
||||
'--startDate' => $startDate,
|
||||
'--endDate' => $endDate,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function outputIsProperlyGenerated()
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->visitsTracker->info($shortCode, Argument::any())->willReturn([
|
||||
(new Visit())->setReferer('foo')
|
||||
->setRemoteAddr('1.2.3.4')
|
||||
->setUserAgent('bar'),
|
||||
])->shouldBeCalledTimes(1);
|
||||
|
||||
$this->commandTester->execute([
|
||||
'command' => 'shortcode:visits',
|
||||
'shortCode' => $shortCode,
|
||||
]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
$this->assertTrue(strpos($output, 'foo') > 0);
|
||||
$this->assertTrue(strpos($output, '1.2.3.4') > 0);
|
||||
$this->assertTrue(strpos($output, 'bar') > 0);
|
||||
}
|
||||
}
|
||||
119
module/CLI/test/Command/ListShortcodesCommandTest.php
Normal file
119
module/CLI/test/Command/ListShortcodesCommandTest.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\CLI\Command;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
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;
|
||||
use Zend\Paginator\Paginator;
|
||||
|
||||
class ListShortcodesCommandTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var CommandTester
|
||||
*/
|
||||
protected $commandTester;
|
||||
/**
|
||||
* @var QuestionHelper
|
||||
*/
|
||||
protected $questionHelper;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
protected $shortUrlService;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->shortUrlService = $this->prophesize(ShortUrlServiceInterface::class);
|
||||
$app = new Application();
|
||||
$command = new ListShortcodesCommand($this->shortUrlService->reveal(), Translator::factory([]));
|
||||
$app->add($command);
|
||||
|
||||
$this->questionHelper = $command->getHelper('question');
|
||||
$this->commandTester = new CommandTester($command);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function noInputCallsListJustOnce()
|
||||
{
|
||||
$this->questionHelper->setInputStream($this->getInputStream('\n'));
|
||||
$this->shortUrlService->listShortUrls(1)->willReturn(new Paginator(new ArrayAdapter()))
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$this->commandTester->execute(['command' => 'shortcode:list']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function loadingMorePagesCallsListMoreTimes()
|
||||
{
|
||||
// The paginator will return more than one page for the first 3 times
|
||||
$data = [];
|
||||
for ($i = 0; $i < 30; $i++) {
|
||||
$data[] = new ShortUrl();
|
||||
}
|
||||
$data = array_chunk($data, 11);
|
||||
|
||||
$questionHelper = $this->questionHelper;
|
||||
$that = $this;
|
||||
$this->shortUrlService->listShortUrls(Argument::any())->will(function () use (&$data, $questionHelper, $that) {
|
||||
$questionHelper->setInputStream($that->getInputStream('y'));
|
||||
return new Paginator(new ArrayAdapter(array_shift($data)));
|
||||
})->shouldBeCalledTimes(3);
|
||||
|
||||
$this->commandTester->execute(['command' => 'shortcode:list']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function havingMorePagesButAnsweringNoCallsListJustOnce()
|
||||
{
|
||||
// The paginator will return more than one page
|
||||
$data = [];
|
||||
for ($i = 0; $i < 30; $i++) {
|
||||
$data[] = new ShortUrl();
|
||||
}
|
||||
|
||||
$this->questionHelper->setInputStream($this->getInputStream('n'));
|
||||
$this->shortUrlService->listShortUrls(Argument::any())->willReturn(new Paginator(new ArrayAdapter($data)))
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$this->commandTester->execute(['command' => 'shortcode:list']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function passingPageWillMakeListStartOnThatPage()
|
||||
{
|
||||
$page = 5;
|
||||
$this->questionHelper->setInputStream($this->getInputStream('\n'));
|
||||
$this->shortUrlService->listShortUrls($page)->willReturn(new Paginator(new ArrayAdapter()))
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$this->commandTester->execute([
|
||||
'command' => 'shortcode:list',
|
||||
'--page' => $page,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getInputStream($inputData)
|
||||
{
|
||||
$stream = fopen('php://memory', 'r+', false);
|
||||
fputs($stream, $inputData);
|
||||
rewind($stream);
|
||||
|
||||
return $stream;
|
||||
}
|
||||
}
|
||||
96
module/CLI/test/Command/ProcessVisitsCommandTest.php
Normal file
96
module/CLI/test/Command/ProcessVisitsCommandTest.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\CLI\Command;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Command\Visit\ProcessVisitsCommand;
|
||||
use Shlinkio\Shlink\Common\Service\IpLocationResolver;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Service\VisitService;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
|
||||
class ProcessVisitsCommandTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var CommandTester
|
||||
*/
|
||||
protected $commandTester;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
protected $visitService;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
protected $ipResolver;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->visitService = $this->prophesize(VisitService::class);
|
||||
$this->ipResolver = $this->prophesize(IpLocationResolver::class);
|
||||
$command = new ProcessVisitsCommand(
|
||||
$this->visitService->reveal(),
|
||||
$this->ipResolver->reveal(),
|
||||
Translator::factory([])
|
||||
);
|
||||
$app = new Application();
|
||||
$app->add($command);
|
||||
|
||||
$this->commandTester = new CommandTester($command);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function allReturnedVisitsIpsAreProcessed()
|
||||
{
|
||||
$visits = [
|
||||
(new Visit())->setRemoteAddr('1.2.3.4'),
|
||||
(new Visit())->setRemoteAddr('4.3.2.1'),
|
||||
(new Visit())->setRemoteAddr('12.34.56.78'),
|
||||
];
|
||||
$this->visitService->getUnlocatedVisits()->willReturn($visits)
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$this->visitService->saveVisit(Argument::any())->shouldBeCalledTimes(count($visits));
|
||||
$this->ipResolver->resolveIpLocation(Argument::any())->willReturn([])
|
||||
->shouldBeCalledTimes(count($visits));
|
||||
|
||||
$this->commandTester->execute([
|
||||
'command' => 'visit:process',
|
||||
]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
$this->assertTrue(strpos($output, 'Processing IP 1.2.3.4') === 0);
|
||||
$this->assertTrue(strpos($output, 'Processing IP 4.3.2.1') > 0);
|
||||
$this->assertTrue(strpos($output, 'Processing IP 12.34.56.78') > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function localhostAddressIsIgnored()
|
||||
{
|
||||
$visits = [
|
||||
(new Visit())->setRemoteAddr('1.2.3.4'),
|
||||
(new Visit())->setRemoteAddr('4.3.2.1'),
|
||||
(new Visit())->setRemoteAddr('12.34.56.78'),
|
||||
(new Visit())->setRemoteAddr('127.0.0.1'),
|
||||
(new Visit())->setRemoteAddr('127.0.0.1'),
|
||||
];
|
||||
$this->visitService->getUnlocatedVisits()->willReturn($visits)
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$this->visitService->saveVisit(Argument::any())->shouldBeCalledTimes(count($visits) - 2);
|
||||
$this->ipResolver->resolveIpLocation(Argument::any())->willReturn([])
|
||||
->shouldBeCalledTimes(count($visits) - 2);
|
||||
|
||||
$this->commandTester->execute([
|
||||
'command' => 'visit:process',
|
||||
]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
$this->assertTrue(strpos($output, 'Ignored localhost address') > 0);
|
||||
}
|
||||
}
|
||||
85
module/CLI/test/Command/ResolveUrlCommandTest.php
Normal file
85
module/CLI/test/Command/ResolveUrlCommandTest.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\CLI\Command;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Command\Shortcode\ResolveUrlCommand;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
|
||||
class ResolveUrlCommandTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var CommandTester
|
||||
*/
|
||||
protected $commandTester;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
protected $urlShortener;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->urlShortener = $this->prophesize(UrlShortener::class);
|
||||
$command = new ResolveUrlCommand($this->urlShortener->reveal(), Translator::factory([]));
|
||||
$app = new Application();
|
||||
$app->add($command);
|
||||
|
||||
$this->commandTester = new CommandTester($command);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function correctShortCodeResolvesUrl()
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$expectedUrl = 'http://domain.com/foo/bar';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willReturn($expectedUrl)
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$this->commandTester->execute([
|
||||
'command' => 'shortcode:parse',
|
||||
'shortCode' => $shortCode,
|
||||
]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
$this->assertEquals('Long URL: ' . $expectedUrl . PHP_EOL, $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function incorrectShortCodeOutputsErrorMessage()
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willReturn(null)
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$this->commandTester->execute([
|
||||
'command' => 'shortcode:parse',
|
||||
'shortCode' => $shortCode,
|
||||
]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
$this->assertEquals('No URL found for short code "' . $shortCode . '"' . PHP_EOL, $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function wrongShortCodeFormatOutputsErrorMessage()
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willThrow(new InvalidShortCodeException())
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$this->commandTester->execute([
|
||||
'command' => 'shortcode:parse',
|
||||
'shortCode' => $shortCode,
|
||||
]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
$this->assertEquals('Provided short code "' . $shortCode . '" has an invalid format.' . PHP_EOL, $output);
|
||||
}
|
||||
}
|
||||
30
module/CLI/test/ConfigProviderTest.php
Normal file
30
module/CLI/test/ConfigProviderTest.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\CLI;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Shlinkio\Shlink\CLI\ConfigProvider;
|
||||
|
||||
class ConfigProviderTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var ConfigProvider
|
||||
*/
|
||||
protected $configProvider;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->configProvider = new ConfigProvider();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function confiIsProperlyReturned()
|
||||
{
|
||||
$config = $this->configProvider->__invoke();
|
||||
|
||||
$this->assertArrayHasKey('cli', $config);
|
||||
$this->assertArrayHasKey('dependencies', $config);
|
||||
$this->assertArrayHasKey('translator', $config);
|
||||
}
|
||||
}
|
||||
60
module/CLI/test/Factory/ApplicationFactoryTest.php
Normal file
60
module/CLI/test/Factory/ApplicationFactoryTest.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\CLI\Factory;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Shlinkio\Shlink\CLI\Factory\ApplicationFactory;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
class ApplicationFactoryTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var ApplicationFactory
|
||||
*/
|
||||
protected $factory;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->factory = new ApplicationFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function serviceIsCreated()
|
||||
{
|
||||
$instance = $this->factory->__invoke($this->createServiceManager(), '');
|
||||
$this->assertInstanceOf(Application::class, $instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function allCommandsWhichAreServicesAreAdded()
|
||||
{
|
||||
$sm = $this->createServiceManager([
|
||||
'commands' => [
|
||||
'foo',
|
||||
'bar',
|
||||
'baz',
|
||||
],
|
||||
]);
|
||||
$sm->setService('foo', $this->prophesize(Command::class)->reveal());
|
||||
$sm->setService('baz', $this->prophesize(Command::class)->reveal());
|
||||
|
||||
/** @var Application $instance */
|
||||
$instance = $this->factory->__invoke($sm, '');
|
||||
$this->assertInstanceOf(Application::class, $instance);
|
||||
$this->assertCount(2, $instance->all());
|
||||
}
|
||||
|
||||
protected function createServiceManager($config = [])
|
||||
{
|
||||
return new ServiceManager(['services' => [
|
||||
'config' => [
|
||||
'cli' => $config,
|
||||
],
|
||||
]]);
|
||||
}
|
||||
}
|
||||
47
module/Common/config/dependencies.config.php
Normal file
47
module/Common/config/dependencies.config.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
use Acelaya\ZsmAnnotatedServices\Factory\V3\AnnotatedFactory;
|
||||
use Doctrine\Common\Cache\Cache;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Monolog\Logger;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Shlinkio\Shlink\Common\ErrorHandler;
|
||||
use Shlinkio\Shlink\Common\Factory\CacheFactory;
|
||||
use Shlinkio\Shlink\Common\Factory\EntityManagerFactory;
|
||||
use Shlinkio\Shlink\Common\Factory\LoggerFactory;
|
||||
use Shlinkio\Shlink\Common\Factory\TranslatorFactory;
|
||||
use Shlinkio\Shlink\Common\Middleware\LocaleMiddleware;
|
||||
use Shlinkio\Shlink\Common\Service\IpLocationResolver;
|
||||
use Shlinkio\Shlink\Common\Twig\Extension\TranslatorExtension;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||
|
||||
return [
|
||||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
EntityManager::class => EntityManagerFactory::class,
|
||||
GuzzleHttp\Client::class => InvokableFactory::class,
|
||||
Cache::class => CacheFactory::class,
|
||||
LoggerInterface::class => LoggerFactory::class,
|
||||
'Logger_Shlink' => LoggerFactory::class,
|
||||
|
||||
Translator::class => TranslatorFactory::class,
|
||||
TranslatorExtension::class => AnnotatedFactory::class,
|
||||
LocaleMiddleware::class => AnnotatedFactory::class,
|
||||
|
||||
IpLocationResolver::class => AnnotatedFactory::class,
|
||||
|
||||
ErrorHandler\ContentBasedErrorHandler::class => AnnotatedFactory::class,
|
||||
ErrorHandler\ErrorHandlerManager::class => ErrorHandler\ErrorHandlerManagerFactory::class,
|
||||
],
|
||||
'aliases' => [
|
||||
'em' => EntityManager::class,
|
||||
'httpClient' => GuzzleHttp\Client::class,
|
||||
'translator' => Translator::class,
|
||||
'logger' => LoggerInterface::class,
|
||||
Logger::class => LoggerInterface::class,
|
||||
AnnotatedFactory::CACHE_SERVICE => Cache::class,
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
22
module/Common/config/error-handler.config.php
Normal file
22
module/Common/config/error-handler.config.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
use Shlinkio\Shlink\Common\ErrorHandler\ContentBasedErrorHandler;
|
||||
use Zend\Expressive\Container\TemplatedErrorHandlerFactory;
|
||||
use Zend\Stratigility\FinalHandler;
|
||||
|
||||
return [
|
||||
|
||||
'error_handler' => [
|
||||
'plugins' => [
|
||||
'invokables' => [
|
||||
'text/plain' => FinalHandler::class,
|
||||
],
|
||||
'factories' => [
|
||||
ContentBasedErrorHandler::DEFAULT_CONTENT => TemplatedErrorHandlerFactory::class,
|
||||
],
|
||||
'aliases' => [
|
||||
'application/xhtml+xml' => ContentBasedErrorHandler::DEFAULT_CONTENT,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
15
module/Common/config/middleware-pipeline.config.php
Normal file
15
module/Common/config/middleware-pipeline.config.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
use Shlinkio\Shlink\Common\Middleware;
|
||||
|
||||
return [
|
||||
|
||||
'middleware_pipeline' => [
|
||||
'pre-routing' => [
|
||||
'middleware' => [
|
||||
Middleware\LocaleMiddleware::class,
|
||||
],
|
||||
'priority' => 5,
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
12
module/Common/config/templates.config.php
Normal file
12
module/Common/config/templates.config.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
use Shlinkio\Shlink\Common\Twig\Extension\TranslatorExtension;
|
||||
|
||||
return [
|
||||
|
||||
'twig' => [
|
||||
'extensions' => [
|
||||
TranslatorExtension::class,
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
36
module/Common/functions/functions.php
Normal file
36
module/Common/functions/functions.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
if (! function_exists('env')) {
|
||||
/**
|
||||
* Gets the value of an environment variable. Supports boolean, empty and null.
|
||||
* This is basically Laravel's env helper
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
* @link https://github.com/laravel/framework/blob/5.2/src/Illuminate/Foundation/helpers.php#L369
|
||||
*/
|
||||
function env($key, $default = null)
|
||||
{
|
||||
$value = getenv($key);
|
||||
if ($value === false) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
switch (strtolower($value)) {
|
||||
case 'true':
|
||||
case '(true)':
|
||||
return true;
|
||||
case 'false':
|
||||
case '(false)':
|
||||
return false;
|
||||
case 'empty':
|
||||
case '(empty)':
|
||||
return '';
|
||||
case 'null':
|
||||
case '(null)':
|
||||
return null;
|
||||
}
|
||||
|
||||
return trim($value);
|
||||
}
|
||||
}
|
||||
13
module/Common/src/ConfigProvider.php
Normal file
13
module/Common/src/ConfigProvider.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Common;
|
||||
|
||||
use Zend\Config\Factory;
|
||||
use Zend\Stdlib\Glob;
|
||||
|
||||
class ConfigProvider
|
||||
{
|
||||
public function __invoke()
|
||||
{
|
||||
return Factory::fromFiles(Glob::glob(__DIR__ . '/../config/{,*.}config.php', Glob::GLOB_BRACE));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace Acelaya\UrlShortener\Entity;
|
||||
namespace Shlinkio\Shlink\Common\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
76
module/Common/src/ErrorHandler/ContentBasedErrorHandler.php
Normal file
76
module/Common/src/ErrorHandler/ContentBasedErrorHandler.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Common\ErrorHandler;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
||||
|
||||
class ContentBasedErrorHandler implements ErrorHandlerInterface
|
||||
{
|
||||
const DEFAULT_CONTENT = 'text/html';
|
||||
|
||||
/**
|
||||
* @var ErrorHandlerManagerInterface
|
||||
*/
|
||||
private $errorHandlerManager;
|
||||
|
||||
/**
|
||||
* ContentBasedErrorHandler constructor.
|
||||
* @param ErrorHandlerManagerInterface|ErrorHandlerManager $errorHandlerManager
|
||||
*
|
||||
* @Inject({ErrorHandlerManager::class})
|
||||
*/
|
||||
public function __construct(ErrorHandlerManagerInterface $errorHandlerManager)
|
||||
{
|
||||
$this->errorHandlerManager = $errorHandlerManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Final handler for an application.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param null|mixed $err
|
||||
* @return Response
|
||||
*/
|
||||
public function __invoke(Request $request, Response $response, $err = null)
|
||||
{
|
||||
// Try to get an error handler for provided request accepted type
|
||||
$errorHandler = $this->resolveErrorHandlerFromAcceptHeader($request);
|
||||
return $errorHandler($request, $response, $err);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to resolve
|
||||
*
|
||||
* @param Request $request
|
||||
* @return callable
|
||||
*/
|
||||
protected function resolveErrorHandlerFromAcceptHeader(Request $request)
|
||||
{
|
||||
// Try to find an error handler for one of the accepted content types
|
||||
$accepts = $request->hasHeader('Accept') ? $request->getHeaderLine('Accept') : self::DEFAULT_CONTENT;
|
||||
$accepts = explode(',', $accepts);
|
||||
foreach ($accepts as $accept) {
|
||||
if (! $this->errorHandlerManager->has($accept)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $this->errorHandlerManager->get($accept);
|
||||
}
|
||||
|
||||
// If it wasn't possible to find an error handler for accepted content type, use default one if registered
|
||||
if ($this->errorHandlerManager->has(self::DEFAULT_CONTENT)) {
|
||||
return $this->errorHandlerManager->get(self::DEFAULT_CONTENT);
|
||||
}
|
||||
|
||||
// It wasn't possible to find an error handler
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'It wasn\'t possible to find an error handler for ["%s"] content types. '
|
||||
. 'Make sure you have registered at least the default "%s" content type',
|
||||
implode('", "', $accepts),
|
||||
self::DEFAULT_CONTENT
|
||||
));
|
||||
}
|
||||
}
|
||||
18
module/Common/src/ErrorHandler/ErrorHandlerInterface.php
Normal file
18
module/Common/src/ErrorHandler/ErrorHandlerInterface.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Common\ErrorHandler;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
|
||||
interface ErrorHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Final handler for an application.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param null|mixed $err
|
||||
* @return Response
|
||||
*/
|
||||
public function __invoke(Request $request, Response $response, $err = null);
|
||||
}
|
||||
21
module/Common/src/ErrorHandler/ErrorHandlerManager.php
Normal file
21
module/Common/src/ErrorHandler/ErrorHandlerManager.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Common\ErrorHandler;
|
||||
|
||||
use Zend\ServiceManager\AbstractPluginManager;
|
||||
use Zend\ServiceManager\Exception\InvalidServiceException;
|
||||
|
||||
class ErrorHandlerManager extends AbstractPluginManager implements ErrorHandlerManagerInterface
|
||||
{
|
||||
public function validate($instance)
|
||||
{
|
||||
if (is_callable($instance)) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new InvalidServiceException(sprintf(
|
||||
'Only callables are valid plugins for "%s". "%s" provided',
|
||||
__CLASS__,
|
||||
is_object($instance) ? get_class($instance) : gettype($instance)
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Common\ErrorHandler;
|
||||
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Interop\Container\Exception\ContainerException;
|
||||
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
||||
use Zend\ServiceManager\Factory\FactoryInterface;
|
||||
|
||||
class ErrorHandlerManagerFactory 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)
|
||||
{
|
||||
$config = $container->get('config')['error_handler'];
|
||||
$plugins = isset($config['plugins']) ? $config['plugins'] : [];
|
||||
return new ErrorHandlerManager($container, $plugins);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Common\ErrorHandler;
|
||||
|
||||
use Interop\Container\ContainerInterface;
|
||||
|
||||
interface ErrorHandlerManagerInterface extends ContainerInterface
|
||||
{
|
||||
|
||||
}
|
||||
6
module/Common/src/Exception/ExceptionInterface.php
Normal file
6
module/Common/src/Exception/ExceptionInterface.php
Normal file
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Common\Exception;
|
||||
|
||||
interface ExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace Acelaya\UrlShortener\Exception;
|
||||
namespace Shlinkio\Shlink\Common\Exception;
|
||||
|
||||
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
|
||||
{
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace Acelaya\UrlShortener\Exception;
|
||||
namespace Shlinkio\Shlink\Common\Exception;
|
||||
|
||||
class RuntimeException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
10
module/Common/src/Exception/WrongIpException.php
Normal file
10
module/Common/src/Exception/WrongIpException.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Common\Exception;
|
||||
|
||||
class WrongIpException extends RuntimeException
|
||||
{
|
||||
public static function fromIpAddress($ipAddress, \Exception $prev = null)
|
||||
{
|
||||
return new self(sprintf('Provided IP "%s" is invalid', $ipAddress), 0, $prev);
|
||||
}
|
||||
}
|
||||
81
module/Common/src/Factory/CacheFactory.php
Normal file
81
module/Common/src/Factory/CacheFactory.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Common\Factory;
|
||||
|
||||
use Doctrine\Common\Cache;
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Interop\Container\Exception\ContainerException;
|
||||
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
||||
use Zend\ServiceManager\Factory\FactoryInterface;
|
||||
|
||||
class CacheFactory implements FactoryInterface
|
||||
{
|
||||
const VALID_CACHE_ADAPTERS = [
|
||||
Cache\ApcuCache::class,
|
||||
Cache\ArrayCache::class,
|
||||
Cache\FilesystemCache::class,
|
||||
Cache\PhpFileCache::class,
|
||||
Cache\MemcachedCache::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
// Try to get the adapter from config
|
||||
$config = $container->get('config');
|
||||
if (isset($config['cache'])
|
||||
&& isset($config['cache']['adapter'])
|
||||
&& in_array($config['cache']['adapter'], self::VALID_CACHE_ADAPTERS)
|
||||
) {
|
||||
return $this->resolveCacheAdapter($config['cache']);
|
||||
}
|
||||
|
||||
// If the adapter has not been set in config, create one based on environment
|
||||
return env('APP_ENV', 'pro') === 'pro' ? new Cache\ApcuCache() : new Cache\ArrayCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $cacheConfig
|
||||
* @return Cache\Cache
|
||||
*/
|
||||
protected function resolveCacheAdapter(array $cacheConfig)
|
||||
{
|
||||
switch ($cacheConfig['adapter']) {
|
||||
case Cache\ArrayCache::class:
|
||||
case Cache\ApcuCache::class:
|
||||
return new $cacheConfig['adapter']();
|
||||
case Cache\FilesystemCache::class:
|
||||
case Cache\PhpFileCache::class:
|
||||
return new $cacheConfig['adapter']($cacheConfig['options']['dir']);
|
||||
case Cache\MemcachedCache::class:
|
||||
$memcached = new \Memcached();
|
||||
$servers = isset($cacheConfig['options']['servers']) ? $cacheConfig['options']['servers'] : [];
|
||||
|
||||
foreach ($servers as $server) {
|
||||
if (! isset($server['host'])) {
|
||||
continue;
|
||||
}
|
||||
$port = isset($server['port']) ? intval($server['port']) : 11211;
|
||||
|
||||
$memcached->addServer($server['host'], $port);
|
||||
}
|
||||
|
||||
$cache = new Cache\MemcachedCache();
|
||||
$cache->setMemcached($memcached);
|
||||
return $cache;
|
||||
default:
|
||||
return new Cache\ArrayCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace Acelaya\UrlShortener\Factory;
|
||||
namespace Shlinkio\Shlink\Common\Factory;
|
||||
|
||||
use Doctrine\Common\Cache\ArrayCache;
|
||||
use Doctrine\Common\Cache\Cache;
|
||||
@@ -30,12 +30,14 @@ class EntityManagerFactory implements FactoryInterface
|
||||
$globalConfig = $container->get('config');
|
||||
$isDevMode = isset($globalConfig['debug']) ? ((bool) $globalConfig['debug']) : false;
|
||||
$cache = $container->has(Cache::class) ? $container->get(Cache::class) : new ArrayCache();
|
||||
$dbConfig = isset($globalConfig['database']) ? $globalConfig['database'] : [];
|
||||
$emConfig = isset($globalConfig['entity_manager']) ? $globalConfig['entity_manager'] : [];
|
||||
$connecitonConfig = isset($emConfig['connection']) ? $emConfig['connection'] : [];
|
||||
$ormConfig = isset($emConfig['orm']) ? $emConfig['orm'] : [];
|
||||
|
||||
return EntityManager::create($dbConfig, Setup::createAnnotationMetadataConfiguration(
|
||||
['src/Entity'],
|
||||
return EntityManager::create($connecitonConfig, Setup::createAnnotationMetadataConfiguration(
|
||||
isset($ormConfig['entities_paths']) ? $ormConfig['entities_paths'] : [],
|
||||
$isDevMode,
|
||||
'data/proxies',
|
||||
isset($ormConfig['proxies_dir']) ? $ormConfig['proxies_dir'] : null,
|
||||
$cache,
|
||||
false
|
||||
));
|
||||
39
module/Common/src/Factory/LoggerFactory.php
Normal file
39
module/Common/src/Factory/LoggerFactory.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Common\Factory;
|
||||
|
||||
use Cascade\Cascade;
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Interop\Container\Exception\ContainerException;
|
||||
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
||||
use Zend\ServiceManager\Factory\FactoryInterface;
|
||||
|
||||
class LoggerFactory 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)
|
||||
{
|
||||
$config = $container->has('config') ? $container->get('config') : [];
|
||||
Cascade::fileConfig(isset($config['logger']) ? $config['logger'] : ['loggers' => []]);
|
||||
|
||||
// Compose requested logger name
|
||||
$loggerName = isset($options) & isset($options['logger_name']) ? $options['logger_name'] : 'Logger';
|
||||
$nameParts = explode('_', $requestedName);
|
||||
if (count($nameParts) > 1) {
|
||||
$loggerName = $nameParts[1];
|
||||
}
|
||||
|
||||
return Cascade::getLogger($loggerName);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,14 @@
|
||||
<?php
|
||||
namespace Acelaya\UrlShortener\Factory;
|
||||
namespace Shlinkio\Shlink\Common\Factory;
|
||||
|
||||
use Doctrine\Common\Cache\ApcuCache;
|
||||
use Doctrine\Common\Cache\ArrayCache;
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Interop\Container\Exception\ContainerException;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
||||
use Zend\ServiceManager\Factory\FactoryInterface;
|
||||
|
||||
class CacheFactory implements FactoryInterface
|
||||
class TranslatorFactory implements FactoryInterface
|
||||
{
|
||||
/**
|
||||
* Create an object
|
||||
@@ -25,6 +24,7 @@ class CacheFactory implements FactoryInterface
|
||||
*/
|
||||
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
|
||||
{
|
||||
return getenv('APP_ENV') === 'pro' ? new ApcuCache() : new ArrayCache();
|
||||
$config = $container->get('config');
|
||||
return Translator::factory(isset($config['translator']) ? $config['translator'] : []);
|
||||
}
|
||||
}
|
||||
82
module/Common/src/Middleware/LocaleMiddleware.php
Normal file
82
module/Common/src/Middleware/LocaleMiddleware.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Common\Middleware;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
use Zend\Stratigility\MiddlewareInterface;
|
||||
|
||||
class LocaleMiddleware implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* @var Translator
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
/**
|
||||
* LocaleMiddleware constructor.
|
||||
* @param Translator $translator
|
||||
*
|
||||
* @Inject({"translator"})
|
||||
*/
|
||||
public function __construct(Translator $translator)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an incoming request and/or response.
|
||||
*
|
||||
* Accepts a server-side request and a response instance, and does
|
||||
* something with them.
|
||||
*
|
||||
* If the response is not complete and/or further processing would not
|
||||
* interfere with the work done in the middleware, or if the middleware
|
||||
* wants to delegate to another process, it can use the `$out` callable
|
||||
* if present.
|
||||
*
|
||||
* If the middleware does not return a value, execution of the current
|
||||
* request is considered complete, and the response instance provided will
|
||||
* be considered the response to return.
|
||||
*
|
||||
* Alternately, the middleware may return a response instance.
|
||||
*
|
||||
* Often, middleware will `return $out();`, with the assumption that a
|
||||
* later middleware will return a response.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param null|callable $out
|
||||
* @return null|Response
|
||||
*/
|
||||
public function __invoke(Request $request, Response $response, callable $out = null)
|
||||
{
|
||||
if (! $request->hasHeader('Accept-Language')) {
|
||||
return $out($request, $response);
|
||||
}
|
||||
|
||||
$locale = $request->getHeaderLine('Accept-Language');
|
||||
$this->translator->setLocale($this->normalizeLocale($locale));
|
||||
return $out($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $locale
|
||||
* @return string
|
||||
*/
|
||||
protected function normalizeLocale($locale)
|
||||
{
|
||||
$parts = explode('_', $locale);
|
||||
if (count($parts) > 1) {
|
||||
return $parts[0];
|
||||
}
|
||||
|
||||
$parts = explode('-', $locale);
|
||||
if (count($parts) > 1) {
|
||||
return $parts[0];
|
||||
}
|
||||
|
||||
return $locale;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace Acelaya\UrlShortener\Paginator\Adapter;
|
||||
namespace Shlinkio\Shlink\Common\Paginator\Adapter;
|
||||
|
||||
use Acelaya\UrlShortener\Repository\PaginableRepositoryInterface;
|
||||
use Shlinkio\Shlink\Common\Repository\PaginableRepositoryInterface;
|
||||
use Zend\Paginator\Adapter\AdapterInterface;
|
||||
|
||||
class PaginableRepositoryAdapter implements AdapterInterface
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace Acelaya\UrlShortener\Paginator\Util;
|
||||
namespace Shlinkio\Shlink\Common\Paginator\Util;
|
||||
|
||||
use Zend\Paginator\Paginator;
|
||||
use Zend\Stdlib\ArrayUtils;
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace Acelaya\UrlShortener\Repository;
|
||||
namespace Shlinkio\Shlink\Common\Repository;
|
||||
|
||||
interface PaginableRepositoryInterface
|
||||
{
|
||||
35
module/Common/src/Response/QrCodeResponse.php
Normal file
35
module/Common/src/Response/QrCodeResponse.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Common\Response;
|
||||
|
||||
use Endroid\QrCode\QrCode;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Diactoros\Stream;
|
||||
|
||||
class QrCodeResponse extends Response
|
||||
{
|
||||
use Response\InjectContentTypeTrait;
|
||||
|
||||
public function __construct(QrCode $qrCode, $status = 200, array $headers = [])
|
||||
{
|
||||
parent::__construct(
|
||||
$this->createBody($qrCode),
|
||||
$status,
|
||||
$this->injectContentType($qrCode->getContentType(), $headers)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the message body.
|
||||
*
|
||||
* @param QrCode $qrCode
|
||||
* @return StreamInterface
|
||||
*/
|
||||
private function createBody(QrCode $qrCode)
|
||||
{
|
||||
$body = new Stream('php://temp', 'wb+');
|
||||
$body->write($qrCode->get());
|
||||
$body->rewind();
|
||||
return $body;
|
||||
}
|
||||
}
|
||||
42
module/Common/src/Service/IpLocationResolver.php
Normal file
42
module/Common/src/Service/IpLocationResolver.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Common\Service;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
||||
|
||||
class IpLocationResolver implements IpLocationResolverInterface
|
||||
{
|
||||
const SERVICE_PATTERN = 'http://freegeoip.net/json/%s';
|
||||
|
||||
/**
|
||||
* @var Client
|
||||
*/
|
||||
private $httpClient;
|
||||
|
||||
/**
|
||||
* IpLocationResolver constructor.
|
||||
* @param Client $httpClient
|
||||
*
|
||||
* @Inject({"httpClient"})
|
||||
*/
|
||||
public function __construct(Client $httpClient)
|
||||
{
|
||||
$this->httpClient = $httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $ipAddress
|
||||
* @return array
|
||||
*/
|
||||
public function resolveIpLocation($ipAddress)
|
||||
{
|
||||
try {
|
||||
$response = $this->httpClient->get(sprintf(self::SERVICE_PATTERN, $ipAddress));
|
||||
return json_decode($response->getBody(), true);
|
||||
} catch (GuzzleException $e) {
|
||||
throw WrongIpException::fromIpAddress($ipAddress, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
module/Common/src/Service/IpLocationResolverInterface.php
Normal file
11
module/Common/src/Service/IpLocationResolverInterface.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Common\Service;
|
||||
|
||||
interface IpLocationResolverInterface
|
||||
{
|
||||
/**
|
||||
* @param $ipAddress
|
||||
* @return array
|
||||
*/
|
||||
public function resolveIpLocation($ipAddress);
|
||||
}
|
||||
75
module/Common/src/Twig/Extension/TranslatorExtension.php
Normal file
75
module/Common/src/Twig/Extension/TranslatorExtension.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Common\Twig\Extension;
|
||||
|
||||
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
|
||||
class TranslatorExtension extends \Twig_Extension implements TranslatorInterface
|
||||
{
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
/**
|
||||
* TranslatorExtension constructor.
|
||||
* @param TranslatorInterface $translator
|
||||
*
|
||||
* @Inject({"translator"})
|
||||
*/
|
||||
public function __construct(TranslatorInterface $translator)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the extension.
|
||||
*
|
||||
* @return string The extension name
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return __CLASS__;
|
||||
}
|
||||
|
||||
public function getFunctions()
|
||||
{
|
||||
return [
|
||||
new \Twig_SimpleFunction('translate', [$this, 'translate']),
|
||||
new \Twig_SimpleFunction('translate_plural', [$this, 'translatePlural']),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate a message.
|
||||
*
|
||||
* @param string $message
|
||||
* @param string $textDomain
|
||||
* @param string $locale
|
||||
* @return string
|
||||
*/
|
||||
public function translate($message, $textDomain = 'default', $locale = null)
|
||||
{
|
||||
return $this->translator->translate($message, $textDomain, $locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate a plural message.
|
||||
*
|
||||
* @param string $singular
|
||||
* @param string $plural
|
||||
* @param int $number
|
||||
* @param string $textDomain
|
||||
* @param string|null $locale
|
||||
* @return string
|
||||
*/
|
||||
public function translatePlural(
|
||||
$singular,
|
||||
$plural,
|
||||
$number,
|
||||
$textDomain = 'default',
|
||||
$locale = null
|
||||
) {
|
||||
$this->translator->translatePlural($singular, $plural, $number, $textDomain, $locale);
|
||||
}
|
||||
}
|
||||
44
module/Common/src/Util/DateRange.php
Normal file
44
module/Common/src/Util/DateRange.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
namespace Shlinkio\Shlink\Common\Util;
|
||||
|
||||
class DateRange
|
||||
{
|
||||
/**
|
||||
* @var \DateTimeInterface
|
||||
*/
|
||||
private $startDate;
|
||||
/**
|
||||
* @var \DateTimeInterface
|
||||
*/
|
||||
private $endDate;
|
||||
|
||||
public function __construct(\DateTimeInterface $startDate = null, \DateTimeInterface $endDate = null)
|
||||
{
|
||||
$this->startDate = $startDate;
|
||||
$this->endDate = $endDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTimeInterface
|
||||
*/
|
||||
public function getStartDate()
|
||||
{
|
||||
return $this->startDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTimeInterface
|
||||
*/
|
||||
public function getEndDate()
|
||||
{
|
||||
return $this->endDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmpty()
|
||||
{
|
||||
return is_null($this->startDate) && is_null($this->endDate);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace Acelaya\UrlShortener\Util;
|
||||
namespace Shlinkio\Shlink\Common\Util;
|
||||
|
||||
trait StringUtilsTrait
|
||||
{
|
||||
31
module/Common/test/ConfigProviderTest.php
Normal file
31
module/Common/test/ConfigProviderTest.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Common;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Shlinkio\Shlink\Common\ConfigProvider;
|
||||
|
||||
class ConfigProviderTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var ConfigProvider
|
||||
*/
|
||||
protected $configProvider;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->configProvider = new ConfigProvider();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function configIsReturned()
|
||||
{
|
||||
$config = $this->configProvider->__invoke();
|
||||
|
||||
$this->assertArrayHasKey('error_handler', $config);
|
||||
$this->assertArrayHasKey('middleware_pipeline', $config);
|
||||
$this->assertArrayHasKey('dependencies', $config);
|
||||
$this->assertArrayHasKey('twig', $config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Common\ErrorHandler;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Shlinkio\Shlink\Common\ErrorHandler\ContentBasedErrorHandler;
|
||||
use Shlinkio\Shlink\Common\ErrorHandler\ErrorHandlerManager;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
class ContentBasedErrorHandlerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var ContentBasedErrorHandler
|
||||
*/
|
||||
protected $errorHandler;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->errorHandler = new ContentBasedErrorHandler(new ErrorHandlerManager(new ServiceManager(), [
|
||||
'factories' => [
|
||||
'text/html' => [$this, 'factory'],
|
||||
'application/json' => [$this, 'factory'],
|
||||
],
|
||||
]));
|
||||
}
|
||||
|
||||
public function factory($container, $name)
|
||||
{
|
||||
return function () use ($name) {
|
||||
return $name;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function correctAcceptHeaderValueInvokesErrorHandler()
|
||||
{
|
||||
$request = ServerRequestFactory::fromGlobals()->withHeader('Accept', 'foo/bar,application/json');
|
||||
$result = $this->errorHandler->__invoke($request, new Response());
|
||||
$this->assertEquals('application/json', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function defaultContentTypeIsUsedWhenNoAcceptHeaderisPresent()
|
||||
{
|
||||
$request = ServerRequestFactory::fromGlobals();
|
||||
$result = $this->errorHandler->__invoke($request, new Response());
|
||||
$this->assertEquals('text/html', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function defaultContentTypeIsUsedWhenAcceptedContentIsNotSupported()
|
||||
{
|
||||
$request = ServerRequestFactory::fromGlobals()->withHeader('Accept', 'foo/bar,text/xml');
|
||||
$result = $this->errorHandler->__invoke($request, new Response());
|
||||
$this->assertEquals('text/html', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @expectedException \Shlinkio\Shlink\Common\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function ifNoErrorHandlerIsFoundAnExceptionIsThrown()
|
||||
{
|
||||
$this->errorHandler = new ContentBasedErrorHandler(new ErrorHandlerManager(new ServiceManager(), []));
|
||||
$request = ServerRequestFactory::fromGlobals()->withHeader('Accept', 'foo/bar,text/xml');
|
||||
$result = $this->errorHandler->__invoke($request, new Response());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Common\ErrorHandler;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Shlinkio\Shlink\Common\ErrorHandler\ErrorHandlerManager;
|
||||
use Shlinkio\Shlink\Common\ErrorHandler\ErrorHandlerManagerFactory;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
class ErrorHandlerManagerFactoryTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var ErrorHandlerManagerFactory
|
||||
*/
|
||||
protected $factory;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->factory = new ErrorHandlerManagerFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function serviceIsCreated()
|
||||
{
|
||||
$instance = $this->factory->__invoke(new ServiceManager(['services' => [
|
||||
'config' => [
|
||||
'error_handler' => [
|
||||
'plugins' => [],
|
||||
],
|
||||
],
|
||||
]]), '');
|
||||
$this->assertInstanceOf(ErrorHandlerManager::class, $instance);
|
||||
}
|
||||
}
|
||||
45
module/Common/test/ErrorHandler/ErrorHandlerManagerTest.php
Normal file
45
module/Common/test/ErrorHandler/ErrorHandlerManagerTest.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Common\ErrorHandler;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Shlinkio\Shlink\Common\ErrorHandler\ErrorHandlerManager;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
class ErrorHandlerManagerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var ErrorHandlerManager
|
||||
*/
|
||||
protected $pluginManager;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->pluginManager = new ErrorHandlerManager(new ServiceManager(), [
|
||||
'services' => [
|
||||
'foo' => function () {
|
||||
},
|
||||
],
|
||||
'invokables' => [
|
||||
'invalid' => \stdClass::class,
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function callablesAreReturned()
|
||||
{
|
||||
$instance = $this->pluginManager->get('foo');
|
||||
$this->assertInstanceOf(\Closure::class, $instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @expectedException \Zend\ServiceManager\Exception\InvalidServiceException
|
||||
*/
|
||||
public function nonCallablesThrowException()
|
||||
{
|
||||
$this->pluginManager->get('invalid');
|
||||
}
|
||||
}
|
||||
114
module/Common/test/Factory/CacheFactoryTest.php
Normal file
114
module/Common/test/Factory/CacheFactoryTest.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Common\Factory;
|
||||
|
||||
use Doctrine\Common\Cache\ApcuCache;
|
||||
use Doctrine\Common\Cache\ArrayCache;
|
||||
use Doctrine\Common\Cache\FilesystemCache;
|
||||
use Doctrine\Common\Cache\MemcachedCache;
|
||||
use Doctrine\Common\Cache\RedisCache;
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Shlinkio\Shlink\Common\Factory\CacheFactory;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
class CacheFactoryTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var CacheFactory
|
||||
*/
|
||||
protected $factory;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->factory = new CacheFactory();
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass()
|
||||
{
|
||||
putenv('APP_ENV');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function productionReturnsApcAdapter()
|
||||
{
|
||||
putenv('APP_ENV=pro');
|
||||
$instance = $this->factory->__invoke($this->createSM(), '');
|
||||
$this->assertInstanceOf(ApcuCache::class, $instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function developmentReturnsArrayAdapter()
|
||||
{
|
||||
putenv('APP_ENV=dev');
|
||||
$instance = $this->factory->__invoke($this->createSM(), '');
|
||||
$this->assertInstanceOf(ArrayCache::class, $instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function adapterDefinedInConfigIgnoresEnvironment()
|
||||
{
|
||||
putenv('APP_ENV=pro');
|
||||
$instance = $this->factory->__invoke($this->createSM(ArrayCache::class), '');
|
||||
$this->assertInstanceOf(ArrayCache::class, $instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function invalidAdapterDefinedInConfigFallbacksToEnvironment()
|
||||
{
|
||||
putenv('APP_ENV=pro');
|
||||
$instance = $this->factory->__invoke($this->createSM(RedisCache::class), '');
|
||||
$this->assertInstanceOf(ApcuCache::class, $instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function filesystemCacheAdaptersReadDirOption()
|
||||
{
|
||||
$dir = sys_get_temp_dir();
|
||||
/** @var FilesystemCache $instance */
|
||||
$instance = $this->factory->__invoke($this->createSM(FilesystemCache::class, ['dir' => $dir]), '');
|
||||
$this->assertInstanceOf(FilesystemCache::class, $instance);
|
||||
$this->assertEquals($dir, $instance->getDirectory());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function memcachedCacheAdaptersReadServersOption()
|
||||
{
|
||||
$servers = [
|
||||
[
|
||||
'host' => '1.2.3.4',
|
||||
'port' => 123
|
||||
],
|
||||
[
|
||||
'host' => '4.3.2.1',
|
||||
'port' => 321
|
||||
],
|
||||
];
|
||||
/** @var MemcachedCache $instance */
|
||||
$instance = $this->factory->__invoke($this->createSM(MemcachedCache::class, ['servers' => $servers]), '');
|
||||
$this->assertInstanceOf(MemcachedCache::class, $instance);
|
||||
$this->assertEquals(count($servers), count($instance->getMemcached()->getServerList()));
|
||||
}
|
||||
|
||||
private function createSM($cacheAdapter = null, array $options = [])
|
||||
{
|
||||
return new ServiceManager(['services' => [
|
||||
'config' => isset($cacheAdapter) ? [
|
||||
'cache' => [
|
||||
'adapter' => $cacheAdapter,
|
||||
'options' => $options,
|
||||
],
|
||||
] : [],
|
||||
]]);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
namespace AcelayaTest\UrlShortener\Factory;
|
||||
namespace ShlinkioTest\Shlink\Common\Factory;
|
||||
|
||||
use Acelaya\UrlShortener\Factory\EntityManagerFactory;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Shlinkio\Shlink\Common\Factory\EntityManagerFactory;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
class EntityManagerFactoryTest extends TestCase
|
||||
@@ -26,8 +26,10 @@ class EntityManagerFactoryTest extends TestCase
|
||||
$sm = new ServiceManager(['services' => [
|
||||
'config' => [
|
||||
'debug' => true,
|
||||
'database' => [
|
||||
'driver' => 'pdo_sqlite',
|
||||
'entity_manager' => [
|
||||
'connection' => [
|
||||
'driver' => 'pdo_sqlite',
|
||||
],
|
||||
],
|
||||
],
|
||||
]]);
|
||||
54
module/Common/test/Factory/LoggerFactoryTest.php
Normal file
54
module/Common/test/Factory/LoggerFactoryTest.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Common\Factory;
|
||||
|
||||
use Monolog\Logger;
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Shlinkio\Shlink\Common\Factory\LoggerFactory;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
class LoggerFactoryTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var LoggerFactory
|
||||
*/
|
||||
protected $factory;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->factory = new LoggerFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function serviceIsCreated()
|
||||
{
|
||||
/** @var Logger $instance */
|
||||
$instance = $this->factory->__invoke(new ServiceManager(), '');
|
||||
$this->assertInstanceOf(LoggerInterface::class, $instance);
|
||||
$this->assertEquals('Logger', $instance->getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function nameIsSetFromOptions()
|
||||
{
|
||||
/** @var Logger $instance */
|
||||
$instance = $this->factory->__invoke(new ServiceManager(), '', ['logger_name' => 'Foo']);
|
||||
$this->assertInstanceOf(LoggerInterface::class, $instance);
|
||||
$this->assertEquals('Foo', $instance->getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function serviceNameOverwritesOptionsLoggerName()
|
||||
{
|
||||
/** @var Logger $instance */
|
||||
$instance = $this->factory->__invoke(new ServiceManager(), 'Logger_Shlink', ['logger_name' => 'Foo']);
|
||||
$this->assertInstanceOf(LoggerInterface::class, $instance);
|
||||
$this->assertEquals('Shlink', $instance->getName());
|
||||
}
|
||||
}
|
||||
31
module/Common/test/Factory/TranslatorFactoryTest.php
Normal file
31
module/Common/test/Factory/TranslatorFactoryTest.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Common\Factory;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Shlinkio\Shlink\Common\Factory\TranslatorFactory;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
class TranslatorFactoryTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var TranslatorFactory
|
||||
*/
|
||||
protected $factory;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->factory = new TranslatorFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function serviceIsCreated()
|
||||
{
|
||||
$instance = $this->factory->__invoke(new ServiceManager(['services' => [
|
||||
'config' => [],
|
||||
]]), '');
|
||||
$this->assertInstanceOf(Translator::class, $instance);
|
||||
}
|
||||
}
|
||||
71
module/Common/test/Middleware/LocaleMiddlewareTest.php
Normal file
71
module/Common/test/Middleware/LocaleMiddlewareTest.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Common\Middleware;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Shlinkio\Shlink\Common\Middleware\LocaleMiddleware;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Diactoros\ServerRequestFactory;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
|
||||
class LocaleMiddlewareTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var LocaleMiddleware
|
||||
*/
|
||||
protected $middleware;
|
||||
/**
|
||||
* @var Translator
|
||||
*/
|
||||
protected $translator;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->translator = Translator::factory(['locale' => 'ru']);
|
||||
$this->middleware = new LocaleMiddleware($this->translator);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function whenNoHeaderIsPresentLocaleIsNotChanged()
|
||||
{
|
||||
$this->assertEquals('ru', $this->translator->getLocale());
|
||||
$this->middleware->__invoke(ServerRequestFactory::fromGlobals(), new Response(), function ($req, $resp) {
|
||||
return $resp;
|
||||
});
|
||||
$this->assertEquals('ru', $this->translator->getLocale());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function whenTheHeaderIsPresentLocaleIsChanged()
|
||||
{
|
||||
$this->assertEquals('ru', $this->translator->getLocale());
|
||||
$request = ServerRequestFactory::fromGlobals()->withHeader('Accept-Language', 'es');
|
||||
$this->middleware->__invoke($request, new Response(), function ($req, $resp) {
|
||||
return $resp;
|
||||
});
|
||||
$this->assertEquals('es', $this->translator->getLocale());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function localeGetsNormalized()
|
||||
{
|
||||
$this->assertEquals('ru', $this->translator->getLocale());
|
||||
|
||||
$request = ServerRequestFactory::fromGlobals()->withHeader('Accept-Language', 'es_ES');
|
||||
$this->middleware->__invoke($request, new Response(), function ($req, $resp) {
|
||||
return $resp;
|
||||
});
|
||||
$this->assertEquals('es', $this->translator->getLocale());
|
||||
|
||||
$request = ServerRequestFactory::fromGlobals()->withHeader('Accept-Language', 'en-US');
|
||||
$this->middleware->__invoke($request, new Response(), function ($req, $resp) {
|
||||
return $resp;
|
||||
});
|
||||
$this->assertEquals('en', $this->translator->getLocale());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Common\Paginator;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Common\Paginator\Adapter\PaginableRepositoryAdapter;
|
||||
use Shlinkio\Shlink\Common\Repository\PaginableRepositoryInterface;
|
||||
|
||||
class PaginableRepositoryAdapterTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var PaginableRepositoryAdapter
|
||||
*/
|
||||
protected $adapter;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
protected $repo;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->repo = $this->prophesize(PaginableRepositoryInterface::class);
|
||||
$this->adapter = new PaginableRepositoryAdapter($this->repo->reveal(), 'search', 'order');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function getItemsFallbacksToFindList()
|
||||
{
|
||||
$this->repo->findList(10, 5, 'search', 'order')->shouldBeCalledTimes(1);
|
||||
$this->adapter->getItems(5, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function countFallbacksToCountList()
|
||||
{
|
||||
$this->repo->countList('search')->shouldBeCalledTimes(1);
|
||||
$this->adapter->count();
|
||||
}
|
||||
}
|
||||
56
module/Common/test/Service/IpLocationResolverTest.php
Normal file
56
module/Common/test/Service/IpLocationResolverTest.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Common\Service;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\TransferException;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Common\Service\IpLocationResolver;
|
||||
|
||||
class IpLocationResolverTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var IpLocationResolver
|
||||
*/
|
||||
protected $ipResolver;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
protected $client;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->client = $this->prophesize(Client::class);
|
||||
$this->ipResolver = new IpLocationResolver($this->client->reveal());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function correctIpReturnsDecodedInfo()
|
||||
{
|
||||
$expected = [
|
||||
'foo' => 'bar',
|
||||
'baz' => 'foo',
|
||||
];
|
||||
$response = new Response();
|
||||
$response->getBody()->write(json_encode($expected));
|
||||
$response->getBody()->rewind();
|
||||
|
||||
$this->client->get('http://freegeoip.net/json/1.2.3.4')->willReturn($response)
|
||||
->shouldBeCalledTimes(1);
|
||||
$this->assertEquals($expected, $this->ipResolver->resolveIpLocation('1.2.3.4'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @expectedException \Shlinkio\Shlink\Common\Exception\WrongIpException
|
||||
*/
|
||||
public function guzzleExceptionThrowsShlinkException()
|
||||
{
|
||||
$this->client->get('http://freegeoip.net/json/1.2.3.4')->willThrow(new TransferException())
|
||||
->shouldBeCalledTimes(1);
|
||||
$this->ipResolver->resolveIpLocation('1.2.3.4');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
namespace ShlinkioTest\Shlink\Common\Twig\Extension;
|
||||
|
||||
use PHPUnit_Framework_TestCase as TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Common\Twig\Extension\TranslatorExtension;
|
||||
use Zend\I18n\Translator\Translator;
|
||||
|
||||
class TranslatorExtensionTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var TranslatorExtension
|
||||
*/
|
||||
protected $extension;
|
||||
/**
|
||||
* @var ObjectProphecy
|
||||
*/
|
||||
protected $translator;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->translator = $this->prophesize(Translator::class);
|
||||
$this->extension = new TranslatorExtension($this->translator->reveal());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function extensionNameIsClassName()
|
||||
{
|
||||
$this->assertEquals(TranslatorExtension::class, $this->extension->getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function properFunctionsAreReturned()
|
||||
{
|
||||
$funcs = $this->extension->getFunctions();
|
||||
$this->assertCount(2, $funcs);
|
||||
foreach ($funcs as $func) {
|
||||
$this->assertInstanceOf(\Twig_SimpleFunction::class, $func);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function translateFallbacksToTranslator()
|
||||
{
|
||||
$this->translator->translate('foo', 'default', null)->shouldBeCalledTimes(1);
|
||||
$this->extension->translate('foo');
|
||||
|
||||
$this->translator->translate('bar', 'baz', 'en')->shouldBeCalledTimes(1);
|
||||
$this->extension->translate('bar', 'baz', 'en');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function translatePluralFallbacksToTranslator()
|
||||
{
|
||||
$this->translator->translatePlural('foo', 'bar', 'baz', 'default', null)->shouldBeCalledTimes(1);
|
||||
$this->extension->translatePlural('foo', 'bar', 'baz');
|
||||
|
||||
$this->translator->translatePlural('foo', 'bar', 'baz', 'another', 'en')->shouldBeCalledTimes(1);
|
||||
$this->extension->translatePlural('foo', 'bar', 'baz', 'another', 'en');
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user