mirror of
https://github.com/shlinkio/shlink.git
synced 2025-02-25 18:45:27 -06:00
Moved all configuration customization steps to individual plugins
This commit is contained in:
5
bin/cli
5
bin/cli
@@ -5,7 +5,4 @@ use Symfony\Component\Console\Application as CliApp;
|
|||||||
|
|
||||||
/** @var ContainerInterface $container */
|
/** @var ContainerInterface $container */
|
||||||
$container = include __DIR__ . '/../config/container.php';
|
$container = include __DIR__ . '/../config/container.php';
|
||||||
|
$container->get(CliApp::class)->run();
|
||||||
/** @var CliApp $app */
|
|
||||||
$app = $container->get(CliApp::class);
|
|
||||||
$app->run();
|
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
<?php
|
<?php
|
||||||
use Shlinkio\Shlink\CLI\Factory\InstallApplicationFactory;
|
use Shlinkio\Shlink\CLI\Factory\InstallApplicationFactory;
|
||||||
use Symfony\Component\Console\Application;
|
use Symfony\Component\Console\Application;
|
||||||
|
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||||
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||||
use Zend\ServiceManager\ServiceManager;
|
use Zend\ServiceManager\ServiceManager;
|
||||||
|
|
||||||
chdir(dirname(__DIR__));
|
chdir(dirname(__DIR__));
|
||||||
@@ -10,5 +13,7 @@ require __DIR__ . '/../vendor/autoload.php';
|
|||||||
|
|
||||||
$container = new ServiceManager(['factories' => [
|
$container = new ServiceManager(['factories' => [
|
||||||
Application::class => InstallApplicationFactory::class,
|
Application::class => InstallApplicationFactory::class,
|
||||||
|
Filesystem::class => InvokableFactory::class,
|
||||||
|
QuestionHelper::class => InvokableFactory::class,
|
||||||
]]);
|
]]);
|
||||||
$container->build(Application::class)->run();
|
$container->build(Application::class)->run();
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
<?php
|
<?php
|
||||||
use Shlinkio\Shlink\CLI\Factory\InstallApplicationFactory;
|
use Shlinkio\Shlink\CLI\Factory\InstallApplicationFactory;
|
||||||
use Symfony\Component\Console\Application;
|
use Symfony\Component\Console\Application;
|
||||||
|
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||||
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
use Zend\ServiceManager\Factory\InvokableFactory;
|
||||||
use Zend\ServiceManager\ServiceManager;
|
use Zend\ServiceManager\ServiceManager;
|
||||||
|
|
||||||
chdir(dirname(__DIR__));
|
chdir(dirname(__DIR__));
|
||||||
@@ -10,5 +13,7 @@ require __DIR__ . '/../vendor/autoload.php';
|
|||||||
|
|
||||||
$container = new ServiceManager(['factories' => [
|
$container = new ServiceManager(['factories' => [
|
||||||
Application::class => InstallApplicationFactory::class,
|
Application::class => InstallApplicationFactory::class,
|
||||||
|
Filesystem::class => InvokableFactory::class,
|
||||||
|
QuestionHelper::class => InvokableFactory::class,
|
||||||
]]);
|
]]);
|
||||||
$container->build(Application::class, ['isUpdate' => true])->run();
|
$container->build(Application::class, ['isUpdate' => true])->run();
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace Shlinkio\Shlink\CLI\Command\Install;
|
namespace Shlinkio\Shlink\CLI\Command\Install;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\CLI\Install\ConfigCustomizerPluginManagerInterface;
|
||||||
|
use Shlinkio\Shlink\CLI\Install\Plugin;
|
||||||
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||||
use Shlinkio\Shlink\Common\Util\StringUtilsTrait;
|
use Shlinkio\Shlink\Common\Util\StringUtilsTrait;
|
||||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Exception\LogicException;
|
use Symfony\Component\Console\Exception\LogicException;
|
||||||
use Symfony\Component\Console\Exception\RuntimeException;
|
use Symfony\Component\Console\Exception\RuntimeException;
|
||||||
@@ -11,7 +12,6 @@ use Symfony\Component\Console\Helper\ProcessHelper;
|
|||||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
|
||||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||||
use Symfony\Component\Console\Question\Question;
|
use Symfony\Component\Console\Question\Question;
|
||||||
use Symfony\Component\Filesystem\Exception\IOException;
|
use Symfony\Component\Filesystem\Exception\IOException;
|
||||||
@@ -20,14 +20,6 @@ use Zend\Config\Writer\WriterInterface;
|
|||||||
|
|
||||||
class InstallCommand extends Command
|
class InstallCommand extends Command
|
||||||
{
|
{
|
||||||
use StringUtilsTrait;
|
|
||||||
|
|
||||||
const DATABASE_DRIVERS = [
|
|
||||||
'MySQL' => 'pdo_mysql',
|
|
||||||
'PostgreSQL' => 'pdo_pgsql',
|
|
||||||
'SQLite' => 'pdo_sqlite',
|
|
||||||
];
|
|
||||||
const SUPPORTED_LANGUAGES = ['en', 'es'];
|
|
||||||
const GENERATED_CONFIG_PATH = 'config/params/generated_config.php';
|
const GENERATED_CONFIG_PATH = 'config/params/generated_config.php';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,22 +38,22 @@ class InstallCommand extends Command
|
|||||||
* @var ProcessHelper
|
* @var ProcessHelper
|
||||||
*/
|
*/
|
||||||
private $processHelper;
|
private $processHelper;
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $importedInstallationPath;
|
|
||||||
/**
|
/**
|
||||||
* @var WriterInterface
|
* @var WriterInterface
|
||||||
*/
|
*/
|
||||||
private $configWriter;
|
private $configWriter;
|
||||||
/**
|
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
private $isUpdate;
|
|
||||||
/**
|
/**
|
||||||
* @var Filesystem
|
* @var Filesystem
|
||||||
*/
|
*/
|
||||||
private $filesystem;
|
private $filesystem;
|
||||||
|
/**
|
||||||
|
* @var ConfigCustomizerPluginManagerInterface
|
||||||
|
*/
|
||||||
|
private $configCustomizers;
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $isUpdate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* InstallCommand constructor.
|
* InstallCommand constructor.
|
||||||
@@ -70,18 +62,24 @@ class InstallCommand extends Command
|
|||||||
* @param bool $isUpdate
|
* @param bool $isUpdate
|
||||||
* @throws LogicException
|
* @throws LogicException
|
||||||
*/
|
*/
|
||||||
public function __construct(WriterInterface $configWriter, Filesystem $filesystem, $isUpdate = false)
|
public function __construct(
|
||||||
{
|
WriterInterface $configWriter,
|
||||||
|
Filesystem $filesystem,
|
||||||
|
ConfigCustomizerPluginManagerInterface $configCustomizers,
|
||||||
|
$isUpdate = false
|
||||||
|
) {
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
$this->configWriter = $configWriter;
|
$this->configWriter = $configWriter;
|
||||||
$this->isUpdate = $isUpdate;
|
$this->isUpdate = $isUpdate;
|
||||||
$this->filesystem = $filesystem;
|
$this->filesystem = $filesystem;
|
||||||
|
$this->configCustomizers = $configCustomizers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function configure()
|
public function configure()
|
||||||
{
|
{
|
||||||
$this->setName('shlink:install')
|
$this
|
||||||
->setDescription('Installs Shlink');
|
->setName('shlink:install')
|
||||||
|
->setDescription('Installs or updates Shlink');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function execute(InputInterface $input, OutputInterface $output)
|
public function execute(InputInterface $input, OutputInterface $output)
|
||||||
@@ -118,10 +116,16 @@ class InstallCommand extends Command
|
|||||||
$config = $this->isUpdate ? $this->importConfig() : new CustomizableAppConfig();
|
$config = $this->isUpdate ? $this->importConfig() : new CustomizableAppConfig();
|
||||||
|
|
||||||
// Ask for custom config params
|
// Ask for custom config params
|
||||||
$this->askDatabase($config);
|
foreach ([
|
||||||
$this->askUrlShortener($config);
|
Plugin\DatabaseConfigCustomizerPlugin::class,
|
||||||
$this->askLanguage($config);
|
Plugin\UrlShortenerConfigCustomizerPlugin::class,
|
||||||
$this->askApplication($config);
|
Plugin\LanguageConfigCustomizerPlugin::class,
|
||||||
|
Plugin\ApplicationConfigCustomizerPlugin::class,
|
||||||
|
] as $pluginName) {
|
||||||
|
/** @var Plugin\ConfigCustomizerPluginInterface $configCustomizer */
|
||||||
|
$configCustomizer = $this->configCustomizers->get($pluginName);
|
||||||
|
$configCustomizer->process($input, $output, $config);
|
||||||
|
}
|
||||||
|
|
||||||
// Generate config params files
|
// Generate config params files
|
||||||
$this->configWriter->toFile(self::GENERATED_CONFIG_PATH, $config->getArrayCopy(), false);
|
$this->configWriter->toFile(self::GENERATED_CONFIG_PATH, $config->getArrayCopy(), false);
|
||||||
@@ -170,10 +174,10 @@ class InstallCommand extends Command
|
|||||||
// Ask the user for the older shlink path
|
// Ask the user for the older shlink path
|
||||||
$keepAsking = true;
|
$keepAsking = true;
|
||||||
do {
|
do {
|
||||||
$this->importedInstallationPath = $this->ask(
|
$config->setImportedInstallationPath($this->ask(
|
||||||
'Previous shlink installation path from which to import config'
|
'Previous shlink installation path from which to import config'
|
||||||
);
|
));
|
||||||
$configFile = $this->importedInstallationPath . '/' . self::GENERATED_CONFIG_PATH;
|
$configFile = $config->getImportedInstallationPath() . '/' . self::GENERATED_CONFIG_PATH;
|
||||||
$configExists = $this->filesystem->exists($configFile);
|
$configExists = $this->filesystem->exists($configFile);
|
||||||
|
|
||||||
if (! $configExists) {
|
if (! $configExists) {
|
||||||
@@ -194,151 +198,6 @@ class InstallCommand extends Command
|
|||||||
return $config;
|
return $config;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function askDatabase(CustomizableAppConfig $config)
|
|
||||||
{
|
|
||||||
$this->printTitle('DATABASE');
|
|
||||||
|
|
||||||
if ($config->hasDatabase()) {
|
|
||||||
$keepConfig = $this->questionHelper->ask($this->input, $this->output, new ConfirmationQuestion(
|
|
||||||
'<question>Do you want to keep imported database config? (Y/n):</question> '
|
|
||||||
));
|
|
||||||
if ($keepConfig) {
|
|
||||||
// If the user selected to keep DB config and is configured to use sqlite, copy DB file
|
|
||||||
if ($config->getDatabase()['DRIVER'] === self::DATABASE_DRIVERS['SQLite']) {
|
|
||||||
$this->filesystem->copy(
|
|
||||||
$this->importedInstallationPath . '/' . CustomizableAppConfig::SQLITE_DB_PATH,
|
|
||||||
CustomizableAppConfig::SQLITE_DB_PATH
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select database type
|
|
||||||
$params = [];
|
|
||||||
$databases = array_keys(self::DATABASE_DRIVERS);
|
|
||||||
$dbType = $this->questionHelper->ask($this->input, $this->output, new ChoiceQuestion(
|
|
||||||
'<question>Select database type (defaults to ' . $databases[0] . '):</question>',
|
|
||||||
$databases,
|
|
||||||
0
|
|
||||||
));
|
|
||||||
$params['DRIVER'] = self::DATABASE_DRIVERS[$dbType];
|
|
||||||
|
|
||||||
// Ask for connection params if database is not SQLite
|
|
||||||
if ($params['DRIVER'] !== self::DATABASE_DRIVERS['SQLite']) {
|
|
||||||
$params['NAME'] = $this->ask('Database name', 'shlink');
|
|
||||||
$params['USER'] = $this->ask('Database username');
|
|
||||||
$params['PASSWORD'] = $this->ask('Database password');
|
|
||||||
$params['HOST'] = $this->ask('Database host', 'localhost');
|
|
||||||
$params['PORT'] = $this->ask('Database port', $this->getDefaultDbPort($params['DRIVER']));
|
|
||||||
}
|
|
||||||
|
|
||||||
$config->setDatabase($params);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getDefaultDbPort($driver)
|
|
||||||
{
|
|
||||||
return $driver === 'pdo_mysql' ? '3306' : '5432';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function askUrlShortener(CustomizableAppConfig $config)
|
|
||||||
{
|
|
||||||
$this->printTitle('URL SHORTENER');
|
|
||||||
|
|
||||||
if ($config->hasUrlShortener()) {
|
|
||||||
$keepConfig = $this->questionHelper->ask($this->input, $this->output, new ConfirmationQuestion(
|
|
||||||
'<question>Do you want to keep imported URL shortener config? (Y/n):</question> '
|
|
||||||
));
|
|
||||||
if ($keepConfig) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ask for URL shortener params
|
|
||||||
$config->setUrlShortener([
|
|
||||||
'SCHEMA' => $this->questionHelper->ask($this->input, $this->output, new ChoiceQuestion(
|
|
||||||
'<question>Select schema for generated short URLs (defaults to http):</question>',
|
|
||||||
['http', 'https'],
|
|
||||||
0
|
|
||||||
)),
|
|
||||||
'HOSTNAME' => $this->ask('Hostname for generated URLs'),
|
|
||||||
'CHARS' => $this->ask(
|
|
||||||
'Character set for generated short codes (leave empty to autogenerate one)',
|
|
||||||
null,
|
|
||||||
true
|
|
||||||
) ?: str_shuffle(UrlShortener::DEFAULT_CHARS)
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function askLanguage(CustomizableAppConfig $config)
|
|
||||||
{
|
|
||||||
$this->printTitle('LANGUAGE');
|
|
||||||
|
|
||||||
if ($config->hasLanguage()) {
|
|
||||||
$keepConfig = $this->questionHelper->ask($this->input, $this->output, new ConfirmationQuestion(
|
|
||||||
'<question>Do you want to keep imported language? (Y/n):</question> '
|
|
||||||
));
|
|
||||||
if ($keepConfig) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$config->setLanguage([
|
|
||||||
'DEFAULT' => $this->questionHelper->ask($this->input, $this->output, new ChoiceQuestion(
|
|
||||||
'<question>Select default language for the application in general (defaults to '
|
|
||||||
. self::SUPPORTED_LANGUAGES[0] . '):</question>',
|
|
||||||
self::SUPPORTED_LANGUAGES,
|
|
||||||
0
|
|
||||||
)),
|
|
||||||
'CLI' => $this->questionHelper->ask($this->input, $this->output, new ChoiceQuestion(
|
|
||||||
'<question>Select default language for CLI executions (defaults to '
|
|
||||||
. self::SUPPORTED_LANGUAGES[0] . '):</question>',
|
|
||||||
self::SUPPORTED_LANGUAGES,
|
|
||||||
0
|
|
||||||
)),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function askApplication(CustomizableAppConfig $config)
|
|
||||||
{
|
|
||||||
$this->printTitle('APPLICATION');
|
|
||||||
|
|
||||||
if ($config->hasApp()) {
|
|
||||||
$keepConfig = $this->questionHelper->ask($this->input, $this->output, new ConfirmationQuestion(
|
|
||||||
'<question>Do you want to keep imported application config? (Y/n):</question> '
|
|
||||||
));
|
|
||||||
if ($keepConfig) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$config->setApp([
|
|
||||||
'SECRET' => $this->ask(
|
|
||||||
'Define a secret string that will be used to sign API tokens (leave empty to autogenerate one)',
|
|
||||||
null,
|
|
||||||
true
|
|
||||||
) ?: $this->generateRandomString(32),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $text
|
|
||||||
*/
|
|
||||||
protected function printTitle($text)
|
|
||||||
{
|
|
||||||
$text = trim($text);
|
|
||||||
$length = strlen($text) + 4;
|
|
||||||
$header = str_repeat('*', $length);
|
|
||||||
|
|
||||||
$this->output->writeln([
|
|
||||||
'',
|
|
||||||
'<info>' . $header . '</info>',
|
|
||||||
'<info>* ' . strtoupper($text) . ' *</info>',
|
|
||||||
'<info>' . $header . '</info>',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $text
|
* @param string $text
|
||||||
* @param string|null $default
|
* @param string|null $default
|
||||||
|
|||||||
@@ -3,10 +3,15 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Factory;
|
namespace Shlinkio\Shlink\CLI\Factory;
|
||||||
|
|
||||||
|
use Acelaya\ZsmAnnotatedServices\Factory\V3\AnnotatedFactory;
|
||||||
use Interop\Container\ContainerInterface;
|
use Interop\Container\ContainerInterface;
|
||||||
use Interop\Container\Exception\ContainerException;
|
use Interop\Container\Exception\ContainerException;
|
||||||
use Shlinkio\Shlink\CLI\Command\Install\InstallCommand;
|
use Shlinkio\Shlink\CLI\Command\Install\InstallCommand;
|
||||||
|
use Shlinkio\Shlink\CLI\Install\ConfigCustomizerPluginManager;
|
||||||
|
use Shlinkio\Shlink\CLI\Install\Plugin;
|
||||||
|
use Shlinkio\Shlink\CLI\Install\Plugin\Factory\DefaultConfigCustomizerPluginFactory;
|
||||||
use Symfony\Component\Console\Application;
|
use Symfony\Component\Console\Application;
|
||||||
|
use Symfony\Component\Console\Exception\LogicException;
|
||||||
use Symfony\Component\Filesystem\Filesystem;
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
use Zend\Config\Writer\PhpArray;
|
use Zend\Config\Writer\PhpArray;
|
||||||
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||||
@@ -22,6 +27,7 @@ class InstallApplicationFactory implements FactoryInterface
|
|||||||
* @param string $requestedName
|
* @param string $requestedName
|
||||||
* @param null|array $options
|
* @param null|array $options
|
||||||
* @return object
|
* @return object
|
||||||
|
* @throws LogicException
|
||||||
* @throws ServiceNotFoundException if unable to resolve the service.
|
* @throws ServiceNotFoundException if unable to resolve the service.
|
||||||
* @throws ServiceNotCreatedException if an exception is raised when
|
* @throws ServiceNotCreatedException if an exception is raised when
|
||||||
* creating a service.
|
* creating a service.
|
||||||
@@ -32,7 +38,17 @@ class InstallApplicationFactory implements FactoryInterface
|
|||||||
$isUpdate = $options !== null && isset($options['isUpdate']) ? (bool) $options['isUpdate'] : false;
|
$isUpdate = $options !== null && isset($options['isUpdate']) ? (bool) $options['isUpdate'] : false;
|
||||||
|
|
||||||
$app = new Application();
|
$app = new Application();
|
||||||
$command = new InstallCommand(new PhpArray(), new Filesystem(), $isUpdate);
|
$command = new InstallCommand(
|
||||||
|
new PhpArray(),
|
||||||
|
$container->get(Filesystem::class),
|
||||||
|
new ConfigCustomizerPluginManager($container, ['factories' => [
|
||||||
|
Plugin\DatabaseConfigCustomizerPlugin::class => AnnotatedFactory::class,
|
||||||
|
Plugin\UrlShortenerConfigCustomizerPlugin::class => DefaultConfigCustomizerPluginFactory::class,
|
||||||
|
Plugin\LanguageConfigCustomizerPlugin::class => DefaultConfigCustomizerPluginFactory::class,
|
||||||
|
Plugin\ApplicationConfigCustomizerPlugin::class => DefaultConfigCustomizerPluginFactory::class,
|
||||||
|
]]),
|
||||||
|
$isUpdate
|
||||||
|
);
|
||||||
$app->add($command);
|
$app->add($command);
|
||||||
$app->setDefaultCommand($command->getName());
|
$app->setDefaultCommand($command->getName());
|
||||||
|
|
||||||
|
|||||||
10
module/CLI/src/Install/ConfigCustomizerPluginManager.php
Normal file
10
module/CLI/src/Install/ConfigCustomizerPluginManager.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
namespace Shlinkio\Shlink\CLI\Install;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\CLI\Install\Plugin\ConfigCustomizerPluginInterface;
|
||||||
|
use Zend\ServiceManager\AbstractPluginManager;
|
||||||
|
|
||||||
|
class ConfigCustomizerPluginManager extends AbstractPluginManager implements ConfigCustomizerPluginManagerInterface
|
||||||
|
{
|
||||||
|
protected $instanceOf = ConfigCustomizerPluginInterface::class;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
namespace Shlinkio\Shlink\CLI\Install;
|
||||||
|
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
|
||||||
|
interface ConfigCustomizerPluginManagerInterface extends ContainerInterface
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Exception\RuntimeException;
|
||||||
|
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Question\Question;
|
||||||
|
|
||||||
|
abstract class AbstractConfigCustomizerPlugin implements ConfigCustomizerPluginInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var QuestionHelper
|
||||||
|
*/
|
||||||
|
protected $questionHelper;
|
||||||
|
|
||||||
|
public function __construct(QuestionHelper $questionHelper)
|
||||||
|
{
|
||||||
|
$this->questionHelper = $questionHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InputInterface $input
|
||||||
|
* @param OutputInterface $output
|
||||||
|
* @param string $text
|
||||||
|
* @param string|null $default
|
||||||
|
* @param bool $allowEmpty
|
||||||
|
* @return string
|
||||||
|
* @throws RuntimeException
|
||||||
|
*/
|
||||||
|
protected function ask(InputInterface $input, OutputInterface $output, $text, $default = null, $allowEmpty = false)
|
||||||
|
{
|
||||||
|
if ($default !== null) {
|
||||||
|
$text .= ' (defaults to ' . $default . ')';
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
$value = $this->questionHelper->ask($input, $output, new Question(
|
||||||
|
'<question>' . $text . ':</question> ',
|
||||||
|
$default
|
||||||
|
));
|
||||||
|
if (empty($value) && ! $allowEmpty) {
|
||||||
|
$output->writeln('<error>Value can\'t be empty</error>');
|
||||||
|
}
|
||||||
|
} while (empty($value) && $default === null && ! $allowEmpty);
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param OutputInterface $output
|
||||||
|
* @param string $text
|
||||||
|
*/
|
||||||
|
protected function printTitle(OutputInterface $output, $text)
|
||||||
|
{
|
||||||
|
$text = trim($text);
|
||||||
|
$length = strlen($text) + 4;
|
||||||
|
$header = str_repeat('*', $length);
|
||||||
|
|
||||||
|
$output->writeln([
|
||||||
|
'',
|
||||||
|
'<info>' . $header . '</info>',
|
||||||
|
'<info>* ' . strtoupper($text) . ' *</info>',
|
||||||
|
'<info>' . $header . '</info>',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||||
|
use Shlinkio\Shlink\Common\Util\StringUtilsTrait;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||||
|
|
||||||
|
class ApplicationConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin
|
||||||
|
{
|
||||||
|
use StringUtilsTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InputInterface $input
|
||||||
|
* @param OutputInterface $output
|
||||||
|
* @param CustomizableAppConfig $appConfig
|
||||||
|
* @return void
|
||||||
|
* @throws \Symfony\Component\Console\Exception\RuntimeException
|
||||||
|
*/
|
||||||
|
public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig)
|
||||||
|
{
|
||||||
|
$this->printTitle($output, 'APPLICATION');
|
||||||
|
|
||||||
|
if ($appConfig->hasApp()) {
|
||||||
|
$keepConfig = $this->questionHelper->ask($input, $output, new ConfirmationQuestion(
|
||||||
|
'<question>Do you want to keep imported application config? (Y/n):</question> '
|
||||||
|
));
|
||||||
|
if ($keepConfig) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$appConfig->setApp([
|
||||||
|
'SECRET' => $this->ask(
|
||||||
|
$input,
|
||||||
|
$output,
|
||||||
|
'Define a secret string that will be used to sign API tokens (leave empty to autogenerate one)',
|
||||||
|
null,
|
||||||
|
true
|
||||||
|
) ?: $this->generateRandomString(32),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
interface ConfigCustomizerPluginInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param InputInterface $input
|
||||||
|
* @param OutputInterface $output
|
||||||
|
* @param CustomizableAppConfig $appConfig
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig);
|
||||||
|
}
|
||||||
101
module/CLI/src/Install/Plugin/DatabaseConfigCustomizerPlugin.php
Normal file
101
module/CLI/src/Install/Plugin/DatabaseConfigCustomizerPlugin.php
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
||||||
|
|
||||||
|
use Acelaya\ZsmAnnotatedServices\Annotation as DI;
|
||||||
|
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||||
|
use Symfony\Component\Console\Exception\RuntimeException;
|
||||||
|
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||||
|
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||||
|
use Symfony\Component\Filesystem\Exception\IOException;
|
||||||
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
|
||||||
|
class DatabaseConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin
|
||||||
|
{
|
||||||
|
const DATABASE_DRIVERS = [
|
||||||
|
'MySQL' => 'pdo_mysql',
|
||||||
|
'PostgreSQL' => 'pdo_pgsql',
|
||||||
|
'SQLite' => 'pdo_sqlite',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Filesystem
|
||||||
|
*/
|
||||||
|
private $filesystem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DatabaseConfigCustomizerPlugin constructor.
|
||||||
|
* @param QuestionHelper $questionHelper
|
||||||
|
* @param Filesystem $filesystem
|
||||||
|
*
|
||||||
|
* @DI\Inject({QuestionHelper::class, Filesystem::class})
|
||||||
|
*/
|
||||||
|
public function __construct(QuestionHelper $questionHelper, Filesystem $filesystem)
|
||||||
|
{
|
||||||
|
parent::__construct($questionHelper);
|
||||||
|
$this->filesystem = $filesystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InputInterface $input
|
||||||
|
* @param OutputInterface $output
|
||||||
|
* @param CustomizableAppConfig $appConfig
|
||||||
|
* @return void
|
||||||
|
* @throws IOException
|
||||||
|
* @throws RuntimeException
|
||||||
|
*/
|
||||||
|
public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig)
|
||||||
|
{
|
||||||
|
$this->printTitle($output, 'DATABASE');
|
||||||
|
|
||||||
|
if ($appConfig->hasDatabase()) {
|
||||||
|
$keepConfig = $this->questionHelper->ask($input, $output, new ConfirmationQuestion(
|
||||||
|
'<question>Do you want to keep imported database config? (Y/n):</question> '
|
||||||
|
));
|
||||||
|
if ($keepConfig) {
|
||||||
|
// If the user selected to keep DB config and is configured to use sqlite, copy DB file
|
||||||
|
if ($appConfig->getDatabase()['DRIVER'] === self::DATABASE_DRIVERS['SQLite']) {
|
||||||
|
try {
|
||||||
|
$this->filesystem->copy(
|
||||||
|
$appConfig->getImportedInstallationPath() . '/' . CustomizableAppConfig::SQLITE_DB_PATH,
|
||||||
|
CustomizableAppConfig::SQLITE_DB_PATH
|
||||||
|
);
|
||||||
|
} catch (IOException $e) {
|
||||||
|
$output->writeln('<error>It wasn\'t possible to import the SQLite database</error>');
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select database type
|
||||||
|
$params = [];
|
||||||
|
$databases = array_keys(self::DATABASE_DRIVERS);
|
||||||
|
$dbType = $this->questionHelper->ask($input, $output, new ChoiceQuestion(
|
||||||
|
'<question>Select database type (defaults to ' . $databases[0] . '):</question>',
|
||||||
|
$databases,
|
||||||
|
0
|
||||||
|
));
|
||||||
|
$params['DRIVER'] = self::DATABASE_DRIVERS[$dbType];
|
||||||
|
|
||||||
|
// Ask for connection params if database is not SQLite
|
||||||
|
if ($params['DRIVER'] !== self::DATABASE_DRIVERS['SQLite']) {
|
||||||
|
$params['NAME'] = $this->ask($input, $output, 'Database name', 'shlink');
|
||||||
|
$params['USER'] = $this->ask($input, $output, 'Database username');
|
||||||
|
$params['PASSWORD'] = $this->ask($input, $output, 'Database password');
|
||||||
|
$params['HOST'] = $this->ask($input, $output, 'Database host', 'localhost');
|
||||||
|
$params['PORT'] = $this->ask($input, $output, 'Database port', $this->getDefaultDbPort($params['DRIVER']));
|
||||||
|
}
|
||||||
|
|
||||||
|
$appConfig->setDatabase($params);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getDefaultDbPort($driver)
|
||||||
|
{
|
||||||
|
return $driver === 'pdo_mysql' ? '3306' : '5432';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\CLI\Install\Plugin\Factory;
|
||||||
|
|
||||||
|
use Interop\Container\ContainerInterface;
|
||||||
|
use Interop\Container\Exception\ContainerException;
|
||||||
|
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||||
|
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
|
||||||
|
use Zend\ServiceManager\Exception\ServiceNotFoundException;
|
||||||
|
use Zend\ServiceManager\Factory\FactoryInterface;
|
||||||
|
|
||||||
|
class DefaultConfigCustomizerPluginFactory implements FactoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create an object
|
||||||
|
*
|
||||||
|
* @param ContainerInterface $container
|
||||||
|
* @param string $requestedName
|
||||||
|
* @param null|array $options
|
||||||
|
* @return object
|
||||||
|
* @throws ServiceNotFoundException if unable to resolve the service.
|
||||||
|
* @throws ServiceNotCreatedException if an exception is raised when
|
||||||
|
* creating a service.
|
||||||
|
* @throws ContainerException if any other error occurs
|
||||||
|
*/
|
||||||
|
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
|
||||||
|
{
|
||||||
|
return new $requestedName($container->get(QuestionHelper::class));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||||
|
use Symfony\Component\Console\Exception\RuntimeException;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||||
|
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||||
|
|
||||||
|
class LanguageConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin
|
||||||
|
{
|
||||||
|
const SUPPORTED_LANGUAGES = ['en', 'es'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InputInterface $input
|
||||||
|
* @param OutputInterface $output
|
||||||
|
* @param CustomizableAppConfig $appConfig
|
||||||
|
* @return void
|
||||||
|
* @throws RuntimeException
|
||||||
|
*/
|
||||||
|
public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig)
|
||||||
|
{
|
||||||
|
$this->printTitle($output, 'LANGUAGE');
|
||||||
|
|
||||||
|
if ($appConfig->hasLanguage()) {
|
||||||
|
$keepConfig = $this->questionHelper->ask($input, $output, new ConfirmationQuestion(
|
||||||
|
'<question>Do you want to keep imported language? (Y/n):</question> '
|
||||||
|
));
|
||||||
|
if ($keepConfig) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$appConfig->setLanguage([
|
||||||
|
'DEFAULT' => $this->questionHelper->ask($input, $output, new ChoiceQuestion(
|
||||||
|
'<question>Select default language for the application in general (defaults to '
|
||||||
|
. self::SUPPORTED_LANGUAGES[0] . '):</question>',
|
||||||
|
self::SUPPORTED_LANGUAGES,
|
||||||
|
0
|
||||||
|
)),
|
||||||
|
'CLI' => $this->questionHelper->ask($input, $output, new ChoiceQuestion(
|
||||||
|
'<question>Select default language for CLI executions (defaults to '
|
||||||
|
. self::SUPPORTED_LANGUAGES[0] . '):</question>',
|
||||||
|
self::SUPPORTED_LANGUAGES,
|
||||||
|
0
|
||||||
|
)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\CLI\Install\Plugin;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\CLI\Model\CustomizableAppConfig;
|
||||||
|
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||||
|
use Symfony\Component\Console\Exception\RuntimeException;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||||
|
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||||
|
|
||||||
|
class UrlShortenerConfigCustomizerPlugin extends AbstractConfigCustomizerPlugin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param InputInterface $input
|
||||||
|
* @param OutputInterface $output
|
||||||
|
* @param CustomizableAppConfig $appConfig
|
||||||
|
* @return void
|
||||||
|
* @throws RuntimeException
|
||||||
|
*/
|
||||||
|
public function process(InputInterface $input, OutputInterface $output, CustomizableAppConfig $appConfig)
|
||||||
|
{
|
||||||
|
$this->printTitle($output, 'URL SHORTENER');
|
||||||
|
|
||||||
|
if ($appConfig->hasUrlShortener()) {
|
||||||
|
$keepConfig = $this->questionHelper->ask($input, $output, new ConfirmationQuestion(
|
||||||
|
'<question>Do you want to keep imported URL shortener config? (Y/n):</question> '
|
||||||
|
));
|
||||||
|
if ($keepConfig) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ask for URL shortener params
|
||||||
|
$appConfig->setUrlShortener([
|
||||||
|
'SCHEMA' => $this->questionHelper->ask($input, $output, new ChoiceQuestion(
|
||||||
|
'<question>Select schema for generated short URLs (defaults to http):</question>',
|
||||||
|
['http', 'https'],
|
||||||
|
0
|
||||||
|
)),
|
||||||
|
'HOSTNAME' => $this->ask($input, $output, 'Hostname for generated URLs'),
|
||||||
|
'CHARS' => $this->ask(
|
||||||
|
$input,
|
||||||
|
$output,
|
||||||
|
'Character set for generated short codes (leave empty to autogenerate one)',
|
||||||
|
null,
|
||||||
|
true
|
||||||
|
) ?: str_shuffle(UrlShortener::DEFAULT_CHARS)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,10 @@ final class CustomizableAppConfig implements ArraySerializableInterface
|
|||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $app;
|
private $app;
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $importedInstallationPath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array
|
* @return array
|
||||||
@@ -128,6 +132,32 @@ final class CustomizableAppConfig implements ArraySerializableInterface
|
|||||||
return ! empty($this->app);
|
return ! empty($this->app);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getImportedInstallationPath()
|
||||||
|
{
|
||||||
|
return $this->importedInstallationPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $importedInstallationPath
|
||||||
|
* @return $this|self
|
||||||
|
*/
|
||||||
|
public function setImportedInstallationPath($importedInstallationPath)
|
||||||
|
{
|
||||||
|
$this->importedInstallationPath = $importedInstallationPath;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasImportedInstallationPath()
|
||||||
|
{
|
||||||
|
return $this->importedInstallationPath !== null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exchange internal values from provided array
|
* Exchange internal values from provided array
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user