mirror of
https://github.com/shlinkio/shlink.git
synced 2024-12-27 01:11:39 -06:00
Merge pull request #348 from acelaya/feature/external-installer
Feature/external installer
This commit is contained in:
commit
04e03e9b6e
10
.gitattributes
vendored
10
.gitattributes
vendored
@ -1,12 +1,14 @@
|
||||
/config/test export-ignore
|
||||
/data/infra export-ignore
|
||||
/docs export-ignore
|
||||
/module/CLI/test export-ignore
|
||||
/module/CLI/test-resources export-ignore
|
||||
/module/Common/test export-ignore
|
||||
/module/Common/test-func export-ignore
|
||||
/module/Common/test-db export-ignore
|
||||
/module/Core/test export-ignore
|
||||
/module/Core/test-func export-ignore
|
||||
/module/Core/test-db export-ignore
|
||||
/module/Rest/test export-ignore
|
||||
/module/Rest/test-api export-ignore
|
||||
.env.dist export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
@ -17,9 +19,9 @@ build.sh export-ignore
|
||||
CHANGELOG.md export-ignore
|
||||
docker-compose.override.yml.dist export-ignore
|
||||
docker-compose.yml export-ignore
|
||||
func_tests_bootstrap.php export-ignore
|
||||
indocker export-ignore
|
||||
phpcs.xml export-ignore
|
||||
phpunit.xml.dist export-ignore
|
||||
phpunit-func.xml export-ignore
|
||||
phpunit-api.xml export-ignore
|
||||
phpunit-db.xml export-ignore
|
||||
phpstan.neon
|
||||
|
@ -47,6 +47,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
|
||||
* [#320](https://github.com/shlinkio/shlink/issues/320) Replaced query builder by plain DQL for all queries which do not need to be dynamically generated.
|
||||
* [#330](https://github.com/shlinkio/shlink/issues/330) No longer allow failures on PHP 7.3 envs during project CI build.
|
||||
* [#335](https://github.com/shlinkio/shlink/issues/335) Renamed functional test suite to database test suite, since that better describes what it actually does.
|
||||
* [#346](https://github.com/shlinkio/shlink/issues/346) Extracted installer as an independent tool.
|
||||
|
||||
#### Deprecated
|
||||
|
||||
|
10
bin/install
10
bin/install
@ -2,11 +2,7 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Installer;
|
||||
namespace Shlinkio\Shlink;
|
||||
|
||||
use Symfony\Component\Console\Application;
|
||||
use Zend\ServiceManager\ServiceLocatorInterface;
|
||||
|
||||
/** @var ServiceLocatorInterface $container */
|
||||
$container = include __DIR__ . '/../config/install-container.php';
|
||||
$container->build(Application::class)->run();
|
||||
$run = require __DIR__ . '/../vendor/shlinkio/shlink-installer/bin/run.php';
|
||||
$run(false);
|
||||
|
10
bin/update
10
bin/update
@ -2,11 +2,7 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Installer;
|
||||
namespace Shlinkio\Shlink;
|
||||
|
||||
use Symfony\Component\Console\Application;
|
||||
use Zend\ServiceManager\ServiceLocatorInterface;
|
||||
|
||||
/** @var ServiceLocatorInterface $container */
|
||||
$container = include __DIR__ . '/../config/install-container.php';
|
||||
$container->build(Application::class, ['isUpdate' => true])->run();
|
||||
$run = require __DIR__ . '/../vendor/shlinkio/shlink-installer/bin/run.php';
|
||||
$run(true);
|
||||
|
@ -29,7 +29,7 @@
|
||||
"lstrojny/functional-php": "^1.8",
|
||||
"mikehaertl/phpwkhtmltopdf": "^2.2",
|
||||
"monolog/monolog": "^1.21",
|
||||
"roave/security-advisories": "dev-master",
|
||||
"shlinkio/shlink-installer": "^1.0",
|
||||
"symfony/console": "^4.2",
|
||||
"symfony/filesystem": "^4.2",
|
||||
"symfony/lock": "^4.2",
|
||||
@ -37,7 +37,7 @@
|
||||
"theorchard/monolog-cascade": "^0.4",
|
||||
"zendframework/zend-config": "^3.0",
|
||||
"zendframework/zend-config-aggregator": "^1.0",
|
||||
"zendframework/zend-diactoros": "^2.0 <2.0.2",
|
||||
"zendframework/zend-diactoros": "^2.1.1",
|
||||
"zendframework/zend-expressive": "^3.0",
|
||||
"zendframework/zend-expressive-fastroute": "^3.0",
|
||||
"zendframework/zend-expressive-helpers": "^5.0",
|
||||
@ -57,6 +57,7 @@
|
||||
"phpstan/phpstan": "^0.10.0",
|
||||
"phpunit/phpcov": "^5.0",
|
||||
"phpunit/phpunit": "^7.3",
|
||||
"roave/security-advisories": "dev-master",
|
||||
"shlinkio/php-coding-standard": "~1.0.0",
|
||||
"symfony/dotenv": "^4.2",
|
||||
"symfony/var-dumper": "^4.2",
|
||||
@ -68,8 +69,7 @@
|
||||
"Shlinkio\\Shlink\\CLI\\": "module/CLI/src",
|
||||
"Shlinkio\\Shlink\\Rest\\": "module/Rest/src",
|
||||
"Shlinkio\\Shlink\\Core\\": "module/Core/src",
|
||||
"Shlinkio\\Shlink\\Common\\": "module/Common/src",
|
||||
"Shlinkio\\Shlink\\Installer\\": "module/Installer/src"
|
||||
"Shlinkio\\Shlink\\Common\\": "module/Common/src"
|
||||
},
|
||||
"files": [
|
||||
"module/Common/functions/functions.php"
|
||||
@ -87,8 +87,7 @@
|
||||
"ShlinkioTest\\Shlink\\Common\\": [
|
||||
"module/Common/test",
|
||||
"module/Common/test-db"
|
||||
],
|
||||
"ShlinkioTest\\Shlink\\Installer\\": "module/Installer/test"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
@ -18,7 +18,6 @@ return (new ConfigAggregator\ConfigAggregator([
|
||||
Common\ConfigProvider::class,
|
||||
Core\ConfigProvider::class,
|
||||
CLI\ConfigProvider::class,
|
||||
Installer\ConfigProvider::class,
|
||||
Rest\ConfigProvider::class,
|
||||
new ConfigAggregator\PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'),
|
||||
new ConfigAggregator\ZendConfigProvider('config/params/{generated_config.php,*.config.{php,json}}'),
|
||||
|
@ -1,48 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
|
||||
use Shlinkio\Shlink\Installer\Config\Plugin;
|
||||
use Shlinkio\Shlink\Installer\Factory\InstallApplicationFactory;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
chdir(dirname(__DIR__));
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$config = [
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
Application::class => InstallApplicationFactory::class,
|
||||
Filesystem::class => InvokableFactory::class,
|
||||
],
|
||||
'services' => [
|
||||
'random-chars-generator' => function () {
|
||||
return str_shuffle(UrlShortenerOptions::DEFAULT_CHARS);
|
||||
},
|
||||
],
|
||||
],
|
||||
|
||||
'config_customizer_plugins' => [
|
||||
'factories' => [
|
||||
Plugin\DatabaseConfigCustomizer::class => ConfigAbstractFactory::class,
|
||||
Plugin\UrlShortenerConfigCustomizer::class => ConfigAbstractFactory::class,
|
||||
Plugin\LanguageConfigCustomizer::class => InvokableFactory::class,
|
||||
Plugin\ApplicationConfigCustomizer::class => InvokableFactory::class,
|
||||
],
|
||||
],
|
||||
|
||||
ConfigAbstractFactory::class => [
|
||||
Plugin\DatabaseConfigCustomizer::class => [Filesystem::class],
|
||||
Plugin\UrlShortenerConfigCustomizer::class => ['random-chars-generator'],
|
||||
],
|
||||
];
|
||||
|
||||
$container = new ServiceManager($config['dependencies']);
|
||||
$container->setService('config', $config);
|
||||
|
||||
return $container;
|
@ -1,65 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Collection;
|
||||
|
||||
use function array_key_exists;
|
||||
use function array_shift;
|
||||
use function is_array;
|
||||
|
||||
final class PathCollection
|
||||
{
|
||||
/** @var array */
|
||||
private $array;
|
||||
|
||||
public function __construct(array $array)
|
||||
{
|
||||
$this->array = $array;
|
||||
}
|
||||
|
||||
public function pathExists(array $path): bool
|
||||
{
|
||||
return $this->checkPathExists($path, $this->array);
|
||||
}
|
||||
|
||||
private function checkPathExists(array $path, array $array): bool
|
||||
{
|
||||
// As soon as a step is not found, the path does not exist
|
||||
$step = array_shift($path);
|
||||
if (! array_key_exists($step, $array)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Once the path is empty, we have found all the parts in the path
|
||||
if (empty($path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If current value is not an array, then we have not found the path
|
||||
$newArray = $array[$step];
|
||||
if (! is_array($newArray)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->checkPathExists($path, $newArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getValueInPath(array $path)
|
||||
{
|
||||
$array = $this->array;
|
||||
|
||||
do {
|
||||
$step = array_shift($path);
|
||||
if (! is_array($array) || ! array_key_exists($step, $array)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$array = $array[$step];
|
||||
} while (! empty($path));
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Collection;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\Collection\PathCollection;
|
||||
|
||||
class PathCollectionTest extends TestCase
|
||||
{
|
||||
/** @var PathCollection */
|
||||
private $collection;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->collection = new PathCollection([
|
||||
'foo' => [
|
||||
'bar' => [
|
||||
'baz' => 'Hello world!',
|
||||
],
|
||||
],
|
||||
'something' => [],
|
||||
'another' => [
|
||||
'one' => 'Shlink',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider providePaths
|
||||
*/
|
||||
public function pathExistsReturnsExpectedValue(array $path, bool $expected)
|
||||
{
|
||||
$this->assertEquals($expected, $this->collection->pathExists($path));
|
||||
}
|
||||
|
||||
public function providePaths(): array
|
||||
{
|
||||
return [
|
||||
[[], false],
|
||||
[['boo'], false],
|
||||
[['foo', 'nop'], false],
|
||||
[['another', 'one', 'nop'], false],
|
||||
[['foo'], true],
|
||||
[['foo', 'bar'], true],
|
||||
[['foo', 'bar', 'baz'], true],
|
||||
[['something'], true],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider providePathsWithValue
|
||||
*/
|
||||
public function getValueInPathReturnsExpectedValue(array $path, $expected)
|
||||
{
|
||||
$this->assertEquals($expected, $this->collection->getValueInPath($path));
|
||||
}
|
||||
|
||||
public function providePathsWithValue(): array
|
||||
{
|
||||
return [
|
||||
[[], null],
|
||||
[['boo'], null],
|
||||
[['foo', 'nop'], null],
|
||||
[['another', 'one', 'nop'], null],
|
||||
[['foo'], [
|
||||
'bar' => [
|
||||
'baz' => 'Hello world!',
|
||||
],
|
||||
]],
|
||||
[['foo', 'bar'], [
|
||||
'baz' => 'Hello world!',
|
||||
]],
|
||||
[['foo', 'bar', 'baz'], 'Hello world!'],
|
||||
[['something'], []],
|
||||
];
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return [];
|
@ -1,247 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Installer\Command;
|
||||
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Shlinkio\Shlink\Installer\Config\ConfigCustomizerManagerInterface;
|
||||
use Shlinkio\Shlink\Installer\Config\Plugin;
|
||||
use Shlinkio\Shlink\Installer\Model\CustomizableAppConfig;
|
||||
use Shlinkio\Shlink\Installer\Util\AskUtilsTrait;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Exception\LogicException;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Helper\ProcessHelper;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Filesystem\Exception\IOException;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
use Zend\Config\Writer\WriterInterface;
|
||||
use function array_unshift;
|
||||
use function implode;
|
||||
|
||||
class InstallCommand extends Command
|
||||
{
|
||||
use AskUtilsTrait;
|
||||
|
||||
public const GENERATED_CONFIG_PATH = 'config/params/generated_config.php';
|
||||
|
||||
/** @var SymfonyStyle */
|
||||
private $io;
|
||||
/** @var ProcessHelper */
|
||||
private $processHelper;
|
||||
/** @var WriterInterface */
|
||||
private $configWriter;
|
||||
/** @var Filesystem */
|
||||
private $filesystem;
|
||||
/** @var ConfigCustomizerManagerInterface */
|
||||
private $configCustomizers;
|
||||
/** @var bool */
|
||||
private $isUpdate;
|
||||
/** @var PhpExecutableFinder */
|
||||
private $phpFinder;
|
||||
/** @var string|bool */
|
||||
private $phpBinary;
|
||||
|
||||
/**
|
||||
* InstallCommand constructor.
|
||||
* @param WriterInterface $configWriter
|
||||
* @param Filesystem $filesystem
|
||||
* @param ConfigCustomizerManagerInterface $configCustomizers
|
||||
* @param bool $isUpdate
|
||||
* @param PhpExecutableFinder|null $phpFinder
|
||||
* @throws LogicException
|
||||
*/
|
||||
public function __construct(
|
||||
WriterInterface $configWriter,
|
||||
Filesystem $filesystem,
|
||||
ConfigCustomizerManagerInterface $configCustomizers,
|
||||
bool $isUpdate = false,
|
||||
PhpExecutableFinder $phpFinder = null
|
||||
) {
|
||||
parent::__construct();
|
||||
$this->configWriter = $configWriter;
|
||||
$this->isUpdate = $isUpdate;
|
||||
$this->filesystem = $filesystem;
|
||||
$this->configCustomizers = $configCustomizers;
|
||||
$this->phpFinder = $phpFinder ?: new PhpExecutableFinder();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setName('shlink:install')
|
||||
->setDescription('Installs or updates Shlink');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
* @return void
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): void
|
||||
{
|
||||
$this->io = new SymfonyStyle($input, $output);
|
||||
|
||||
$this->io->writeln([
|
||||
'<info>Welcome to Shlink!!</info>',
|
||||
'This tool will guide you through the installation process.',
|
||||
]);
|
||||
|
||||
// Check if a cached config file exists and drop it if so
|
||||
if ($this->filesystem->exists('data/cache/app_config.php')) {
|
||||
$this->io->write('Deleting old cached config...');
|
||||
try {
|
||||
$this->filesystem->remove('data/cache/app_config.php');
|
||||
$this->io->writeln(' <info>Success</info>');
|
||||
} catch (IOException $e) {
|
||||
$this->io->error(
|
||||
'Failed! You will have to manually delete the data/cache/app_config.php file to'
|
||||
. ' get new config applied.'
|
||||
);
|
||||
if ($this->io->isVerbose()) {
|
||||
$this->getApplication()->renderException($e, $output);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If running update command, ask the user to import previous config
|
||||
$config = $this->isUpdate ? $this->importConfig() : new CustomizableAppConfig();
|
||||
|
||||
// Ask for custom config params
|
||||
foreach ([
|
||||
Plugin\DatabaseConfigCustomizer::class,
|
||||
Plugin\UrlShortenerConfigCustomizer::class,
|
||||
Plugin\LanguageConfigCustomizer::class,
|
||||
Plugin\ApplicationConfigCustomizer::class,
|
||||
] as $pluginName) {
|
||||
/** @var Plugin\ConfigCustomizerInterface $configCustomizer */
|
||||
$configCustomizer = $this->configCustomizers->get($pluginName);
|
||||
$configCustomizer->process($this->io, $config);
|
||||
}
|
||||
|
||||
// Generate config params files
|
||||
$this->configWriter->toFile(self::GENERATED_CONFIG_PATH, $config->getArrayCopy(), false);
|
||||
$this->io->writeln(['<info>Custom configuration properly generated!</info>', '']);
|
||||
|
||||
// If current command is not update, generate database
|
||||
if (! $this->isUpdate) {
|
||||
$this->io->write('Initializing database...');
|
||||
if (! $this->execPhp(
|
||||
['vendor/doctrine/orm/bin/doctrine.php', 'orm:schema-tool:create'],
|
||||
'Error generating database.',
|
||||
$output
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Run database migrations
|
||||
$this->io->write('Updating database...');
|
||||
if (! $this->execPhp(
|
||||
['vendor/doctrine/migrations/bin/doctrine-migrations.php', 'migrations:migrate'],
|
||||
'Error updating database.',
|
||||
$output
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate proxies
|
||||
$this->io->write('Generating proxies...');
|
||||
if (! $this->execPhp(
|
||||
['vendor/doctrine/orm/bin/doctrine.php', 'orm:generate-proxies'],
|
||||
'Error generating proxies.',
|
||||
$output
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Download GeoLite2 db file
|
||||
$this->io->write('Downloading GeoLite2 db...');
|
||||
if (! $this->execPhp(['bin/cli', 'visit:update-db'], 'Error downloading GeoLite2 db.', $output)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->io->success('Installation complete!');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CustomizableAppConfig
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private function importConfig(): CustomizableAppConfig
|
||||
{
|
||||
$config = new CustomizableAppConfig();
|
||||
|
||||
// Ask the user if he/she wants to import an older configuration
|
||||
$importConfig = $this->io->confirm(
|
||||
'Do you want to import configuration from previous installation? (You will still be asked for any new '
|
||||
. 'config option that did not exist in previous shlink versions)'
|
||||
);
|
||||
if (! $importConfig) {
|
||||
return $config;
|
||||
}
|
||||
|
||||
// Ask the user for the older shlink path
|
||||
$keepAsking = true;
|
||||
do {
|
||||
$config->setImportedInstallationPath($this->askRequired(
|
||||
$this->io,
|
||||
'previous installation path',
|
||||
'Previous shlink installation path from which to import config'
|
||||
));
|
||||
$configFile = $config->getImportedInstallationPath() . '/' . self::GENERATED_CONFIG_PATH;
|
||||
$configExists = $this->filesystem->exists($configFile);
|
||||
|
||||
if (! $configExists) {
|
||||
$keepAsking = $this->io->confirm(
|
||||
'Provided path does not seem to be a valid shlink root path. Do you want to try another path?'
|
||||
);
|
||||
}
|
||||
} while (! $configExists && $keepAsking);
|
||||
|
||||
// If after some retries the user has chosen not to test another path, return
|
||||
if (! $configExists) {
|
||||
return $config;
|
||||
}
|
||||
|
||||
// Read the config file
|
||||
$config->exchangeArray(include $configFile);
|
||||
return $config;
|
||||
}
|
||||
|
||||
private function execPhp(array $command, string $errorMessage, OutputInterface $output): bool
|
||||
{
|
||||
if ($this->processHelper === null) {
|
||||
$this->processHelper = $this->getHelper('process');
|
||||
}
|
||||
|
||||
if ($this->phpBinary === null) {
|
||||
$this->phpBinary = $this->phpFinder->find(false) ?: 'php';
|
||||
}
|
||||
|
||||
array_unshift($command, $this->phpBinary);
|
||||
$this->io->write(
|
||||
' <options=bold>[Running "' . implode(' ', $command) . '"]</> ',
|
||||
false,
|
||||
OutputInterface::VERBOSITY_VERBOSE
|
||||
);
|
||||
$process = $this->processHelper->run($output, $command);
|
||||
if ($process->isSuccessful()) {
|
||||
$this->io->writeln(' <info>Success!</info>');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (! $this->io->isVerbose()) {
|
||||
$this->io->error($errorMessage . ' Run this command with -vvv to see specific error info.');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Installer\Config;
|
||||
|
||||
use Shlinkio\Shlink\Installer\Config\Plugin\ConfigCustomizerInterface;
|
||||
use Zend\ServiceManager\AbstractPluginManager;
|
||||
|
||||
class ConfigCustomizerManager extends AbstractPluginManager implements ConfigCustomizerManagerInterface
|
||||
{
|
||||
protected $instanceOf = ConfigCustomizerInterface::class;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Installer\Config;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
interface ConfigCustomizerManagerInterface extends ContainerInterface
|
||||
{
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Installer\Config\Plugin;
|
||||
|
||||
use Shlinkio\Shlink\Common\Util\StringUtilsTrait;
|
||||
use Shlinkio\Shlink\Installer\Exception\InvalidConfigOptionException;
|
||||
use Shlinkio\Shlink\Installer\Model\CustomizableAppConfig;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use function array_diff;
|
||||
use function array_keys;
|
||||
use function is_numeric;
|
||||
use function sprintf;
|
||||
|
||||
class ApplicationConfigCustomizer implements ConfigCustomizerInterface
|
||||
{
|
||||
use StringUtilsTrait;
|
||||
|
||||
public const SECRET = 'SECRET';
|
||||
public const DISABLE_TRACK_PARAM = 'DISABLE_TRACK_PARAM';
|
||||
public const CHECK_VISITS_THRESHOLD = 'CHECK_VISITS_THRESHOLD';
|
||||
public const VISITS_THRESHOLD = 'VISITS_THRESHOLD';
|
||||
private const EXPECTED_KEYS = [
|
||||
self::SECRET,
|
||||
self::DISABLE_TRACK_PARAM,
|
||||
self::CHECK_VISITS_THRESHOLD,
|
||||
self::VISITS_THRESHOLD,
|
||||
];
|
||||
|
||||
public function process(SymfonyStyle $io, CustomizableAppConfig $appConfig): void
|
||||
{
|
||||
$app = $appConfig->getApp();
|
||||
$keysToAskFor = $appConfig->hasApp() ? array_diff(self::EXPECTED_KEYS, array_keys($app)) : self::EXPECTED_KEYS;
|
||||
|
||||
if (empty($keysToAskFor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$io->title('APPLICATION');
|
||||
foreach ($keysToAskFor as $key) {
|
||||
// Skip visits threshold when the user decided not to check visits on deletions
|
||||
if ($key === self::VISITS_THRESHOLD && ! $app[self::CHECK_VISITS_THRESHOLD]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$app[$key] = $this->ask($io, $key);
|
||||
}
|
||||
$appConfig->setApp($app);
|
||||
}
|
||||
|
||||
private function ask(SymfonyStyle $io, string $key)
|
||||
{
|
||||
switch ($key) {
|
||||
case self::SECRET:
|
||||
return $io->ask(
|
||||
'Define a secret string that will be used to sign API tokens (leave empty to autogenerate one) '
|
||||
. '<fg=red>[DEPRECATED. TO BE REMOVED]</>'
|
||||
) ?: $this->generateRandomString(32);
|
||||
case self::DISABLE_TRACK_PARAM:
|
||||
return $io->ask(
|
||||
'Provide a parameter name that you will be able to use to disable tracking on specific request to '
|
||||
. 'short URLs (leave empty and this feature won\'t be enabled)'
|
||||
);
|
||||
case self::CHECK_VISITS_THRESHOLD:
|
||||
return $io->confirm(
|
||||
'Do you want to enable a safety check which will not allow short URLs to be deleted when they '
|
||||
. 'have more than a specific amount of visits?'
|
||||
);
|
||||
case self::VISITS_THRESHOLD:
|
||||
return $io->ask(
|
||||
'What is the amount of visits from which the system will not allow short URLs to be deleted?',
|
||||
15,
|
||||
[$this, 'validateVisitsThreshold']
|
||||
);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function validateVisitsThreshold($value): int
|
||||
{
|
||||
if (! is_numeric($value) || $value < 1) {
|
||||
throw new InvalidConfigOptionException(
|
||||
sprintf('Provided value "%s" is invalid. Expected a number greater than 1', $value)
|
||||
);
|
||||
}
|
||||
|
||||
return (int) $value;
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Installer\Config\Plugin;
|
||||
|
||||
use Shlinkio\Shlink\Installer\Model\CustomizableAppConfig;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
interface ConfigCustomizerInterface
|
||||
{
|
||||
public function process(SymfonyStyle $io, CustomizableAppConfig $appConfig): void;
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Installer\Config\Plugin;
|
||||
|
||||
use Shlinkio\Shlink\Installer\Model\CustomizableAppConfig;
|
||||
use Shlinkio\Shlink\Installer\Util\AskUtilsTrait;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Filesystem\Exception\IOException;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use function array_diff;
|
||||
use function array_keys;
|
||||
use function Functional\contains;
|
||||
|
||||
class DatabaseConfigCustomizer implements ConfigCustomizerInterface
|
||||
{
|
||||
use AskUtilsTrait;
|
||||
|
||||
public const DRIVER = 'DRIVER';
|
||||
public const NAME = 'NAME';
|
||||
public const USER = 'USER';
|
||||
public const PASSWORD = 'PASSWORD';
|
||||
public const HOST = 'HOST';
|
||||
public const PORT = 'PORT';
|
||||
private const DRIVER_DEPENDANT_OPTIONS = [
|
||||
self::DRIVER,
|
||||
self::NAME,
|
||||
self::USER,
|
||||
self::PASSWORD,
|
||||
self::HOST,
|
||||
self::PORT,
|
||||
];
|
||||
private const EXPECTED_KEYS = self::DRIVER_DEPENDANT_OPTIONS; // Same now, but could change in the future
|
||||
|
||||
private const DATABASE_DRIVERS = [
|
||||
'MySQL' => 'pdo_mysql',
|
||||
'PostgreSQL' => 'pdo_pgsql',
|
||||
'SQLite' => 'pdo_sqlite',
|
||||
];
|
||||
|
||||
/** @var Filesystem */
|
||||
private $filesystem;
|
||||
|
||||
public function __construct(Filesystem $filesystem)
|
||||
{
|
||||
$this->filesystem = $filesystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IOException
|
||||
*/
|
||||
public function process(SymfonyStyle $io, CustomizableAppConfig $appConfig): void
|
||||
{
|
||||
$titlePrinted = false;
|
||||
$db = $appConfig->getDatabase();
|
||||
$doImport = $appConfig->hasDatabase();
|
||||
$keysToAskFor = $doImport ? array_diff(self::EXPECTED_KEYS, array_keys($db)) : self::EXPECTED_KEYS;
|
||||
|
||||
// If the user selected to keep DB, try to import SQLite database
|
||||
if ($doImport) {
|
||||
$this->importSqliteDbFile($io, $appConfig);
|
||||
}
|
||||
|
||||
if (empty($keysToAskFor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the driver is one of the params to ask for, ask for it first
|
||||
if (contains($keysToAskFor, self::DRIVER)) {
|
||||
$io->title('DATABASE');
|
||||
$titlePrinted = true;
|
||||
$db[self::DRIVER] = $this->ask($io, self::DRIVER);
|
||||
$keysToAskFor = array_diff($keysToAskFor, [self::DRIVER]);
|
||||
}
|
||||
|
||||
// If driver is SQLite, do not ask any driver-dependant option
|
||||
if ($db[self::DRIVER] === self::DATABASE_DRIVERS['SQLite']) {
|
||||
$keysToAskFor = array_diff($keysToAskFor, self::DRIVER_DEPENDANT_OPTIONS);
|
||||
}
|
||||
|
||||
if (! $titlePrinted && ! empty($keysToAskFor)) {
|
||||
$io->title('DATABASE');
|
||||
}
|
||||
foreach ($keysToAskFor as $key) {
|
||||
$db[$key] = $this->ask($io, $key, $db);
|
||||
}
|
||||
$appConfig->setDatabase($db);
|
||||
}
|
||||
|
||||
private function importSqliteDbFile(SymfonyStyle $io, CustomizableAppConfig $appConfig): void
|
||||
{
|
||||
if ($appConfig->getDatabase()[self::DRIVER] !== self::DATABASE_DRIVERS['SQLite']) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->filesystem->copy(
|
||||
$appConfig->getImportedInstallationPath() . '/' . CustomizableAppConfig::SQLITE_DB_PATH,
|
||||
CustomizableAppConfig::SQLITE_DB_PATH
|
||||
);
|
||||
} catch (IOException $e) {
|
||||
$io->error('It wasn\'t possible to import the SQLite database');
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
private function ask(SymfonyStyle $io, string $key, array $params = [])
|
||||
{
|
||||
switch ($key) {
|
||||
case self::DRIVER:
|
||||
$databases = array_keys(self::DATABASE_DRIVERS);
|
||||
$dbType = $io->choice('Select database type', $databases, $databases[0]);
|
||||
return self::DATABASE_DRIVERS[$dbType];
|
||||
case self::NAME:
|
||||
return $io->ask('Database name', 'shlink');
|
||||
case self::USER:
|
||||
return $this->askRequired($io, 'username', 'Database username');
|
||||
case self::PASSWORD:
|
||||
return $this->askRequired($io, 'password', 'Database password');
|
||||
case self::HOST:
|
||||
return $io->ask('Database host', 'localhost');
|
||||
case self::PORT:
|
||||
return $io->ask('Database port', $this->getDefaultDbPort($params[self::DRIVER]));
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function getDefaultDbPort(string $driver): string
|
||||
{
|
||||
return $driver === 'pdo_mysql' ? '3306' : '5432';
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Installer\Config\Plugin;
|
||||
|
||||
use Shlinkio\Shlink\Installer\Model\CustomizableAppConfig;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use function array_diff;
|
||||
use function array_keys;
|
||||
|
||||
class LanguageConfigCustomizer implements ConfigCustomizerInterface
|
||||
{
|
||||
public const DEFAULT_LANG = 'DEFAULT';
|
||||
private const EXPECTED_KEYS = [
|
||||
self::DEFAULT_LANG,
|
||||
];
|
||||
|
||||
private const SUPPORTED_LANGUAGES = ['en', 'es'];
|
||||
|
||||
public function process(SymfonyStyle $io, CustomizableAppConfig $appConfig): void
|
||||
{
|
||||
$lang = $appConfig->getLanguage();
|
||||
$keysToAskFor = $appConfig->hasLanguage()
|
||||
? array_diff(self::EXPECTED_KEYS, array_keys($lang))
|
||||
: self::EXPECTED_KEYS;
|
||||
|
||||
if (empty($keysToAskFor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$io->title('LANGUAGE');
|
||||
foreach ($keysToAskFor as $key) {
|
||||
$lang[$key] = $this->ask($io, $key);
|
||||
}
|
||||
$appConfig->setLanguage($lang);
|
||||
}
|
||||
|
||||
private function ask(SymfonyStyle $io, string $key)
|
||||
{
|
||||
switch ($key) {
|
||||
case self::DEFAULT_LANG:
|
||||
return $this->chooseLanguage($io, 'Select default language for the application error pages');
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function chooseLanguage(SymfonyStyle $io, string $message): string
|
||||
{
|
||||
return $io->choice($message, self::SUPPORTED_LANGUAGES, self::SUPPORTED_LANGUAGES[0]);
|
||||
}
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Installer\Config\Plugin;
|
||||
|
||||
use Shlinkio\Shlink\Installer\Model\CustomizableAppConfig;
|
||||
use Shlinkio\Shlink\Installer\Util\AskUtilsTrait;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use function array_diff;
|
||||
use function array_keys;
|
||||
use function count;
|
||||
use function Functional\contains;
|
||||
|
||||
class UrlShortenerConfigCustomizer implements ConfigCustomizerInterface
|
||||
{
|
||||
use AskUtilsTrait;
|
||||
|
||||
public const SCHEMA = 'SCHEMA';
|
||||
public const HOSTNAME = 'HOSTNAME';
|
||||
public const CHARS = 'CHARS';
|
||||
public const VALIDATE_URL = 'VALIDATE_URL';
|
||||
public const ENABLE_NOT_FOUND_REDIRECTION = 'ENABLE_NOT_FOUND_REDIRECTION';
|
||||
public const NOT_FOUND_REDIRECT_TO = 'NOT_FOUND_REDIRECT_TO';
|
||||
private const EXPECTED_KEYS = [
|
||||
self::SCHEMA,
|
||||
self::HOSTNAME,
|
||||
self::CHARS,
|
||||
self::VALIDATE_URL,
|
||||
self::ENABLE_NOT_FOUND_REDIRECTION,
|
||||
self::NOT_FOUND_REDIRECT_TO,
|
||||
];
|
||||
|
||||
/** @var callable */
|
||||
private $randomCharsGenerator;
|
||||
|
||||
public function __construct(callable $randomCharsGenerator)
|
||||
{
|
||||
$this->randomCharsGenerator = $randomCharsGenerator;
|
||||
}
|
||||
|
||||
public function process(SymfonyStyle $io, CustomizableAppConfig $appConfig): void
|
||||
{
|
||||
$urlShortener = $appConfig->getUrlShortener();
|
||||
$doImport = $appConfig->hasUrlShortener();
|
||||
$keysToAskFor = $doImport ? array_diff(self::EXPECTED_KEYS, array_keys($urlShortener)) : self::EXPECTED_KEYS;
|
||||
|
||||
if (empty($keysToAskFor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Print title if there are keys other than "chars"
|
||||
$onlyKeyIsChars = count($keysToAskFor) === 1 && contains($keysToAskFor, self::CHARS);
|
||||
if (! $onlyKeyIsChars) {
|
||||
$io->title('URL SHORTENER');
|
||||
}
|
||||
foreach ($keysToAskFor as $key) {
|
||||
// Skip not found redirect URL when the user decided not to redirect
|
||||
if ($key === self::NOT_FOUND_REDIRECT_TO && ! $urlShortener[self::ENABLE_NOT_FOUND_REDIRECTION]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$urlShortener[$key] = $this->ask($io, $key);
|
||||
}
|
||||
$appConfig->setUrlShortener($urlShortener);
|
||||
}
|
||||
|
||||
private function ask(SymfonyStyle $io, string $key)
|
||||
{
|
||||
switch ($key) {
|
||||
case self::SCHEMA:
|
||||
return $io->choice(
|
||||
'Select schema for generated short URLs',
|
||||
['http', 'https'],
|
||||
'http'
|
||||
);
|
||||
case self::HOSTNAME:
|
||||
return $this->askRequired($io, 'hostname', 'Hostname for generated URLs');
|
||||
case self::CHARS:
|
||||
// This won't actually ask anything, just generate the chars. Asking for this was confusing for users
|
||||
return ($this->randomCharsGenerator)();
|
||||
case self::VALIDATE_URL:
|
||||
return $io->confirm('Do you want to validate long urls by 200 HTTP status code on response');
|
||||
case self::ENABLE_NOT_FOUND_REDIRECTION:
|
||||
return $io->confirm(
|
||||
'Do you want to enable a redirection to a custom URL when a user hits an invalid short URL? ' .
|
||||
'(If not enabled, the user will see a default "404 not found" page)',
|
||||
false
|
||||
);
|
||||
case self::NOT_FOUND_REDIRECT_TO:
|
||||
return $this->askRequired(
|
||||
$io,
|
||||
'redirect URL',
|
||||
'Custom URL to redirect to when a user hits an invalid short URL'
|
||||
);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Installer;
|
||||
|
||||
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,10 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Installer\Exception;
|
||||
|
||||
use Throwable;
|
||||
|
||||
interface ExceptionInterface extends Throwable
|
||||
{
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Installer\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class InvalidConfigOptionException extends RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Installer\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
use function sprintf;
|
||||
|
||||
class MissingRequiredOptionException extends RuntimeException implements ExceptionInterface
|
||||
{
|
||||
public static function fromOption(string $optionName): self
|
||||
{
|
||||
return new self(sprintf('The "%s" is required and can\'t be empty', $optionName));
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Installer\Factory;
|
||||
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Interop\Container\Exception\ContainerException;
|
||||
use Shlinkio\Shlink\Installer\Command\InstallCommand;
|
||||
use Shlinkio\Shlink\Installer\Config\ConfigCustomizerManager;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Exception\LogicException;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Zend\Config\Writer\PhpArray;
|
||||
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
||||
use Zend\ServiceManager\Factory\FactoryInterface;
|
||||
|
||||
class InstallApplicationFactory implements FactoryInterface
|
||||
{
|
||||
/**
|
||||
* Create an object
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
* @param string $requestedName
|
||||
* @param null|array $options
|
||||
* @return object
|
||||
* @throws LogicException
|
||||
* @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)
|
||||
{
|
||||
$isUpdate = $options !== null && isset($options['isUpdate']) ? (bool) $options['isUpdate'] : false;
|
||||
|
||||
$app = new Application();
|
||||
$command = new InstallCommand(
|
||||
new PhpArray(),
|
||||
$container->get(Filesystem::class),
|
||||
new ConfigCustomizerManager($container, $container->get('config')['config_customizer_plugins']),
|
||||
$isUpdate
|
||||
);
|
||||
$app->add($command);
|
||||
$app->setDefaultCommand($command->getName(), true);
|
||||
|
||||
return $app;
|
||||
}
|
||||
}
|
@ -1,218 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Installer\Model;
|
||||
|
||||
use PDO;
|
||||
use Shlinkio\Shlink\Common\Collection\PathCollection;
|
||||
use Shlinkio\Shlink\Installer\Config\Plugin\ApplicationConfigCustomizer;
|
||||
use Shlinkio\Shlink\Installer\Config\Plugin\DatabaseConfigCustomizer;
|
||||
use Shlinkio\Shlink\Installer\Config\Plugin\LanguageConfigCustomizer;
|
||||
use Shlinkio\Shlink\Installer\Config\Plugin\UrlShortenerConfigCustomizer;
|
||||
use Zend\Stdlib\ArraySerializableInterface;
|
||||
|
||||
final class CustomizableAppConfig implements ArraySerializableInterface
|
||||
{
|
||||
public const SQLITE_DB_PATH = 'data/database.sqlite';
|
||||
|
||||
/** @var array */
|
||||
private $database = [];
|
||||
/** @var array */
|
||||
private $urlShortener = [];
|
||||
/** @var array */
|
||||
private $language = [];
|
||||
/** @var array */
|
||||
private $app = [];
|
||||
/** @var string|null */
|
||||
private $importedInstallationPath;
|
||||
|
||||
public function getDatabase(): array
|
||||
{
|
||||
return $this->database;
|
||||
}
|
||||
|
||||
public function setDatabase(array $database): self
|
||||
{
|
||||
$this->database = $database;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasDatabase(): bool
|
||||
{
|
||||
return ! empty($this->database);
|
||||
}
|
||||
|
||||
public function getUrlShortener(): array
|
||||
{
|
||||
return $this->urlShortener;
|
||||
}
|
||||
|
||||
public function setUrlShortener(array $urlShortener): self
|
||||
{
|
||||
$this->urlShortener = $urlShortener;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasUrlShortener(): bool
|
||||
{
|
||||
return ! empty($this->urlShortener);
|
||||
}
|
||||
|
||||
public function getLanguage(): array
|
||||
{
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
public function setLanguage(array $language): self
|
||||
{
|
||||
$this->language = $language;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasLanguage(): bool
|
||||
{
|
||||
return ! empty($this->language);
|
||||
}
|
||||
|
||||
public function getApp(): array
|
||||
{
|
||||
return $this->app;
|
||||
}
|
||||
|
||||
public function setApp(array $app): self
|
||||
{
|
||||
$this->app = $app;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasApp(): bool
|
||||
{
|
||||
return ! empty($this->app);
|
||||
}
|
||||
|
||||
public function getImportedInstallationPath(): ?string
|
||||
{
|
||||
return $this->importedInstallationPath;
|
||||
}
|
||||
|
||||
public function setImportedInstallationPath(string $importedInstallationPath): self
|
||||
{
|
||||
$this->importedInstallationPath = $importedInstallationPath;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasImportedInstallationPath(): bool
|
||||
{
|
||||
return $this->importedInstallationPath !== null;
|
||||
}
|
||||
|
||||
public function exchangeArray(array $array): void
|
||||
{
|
||||
$pathCollection = new PathCollection($array);
|
||||
|
||||
$this->setApp($this->mapExistingPathsToKeys([
|
||||
ApplicationConfigCustomizer::SECRET => ['app_options', 'secret_key'],
|
||||
ApplicationConfigCustomizer::DISABLE_TRACK_PARAM => ['app_options', 'disable_track_param'],
|
||||
ApplicationConfigCustomizer::CHECK_VISITS_THRESHOLD => ['delete_short_urls', 'check_visits_threshold'],
|
||||
ApplicationConfigCustomizer::VISITS_THRESHOLD => ['delete_short_urls', 'visits_threshold'],
|
||||
], $pathCollection));
|
||||
|
||||
$this->setDatabase($this->mapExistingPathsToKeys([
|
||||
DatabaseConfigCustomizer::DRIVER => ['entity_manager', 'connection', 'driver'],
|
||||
DatabaseConfigCustomizer::USER => ['entity_manager', 'connection', 'user'],
|
||||
DatabaseConfigCustomizer::PASSWORD => ['entity_manager', 'connection', 'password'],
|
||||
DatabaseConfigCustomizer::NAME => ['entity_manager', 'connection', 'dbname'],
|
||||
DatabaseConfigCustomizer::HOST => ['entity_manager', 'connection', 'host'],
|
||||
DatabaseConfigCustomizer::PORT => ['entity_manager', 'connection', 'port'],
|
||||
], $pathCollection));
|
||||
|
||||
$this->setLanguage($this->mapExistingPathsToKeys([
|
||||
LanguageConfigCustomizer::DEFAULT_LANG => ['translator', 'locale'],
|
||||
], $pathCollection));
|
||||
|
||||
$this->setUrlShortener($this->mapExistingPathsToKeys([
|
||||
UrlShortenerConfigCustomizer::SCHEMA => ['url_shortener', 'domain', 'schema'],
|
||||
UrlShortenerConfigCustomizer::HOSTNAME => ['url_shortener', 'domain', 'hostname'],
|
||||
UrlShortenerConfigCustomizer::CHARS => ['url_shortener', 'shortcode_chars'],
|
||||
UrlShortenerConfigCustomizer::VALIDATE_URL => ['url_shortener', 'validate_url'],
|
||||
UrlShortenerConfigCustomizer::ENABLE_NOT_FOUND_REDIRECTION => [
|
||||
'url_shortener',
|
||||
'not_found_short_url',
|
||||
'enable_redirection',
|
||||
],
|
||||
UrlShortenerConfigCustomizer::NOT_FOUND_REDIRECT_TO => [
|
||||
'url_shortener',
|
||||
'not_found_short_url',
|
||||
'redirect_to',
|
||||
],
|
||||
], $pathCollection));
|
||||
}
|
||||
|
||||
private function mapExistingPathsToKeys(array $map, PathCollection $pathCollection): array
|
||||
{
|
||||
$result = [];
|
||||
foreach ($map as $key => $path) {
|
||||
if ($pathCollection->pathExists($path)) {
|
||||
$result[$key] = $pathCollection->getValueInPath($path);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getArrayCopy(): array
|
||||
{
|
||||
$dbDriver = $this->database[DatabaseConfigCustomizer::DRIVER] ?? '';
|
||||
$config = [
|
||||
'app_options' => [
|
||||
'secret_key' => $this->app[ApplicationConfigCustomizer::SECRET] ?? '',
|
||||
'disable_track_param' => $this->app[ApplicationConfigCustomizer::DISABLE_TRACK_PARAM] ?? null,
|
||||
],
|
||||
'delete_short_urls' => [
|
||||
'check_visits_threshold' => $this->app[ApplicationConfigCustomizer::CHECK_VISITS_THRESHOLD] ?? true,
|
||||
'visits_threshold' => $this->app[ApplicationConfigCustomizer::VISITS_THRESHOLD] ?? 15,
|
||||
],
|
||||
'entity_manager' => [
|
||||
'connection' => [
|
||||
'driver' => $dbDriver,
|
||||
],
|
||||
],
|
||||
'translator' => [
|
||||
'locale' => $this->language[LanguageConfigCustomizer::DEFAULT_LANG] ?? 'en',
|
||||
],
|
||||
'url_shortener' => [
|
||||
'domain' => [
|
||||
'schema' => $this->urlShortener[UrlShortenerConfigCustomizer::SCHEMA] ?? 'http',
|
||||
'hostname' => $this->urlShortener[UrlShortenerConfigCustomizer::HOSTNAME] ?? '',
|
||||
],
|
||||
'shortcode_chars' => $this->urlShortener[UrlShortenerConfigCustomizer::CHARS] ?? '',
|
||||
'validate_url' => $this->urlShortener[UrlShortenerConfigCustomizer::VALIDATE_URL] ?? true,
|
||||
'not_found_short_url' => [
|
||||
'enable_redirection' =>
|
||||
$this->urlShortener[UrlShortenerConfigCustomizer::ENABLE_NOT_FOUND_REDIRECTION] ?? false,
|
||||
'redirect_to' => $this->urlShortener[UrlShortenerConfigCustomizer::NOT_FOUND_REDIRECT_TO] ?? null,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// Build dynamic database config based on selected driver
|
||||
if ($dbDriver === 'pdo_sqlite') {
|
||||
$config['entity_manager']['connection']['path'] = self::SQLITE_DB_PATH;
|
||||
} else {
|
||||
$config['entity_manager']['connection']['user'] = $this->database[DatabaseConfigCustomizer::USER] ?? '';
|
||||
$config['entity_manager']['connection']['password'] =
|
||||
$this->database[DatabaseConfigCustomizer::PASSWORD] ?? '';
|
||||
$config['entity_manager']['connection']['dbname'] = $this->database[DatabaseConfigCustomizer::NAME] ?? '';
|
||||
$config['entity_manager']['connection']['host'] = $this->database[DatabaseConfigCustomizer::HOST] ?? '';
|
||||
$config['entity_manager']['connection']['port'] = $this->database[DatabaseConfigCustomizer::PORT] ?? '';
|
||||
|
||||
if ($dbDriver === 'pdo_mysql') {
|
||||
$config['entity_manager']['connection']['driverOptions'] = [
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Installer\Util;
|
||||
|
||||
use Shlinkio\Shlink\Installer\Exception\MissingRequiredOptionException;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
trait AskUtilsTrait
|
||||
{
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
private function askRequired(SymfonyStyle $io, string $optionName, string $question)
|
||||
{
|
||||
return $io->ask($question, null, function ($value) use ($optionName) {
|
||||
if (empty($value)) {
|
||||
throw MissingRequiredOptionException::fromOption($optionName);
|
||||
};
|
||||
|
||||
return $value;
|
||||
});
|
||||
}
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
<?php
|
||||
return [];
|
@ -1,140 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Installer\Command;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\MethodProphecy;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use ReflectionObject;
|
||||
use Shlinkio\Shlink\Installer\Command\InstallCommand;
|
||||
use Shlinkio\Shlink\Installer\Config\ConfigCustomizerManagerInterface;
|
||||
use Shlinkio\Shlink\Installer\Config\Plugin\ConfigCustomizerInterface;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Helper\ProcessHelper;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
use Symfony\Component\Filesystem\Exception\IOException;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Zend\Config\Writer\WriterInterface;
|
||||
|
||||
class InstallCommandTest extends TestCase
|
||||
{
|
||||
/** @var InstallCommand */
|
||||
private $command;
|
||||
/** @var CommandTester */
|
||||
private $commandTester;
|
||||
/** @var ObjectProphecy */
|
||||
private $configWriter;
|
||||
/** @var ObjectProphecy */
|
||||
private $filesystem;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$processMock = $this->prophesize(Process::class);
|
||||
$processMock->isSuccessful()->willReturn(true);
|
||||
$processHelper = $this->prophesize(ProcessHelper::class);
|
||||
$processHelper->getName()->willReturn('process');
|
||||
$processHelper->setHelperSet(Argument::any())->willReturn(null);
|
||||
$processHelper->run(Argument::cetera())->willReturn($processMock->reveal());
|
||||
|
||||
$this->filesystem = $this->prophesize(Filesystem::class);
|
||||
$this->filesystem->exists(Argument::cetera())->willReturn(false);
|
||||
|
||||
$this->configWriter = $this->prophesize(WriterInterface::class);
|
||||
|
||||
$configCustomizer = $this->prophesize(ConfigCustomizerInterface::class);
|
||||
$configCustomizers = $this->prophesize(ConfigCustomizerManagerInterface::class);
|
||||
$configCustomizers->get(Argument::cetera())->willReturn($configCustomizer->reveal());
|
||||
|
||||
$finder = $this->prophesize(PhpExecutableFinder::class);
|
||||
$finder->find(false)->willReturn('php');
|
||||
|
||||
$app = new Application();
|
||||
$helperSet = $app->getHelperSet();
|
||||
$helperSet->set($processHelper->reveal());
|
||||
$app->setHelperSet($helperSet);
|
||||
$this->command = new InstallCommand(
|
||||
$this->configWriter->reveal(),
|
||||
$this->filesystem->reveal(),
|
||||
$configCustomizers->reveal(),
|
||||
false,
|
||||
$finder->reveal()
|
||||
);
|
||||
$app->add($this->command);
|
||||
|
||||
$this->commandTester = new CommandTester($this->command);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function generatedConfigIsProperlyPersisted()
|
||||
{
|
||||
$this->configWriter->toFile(Argument::any(), Argument::type('array'), false)->shouldBeCalledOnce();
|
||||
$this->commandTester->execute([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function cachedConfigIsDeletedIfExists()
|
||||
{
|
||||
/** @var MethodProphecy $appConfigExists */
|
||||
$appConfigExists = $this->filesystem->exists('data/cache/app_config.php')->willReturn(true);
|
||||
/** @var MethodProphecy $appConfigRemove */
|
||||
$appConfigRemove = $this->filesystem->remove('data/cache/app_config.php')->willReturn(null);
|
||||
|
||||
$this->commandTester->execute([]);
|
||||
|
||||
$appConfigExists->shouldHaveBeenCalledOnce();
|
||||
$appConfigRemove->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function exceptionWhileDeletingCachedConfigCancelsProcess()
|
||||
{
|
||||
/** @var MethodProphecy $appConfigExists */
|
||||
$appConfigExists = $this->filesystem->exists('data/cache/app_config.php')->willReturn(true);
|
||||
/** @var MethodProphecy $appConfigRemove */
|
||||
$appConfigRemove = $this->filesystem->remove('data/cache/app_config.php')->willThrow(IOException::class);
|
||||
/** @var MethodProphecy $configToFile */
|
||||
$configToFile = $this->configWriter->toFile(Argument::cetera())->willReturn(true);
|
||||
|
||||
$this->commandTester->execute([]);
|
||||
|
||||
$appConfigExists->shouldHaveBeenCalledOnce();
|
||||
$appConfigRemove->shouldHaveBeenCalledOnce();
|
||||
$configToFile->shouldNotHaveBeenCalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function whenCommandIsUpdatePreviousConfigCanBeImported()
|
||||
{
|
||||
$ref = new ReflectionObject($this->command);
|
||||
$prop = $ref->getProperty('isUpdate');
|
||||
$prop->setAccessible(true);
|
||||
$prop->setValue($this->command, true);
|
||||
|
||||
/** @var MethodProphecy $importedConfigExists */
|
||||
$importedConfigExists = $this->filesystem->exists(
|
||||
__DIR__ . '/../../test-resources/' . InstallCommand::GENERATED_CONFIG_PATH
|
||||
)->willReturn(true);
|
||||
|
||||
$this->commandTester->setInputs([
|
||||
'',
|
||||
'/foo/bar/wrong_previous_shlink',
|
||||
'',
|
||||
__DIR__ . '/../../test-resources',
|
||||
]);
|
||||
$this->commandTester->execute([]);
|
||||
|
||||
$importedConfigExists->shouldHaveBeenCalled();
|
||||
}
|
||||
}
|
@ -1,171 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Installer\Config\Plugin;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Installer\Config\Plugin\ApplicationConfigCustomizer;
|
||||
use Shlinkio\Shlink\Installer\Exception\InvalidConfigOptionException;
|
||||
use Shlinkio\Shlink\Installer\Model\CustomizableAppConfig;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use function array_shift;
|
||||
use function strpos;
|
||||
|
||||
class ApplicationConfigCustomizerTest extends TestCase
|
||||
{
|
||||
/** @var ApplicationConfigCustomizer */
|
||||
private $plugin;
|
||||
/** @var ObjectProphecy */
|
||||
private $io;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->io = $this->prophesize(SymfonyStyle::class);
|
||||
$this->io->title(Argument::any())->willReturn(null);
|
||||
|
||||
$this->plugin = new ApplicationConfigCustomizer();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function configIsRequestedToTheUser()
|
||||
{
|
||||
$ask = $this->io->ask(Argument::cetera())->willReturn('asked');
|
||||
$confirm = $this->io->confirm(Argument::cetera())->willReturn(false);
|
||||
|
||||
$config = new CustomizableAppConfig();
|
||||
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertTrue($config->hasApp());
|
||||
$this->assertEquals([
|
||||
'SECRET' => 'asked',
|
||||
'DISABLE_TRACK_PARAM' => 'asked',
|
||||
'CHECK_VISITS_THRESHOLD' => false,
|
||||
], $config->getApp());
|
||||
$ask->shouldHaveBeenCalledTimes(2);
|
||||
$confirm->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function visitsThresholdIsRequestedIfCheckIsEnabled()
|
||||
{
|
||||
$ask = $this->io->ask(Argument::cetera())->will(function (array $args) {
|
||||
$message = array_shift($args);
|
||||
return strpos($message, 'What is the amount of visits') === 0 ? 20 : 'asked';
|
||||
});
|
||||
$confirm = $this->io->confirm(Argument::cetera())->willReturn(true);
|
||||
|
||||
$config = new CustomizableAppConfig();
|
||||
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertTrue($config->hasApp());
|
||||
$this->assertEquals([
|
||||
'SECRET' => 'asked',
|
||||
'DISABLE_TRACK_PARAM' => 'asked',
|
||||
'CHECK_VISITS_THRESHOLD' => true,
|
||||
'VISITS_THRESHOLD' => 20,
|
||||
], $config->getApp());
|
||||
$ask->shouldHaveBeenCalledTimes(3);
|
||||
$confirm->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function onlyMissingOptionsAreAsked()
|
||||
{
|
||||
$ask = $this->io->ask(Argument::cetera())->willReturn('disable_param');
|
||||
$config = new CustomizableAppConfig();
|
||||
$config->setApp([
|
||||
'SECRET' => 'foo',
|
||||
'CHECK_VISITS_THRESHOLD' => true,
|
||||
'VISITS_THRESHOLD' => 20,
|
||||
]);
|
||||
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertEquals([
|
||||
'SECRET' => 'foo',
|
||||
'DISABLE_TRACK_PARAM' => 'disable_param',
|
||||
'CHECK_VISITS_THRESHOLD' => true,
|
||||
'VISITS_THRESHOLD' => 20,
|
||||
], $config->getApp());
|
||||
$ask->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function noQuestionsAskedIfImportedConfigContainsEverything()
|
||||
{
|
||||
$ask = $this->io->ask(Argument::cetera())->willReturn('the_new_secret');
|
||||
|
||||
$config = new CustomizableAppConfig();
|
||||
$config->setApp([
|
||||
'SECRET' => 'foo',
|
||||
'DISABLE_TRACK_PARAM' => 'the_new_secret',
|
||||
'CHECK_VISITS_THRESHOLD' => true,
|
||||
'VISITS_THRESHOLD' => 20,
|
||||
]);
|
||||
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertEquals([
|
||||
'SECRET' => 'foo',
|
||||
'DISABLE_TRACK_PARAM' => 'the_new_secret',
|
||||
'CHECK_VISITS_THRESHOLD' => true,
|
||||
'VISITS_THRESHOLD' => 20,
|
||||
], $config->getApp());
|
||||
$ask->shouldNotHaveBeenCalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideInvalidValues
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function validateVisitsThresholdThrowsExceptionWhenProvidedValueIsInvalid($value)
|
||||
{
|
||||
$this->expectException(InvalidConfigOptionException::class);
|
||||
$this->plugin->validateVisitsThreshold($value);
|
||||
}
|
||||
|
||||
public function provideInvalidValues(): array
|
||||
{
|
||||
return [
|
||||
'string' => ['foo'],
|
||||
'empty string' => [''],
|
||||
'negative number' => [-5],
|
||||
'negative number as string' => ['-5'],
|
||||
'zero' => [0],
|
||||
'zero as string' => ['0'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideValidValues
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function validateVisitsThresholdCastsToIntWhenProvidedValueIsValid($value, int $expected)
|
||||
{
|
||||
$this->assertEquals($expected, $this->plugin->validateVisitsThreshold($value));
|
||||
}
|
||||
|
||||
public function provideValidValues(): array
|
||||
{
|
||||
return [
|
||||
'positive as string' => ['20', 20],
|
||||
'positive as integer' => [5, 5],
|
||||
'one as string' => ['1', 1],
|
||||
'one as integer' => [1, 1],
|
||||
];
|
||||
}
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Installer\Config\Plugin;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Installer\Config\Plugin\DatabaseConfigCustomizer;
|
||||
use Shlinkio\Shlink\Installer\Model\CustomizableAppConfig;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
|
||||
class DatabaseConfigCustomizerTest extends TestCase
|
||||
{
|
||||
/** @var DatabaseConfigCustomizer */
|
||||
private $plugin;
|
||||
/** @var ObjectProphecy */
|
||||
private $io;
|
||||
/** @var ObjectProphecy */
|
||||
private $filesystem;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->io = $this->prophesize(SymfonyStyle::class);
|
||||
$this->io->title(Argument::any())->willReturn(null);
|
||||
$this->filesystem = $this->prophesize(Filesystem::class);
|
||||
|
||||
$this->plugin = new DatabaseConfigCustomizer($this->filesystem->reveal());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function configIsRequestedToTheUser()
|
||||
{
|
||||
$choice = $this->io->choice(Argument::cetera())->willReturn('MySQL');
|
||||
$ask = $this->io->ask(Argument::cetera())->willReturn('param');
|
||||
$config = new CustomizableAppConfig();
|
||||
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertTrue($config->hasDatabase());
|
||||
$this->assertEquals([
|
||||
'DRIVER' => 'pdo_mysql',
|
||||
'NAME' => 'param',
|
||||
'USER' => 'param',
|
||||
'PASSWORD' => 'param',
|
||||
'HOST' => 'param',
|
||||
'PORT' => 'param',
|
||||
], $config->getDatabase());
|
||||
$choice->shouldHaveBeenCalledOnce();
|
||||
$ask->shouldHaveBeenCalledTimes(5);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function onlyMissingOptionsAreAsked()
|
||||
{
|
||||
$choice = $this->io->choice(Argument::cetera())->willReturn('MySQL');
|
||||
$ask = $this->io->ask(Argument::cetera())->willReturn('asked');
|
||||
|
||||
$config = new CustomizableAppConfig();
|
||||
$config->setDatabase([
|
||||
'DRIVER' => 'pdo_pgsql',
|
||||
'NAME' => 'foo',
|
||||
'PASSWORD' => 'foo',
|
||||
]);
|
||||
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertEquals([
|
||||
'DRIVER' => 'pdo_pgsql',
|
||||
'NAME' => 'foo',
|
||||
'USER' => 'asked',
|
||||
'PASSWORD' => 'foo',
|
||||
'HOST' => 'asked',
|
||||
'PORT' => 'asked',
|
||||
], $config->getDatabase());
|
||||
$choice->shouldNotHaveBeenCalled();
|
||||
$ask->shouldHaveBeenCalledTimes(3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function noQuestionsAskedIfImportedConfigContainsEverything()
|
||||
{
|
||||
$choice = $this->io->choice(Argument::cetera())->willReturn('MySQL');
|
||||
$ask = $this->io->ask(Argument::cetera())->willReturn('asked');
|
||||
|
||||
$config = new CustomizableAppConfig();
|
||||
$config->setDatabase([
|
||||
'DRIVER' => 'pdo_pgsql',
|
||||
'NAME' => 'foo',
|
||||
'USER' => 'foo',
|
||||
'PASSWORD' => 'foo',
|
||||
'HOST' => 'foo',
|
||||
'PORT' => 'foo',
|
||||
]);
|
||||
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertEquals([
|
||||
'DRIVER' => 'pdo_pgsql',
|
||||
'NAME' => 'foo',
|
||||
'USER' => 'foo',
|
||||
'PASSWORD' => 'foo',
|
||||
'HOST' => 'foo',
|
||||
'PORT' => 'foo',
|
||||
], $config->getDatabase());
|
||||
$choice->shouldNotHaveBeenCalled();
|
||||
$ask->shouldNotHaveBeenCalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function sqliteDatabaseIsImportedWhenRequested()
|
||||
{
|
||||
$copy = $this->filesystem->copy(Argument::cetera())->willReturn(null);
|
||||
|
||||
$config = new CustomizableAppConfig();
|
||||
$config->setDatabase([
|
||||
'DRIVER' => 'pdo_sqlite',
|
||||
]);
|
||||
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertEquals([
|
||||
'DRIVER' => 'pdo_sqlite',
|
||||
], $config->getDatabase());
|
||||
$copy->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Installer\Config\Plugin;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Installer\Config\Plugin\LanguageConfigCustomizer;
|
||||
use Shlinkio\Shlink\Installer\Model\CustomizableAppConfig;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
class LanguageConfigCustomizerTest extends TestCase
|
||||
{
|
||||
/** @var LanguageConfigCustomizer */
|
||||
private $plugin;
|
||||
/** @var ObjectProphecy */
|
||||
private $io;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->io = $this->prophesize(SymfonyStyle::class);
|
||||
$this->io->title(Argument::any())->willReturn(null);
|
||||
$this->plugin = new LanguageConfigCustomizer();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function configIsRequestedToTheUser()
|
||||
{
|
||||
$choice = $this->io->choice(Argument::cetera())->willReturn('en');
|
||||
$config = new CustomizableAppConfig();
|
||||
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertTrue($config->hasLanguage());
|
||||
$this->assertEquals([
|
||||
'DEFAULT' => 'en',
|
||||
], $config->getLanguage());
|
||||
$choice->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function onlyMissingOptionsAreAsked()
|
||||
{
|
||||
$choice = $this->io->choice(Argument::cetera())->willReturn('es');
|
||||
$config = new CustomizableAppConfig();
|
||||
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertEquals([
|
||||
'DEFAULT' => 'es',
|
||||
], $config->getLanguage());
|
||||
$choice->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function noQuestionsAskedIfImportedConfigContainsEverything()
|
||||
{
|
||||
$choice = $this->io->choice(Argument::cetera())->willReturn('en');
|
||||
|
||||
$config = new CustomizableAppConfig();
|
||||
$config->setLanguage([
|
||||
'DEFAULT' => 'es',
|
||||
]);
|
||||
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertEquals([
|
||||
'DEFAULT' => 'es',
|
||||
], $config->getLanguage());
|
||||
$choice->shouldNotHaveBeenCalled();
|
||||
}
|
||||
}
|
@ -1,148 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Installer\Config\Plugin;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Installer\Config\Plugin\UrlShortenerConfigCustomizer;
|
||||
use Shlinkio\Shlink\Installer\Model\CustomizableAppConfig;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
class UrlShortenerConfigCustomizerTest extends TestCase
|
||||
{
|
||||
/** @var UrlShortenerConfigCustomizer */
|
||||
private $plugin;
|
||||
/** @var ObjectProphecy */
|
||||
private $io;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->io = $this->prophesize(SymfonyStyle::class);
|
||||
$this->io->title(Argument::any())->willReturn(null);
|
||||
$this->plugin = new UrlShortenerConfigCustomizer(function () {
|
||||
return 'the_chars';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function configIsRequestedToTheUser()
|
||||
{
|
||||
$choice = $this->io->choice(Argument::cetera())->willReturn('chosen');
|
||||
$ask = $this->io->ask(Argument::cetera())->willReturn('asked');
|
||||
$confirm = $this->io->confirm(Argument::cetera())->willReturn(true);
|
||||
$config = new CustomizableAppConfig();
|
||||
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertTrue($config->hasUrlShortener());
|
||||
$this->assertEquals([
|
||||
'SCHEMA' => 'chosen',
|
||||
'HOSTNAME' => 'asked',
|
||||
'CHARS' => 'the_chars',
|
||||
'VALIDATE_URL' => true,
|
||||
'ENABLE_NOT_FOUND_REDIRECTION' => true,
|
||||
'NOT_FOUND_REDIRECT_TO' => 'asked',
|
||||
], $config->getUrlShortener());
|
||||
$ask->shouldHaveBeenCalledTimes(2);
|
||||
$choice->shouldHaveBeenCalledOnce();
|
||||
$confirm->shouldHaveBeenCalledTimes(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function onlyMissingOptionsAreAsked()
|
||||
{
|
||||
$choice = $this->io->choice(Argument::cetera())->willReturn('chosen');
|
||||
$ask = $this->io->ask(Argument::cetera())->willReturn('asked');
|
||||
$confirm = $this->io->confirm(Argument::cetera())->willReturn(false);
|
||||
$config = new CustomizableAppConfig();
|
||||
$config->setUrlShortener([
|
||||
'SCHEMA' => 'foo',
|
||||
'ENABLE_NOT_FOUND_REDIRECTION' => true,
|
||||
'NOT_FOUND_REDIRECT_TO' => 'foo',
|
||||
]);
|
||||
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertEquals([
|
||||
'SCHEMA' => 'foo',
|
||||
'HOSTNAME' => 'asked',
|
||||
'CHARS' => 'the_chars',
|
||||
'VALIDATE_URL' => false,
|
||||
'ENABLE_NOT_FOUND_REDIRECTION' => true,
|
||||
'NOT_FOUND_REDIRECT_TO' => 'foo',
|
||||
], $config->getUrlShortener());
|
||||
$choice->shouldNotHaveBeenCalled();
|
||||
$ask->shouldHaveBeenCalledOnce();
|
||||
$confirm->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function noQuestionsAskedIfImportedConfigContainsEverything()
|
||||
{
|
||||
$choice = $this->io->choice(Argument::cetera())->willReturn('chosen');
|
||||
$ask = $this->io->ask(Argument::cetera())->willReturn('asked');
|
||||
$confirm = $this->io->confirm(Argument::cetera())->willReturn(false);
|
||||
|
||||
$config = new CustomizableAppConfig();
|
||||
$config->setUrlShortener([
|
||||
'SCHEMA' => 'foo',
|
||||
'HOSTNAME' => 'foo',
|
||||
'CHARS' => 'foo',
|
||||
'VALIDATE_URL' => true,
|
||||
'ENABLE_NOT_FOUND_REDIRECTION' => true,
|
||||
'NOT_FOUND_REDIRECT_TO' => 'foo',
|
||||
]);
|
||||
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertEquals([
|
||||
'SCHEMA' => 'foo',
|
||||
'HOSTNAME' => 'foo',
|
||||
'CHARS' => 'foo',
|
||||
'VALIDATE_URL' => true,
|
||||
'ENABLE_NOT_FOUND_REDIRECTION' => true,
|
||||
'NOT_FOUND_REDIRECT_TO' => 'foo',
|
||||
], $config->getUrlShortener());
|
||||
$choice->shouldNotHaveBeenCalled();
|
||||
$ask->shouldNotHaveBeenCalled();
|
||||
$confirm->shouldNotHaveBeenCalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function redirectUrlOptionIsNotAskedIfAnswerToPreviousQuestionIsNo()
|
||||
{
|
||||
$ask = $this->io->ask(Argument::cetera())->willReturn('asked');
|
||||
$confirm = $this->io->confirm(Argument::cetera())->willReturn(false);
|
||||
|
||||
$config = new CustomizableAppConfig();
|
||||
$config->setUrlShortener([
|
||||
'SCHEMA' => 'foo',
|
||||
'HOSTNAME' => 'foo',
|
||||
'CHARS' => 'foo',
|
||||
'VALIDATE_URL' => true,
|
||||
]);
|
||||
|
||||
$this->plugin->process($this->io->reveal(), $config);
|
||||
|
||||
$this->assertTrue($config->hasUrlShortener());
|
||||
$this->assertEquals([
|
||||
'SCHEMA' => 'foo',
|
||||
'HOSTNAME' => 'foo',
|
||||
'CHARS' => 'foo',
|
||||
'VALIDATE_URL' => true,
|
||||
'ENABLE_NOT_FOUND_REDIRECTION' => false,
|
||||
], $config->getUrlShortener());
|
||||
$ask->shouldNotHaveBeenCalled();
|
||||
$confirm->shouldHaveBeenCalledOnce();
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Installer;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Installer\ConfigProvider;
|
||||
|
||||
class ConfigProviderTest extends TestCase
|
||||
{
|
||||
/** @var ConfigProvider */
|
||||
private $configProvider;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->configProvider = new ConfigProvider();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function configIsReturned()
|
||||
{
|
||||
$config = $this->configProvider->__invoke();
|
||||
$this->assertEmpty($config);
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Installer;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Installer\Config\Plugin\ApplicationConfigCustomizer;
|
||||
use Shlinkio\Shlink\Installer\Config\Plugin\LanguageConfigCustomizer;
|
||||
use Shlinkio\Shlink\Installer\Model\CustomizableAppConfig;
|
||||
|
||||
class CustomizableAppConfigTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function exchangeArrayIgnoresAnyNonProvidedKey()
|
||||
{
|
||||
$config = new CustomizableAppConfig();
|
||||
|
||||
$config->exchangeArray([
|
||||
'app_options' => [
|
||||
'disable_track_param' => null,
|
||||
],
|
||||
'translator' => [
|
||||
'locale' => 'es',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertFalse($config->hasDatabase());
|
||||
$this->assertFalse($config->hasUrlShortener());
|
||||
$this->assertTrue($config->hasApp());
|
||||
$this->assertTrue($config->hasLanguage());
|
||||
$this->assertEquals([
|
||||
ApplicationConfigCustomizer::DISABLE_TRACK_PARAM => null,
|
||||
], $config->getApp());
|
||||
$this->assertEquals([
|
||||
LanguageConfigCustomizer::DEFAULT_LANG => 'es',
|
||||
], $config->getLanguage());
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Installer\Exception;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Installer\Exception\MissingRequiredOptionException;
|
||||
|
||||
class MissingRequiredOptionExceptionTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function fromOptionsGeneratesExpectedMessage()
|
||||
{
|
||||
$e = MissingRequiredOptionException::fromOption('foo');
|
||||
$this->assertEquals('The "foo" is required and can\'t be empty', $e->getMessage());
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Installer\Factory;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Installer\Factory\InstallApplicationFactory;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
class InstallApplicationFactoryTest extends TestCase
|
||||
{
|
||||
/** @var InstallApplicationFactory */
|
||||
private $factory;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->factory = new InstallApplicationFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function serviceIsCreated(): void
|
||||
{
|
||||
$instance = ($this->factory)(new ServiceManager(['services' => [
|
||||
Filesystem::class => $this->prophesize(Filesystem::class)->reveal(),
|
||||
'config' => ['config_customizer_plugins' => []],
|
||||
]]), '');
|
||||
|
||||
$this->assertInstanceOf(Application::class, $instance);
|
||||
}
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
<?xml version="1.0"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/6.5/phpunit.xsd"
|
||||
bootstrap="./vendor/autoload.php"
|
||||
colors="true">
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/6.5/phpunit.xsd"
|
||||
bootstrap="./vendor/autoload.php"
|
||||
colors="true"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Common">
|
||||
<directory>./module/Common/test</directory>
|
||||
|
Loading…
Reference in New Issue
Block a user