mirror of
https://github.com/shlinkio/shlink.git
synced 2024-12-22 23:23:42 -06:00
Removed installer and used external package instead
This commit is contained in:
parent
171b43c517
commit
2059b4050b
10
bin/install
10
bin/install
@ -2,11 +2,7 @@
|
|||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\Installer;
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
use Symfony\Component\Console\Application;
|
$run = require __DIR__ . '/../vendor/shlinkio/shlink-installer/bin/run.php';
|
||||||
use Zend\ServiceManager\ServiceLocatorInterface;
|
$run(false);
|
||||||
|
|
||||||
/** @var ServiceLocatorInterface $container */
|
|
||||||
$container = include __DIR__ . '/../config/install-container.php';
|
|
||||||
$container->build(Application::class)->run();
|
|
||||||
|
10
bin/update
10
bin/update
@ -2,11 +2,7 @@
|
|||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\Installer;
|
namespace Shlinkio\Shlink;
|
||||||
|
|
||||||
use Symfony\Component\Console\Application;
|
$run = require __DIR__ . '/../vendor/shlinkio/shlink-installer/bin/run.php';
|
||||||
use Zend\ServiceManager\ServiceLocatorInterface;
|
$run(true);
|
||||||
|
|
||||||
/** @var ServiceLocatorInterface $container */
|
|
||||||
$container = include __DIR__ . '/../config/install-container.php';
|
|
||||||
$container->build(Application::class, ['isUpdate' => true])->run();
|
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
"lstrojny/functional-php": "^1.8",
|
"lstrojny/functional-php": "^1.8",
|
||||||
"mikehaertl/phpwkhtmltopdf": "^2.2",
|
"mikehaertl/phpwkhtmltopdf": "^2.2",
|
||||||
"monolog/monolog": "^1.21",
|
"monolog/monolog": "^1.21",
|
||||||
|
"shlinkio/shlink-installer": "^1.0",
|
||||||
"symfony/console": "^4.2",
|
"symfony/console": "^4.2",
|
||||||
"symfony/filesystem": "^4.2",
|
"symfony/filesystem": "^4.2",
|
||||||
"symfony/lock": "^4.2",
|
"symfony/lock": "^4.2",
|
||||||
@ -36,7 +37,7 @@
|
|||||||
"theorchard/monolog-cascade": "^0.4",
|
"theorchard/monolog-cascade": "^0.4",
|
||||||
"zendframework/zend-config": "^3.0",
|
"zendframework/zend-config": "^3.0",
|
||||||
"zendframework/zend-config-aggregator": "^1.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": "^3.0",
|
||||||
"zendframework/zend-expressive-fastroute": "^3.0",
|
"zendframework/zend-expressive-fastroute": "^3.0",
|
||||||
"zendframework/zend-expressive-helpers": "^5.0",
|
"zendframework/zend-expressive-helpers": "^5.0",
|
||||||
@ -68,8 +69,7 @@
|
|||||||
"Shlinkio\\Shlink\\CLI\\": "module/CLI/src",
|
"Shlinkio\\Shlink\\CLI\\": "module/CLI/src",
|
||||||
"Shlinkio\\Shlink\\Rest\\": "module/Rest/src",
|
"Shlinkio\\Shlink\\Rest\\": "module/Rest/src",
|
||||||
"Shlinkio\\Shlink\\Core\\": "module/Core/src",
|
"Shlinkio\\Shlink\\Core\\": "module/Core/src",
|
||||||
"Shlinkio\\Shlink\\Common\\": "module/Common/src",
|
"Shlinkio\\Shlink\\Common\\": "module/Common/src"
|
||||||
"Shlinkio\\Shlink\\Installer\\": "module/Installer/src"
|
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"module/Common/functions/functions.php"
|
"module/Common/functions/functions.php"
|
||||||
@ -87,8 +87,7 @@
|
|||||||
"ShlinkioTest\\Shlink\\Common\\": [
|
"ShlinkioTest\\Shlink\\Common\\": [
|
||||||
"module/Common/test",
|
"module/Common/test",
|
||||||
"module/Common/test-db"
|
"module/Common/test-db"
|
||||||
],
|
]
|
||||||
"ShlinkioTest\\Shlink\\Installer\\": "module/Installer/test"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -18,7 +18,6 @@ return (new ConfigAggregator\ConfigAggregator([
|
|||||||
Common\ConfigProvider::class,
|
Common\ConfigProvider::class,
|
||||||
Core\ConfigProvider::class,
|
Core\ConfigProvider::class,
|
||||||
CLI\ConfigProvider::class,
|
CLI\ConfigProvider::class,
|
||||||
Installer\ConfigProvider::class,
|
|
||||||
Rest\ConfigProvider::class,
|
Rest\ConfigProvider::class,
|
||||||
new ConfigAggregator\PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'),
|
new ConfigAggregator\PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'),
|
||||||
new ConfigAggregator\ZendConfigProvider('config/params/{generated_config.php,*.config.{php,json}}'),
|
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,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);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user