459 lines
16 KiB
PHP
Executable File
459 lines
16 KiB
PHP
Executable File
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Mautic\InstallBundle\Command;
|
|
|
|
use Doctrine\Persistence\ManagerRegistry;
|
|
use Mautic\InstallBundle\Configurator\Step\CheckStep;
|
|
use Mautic\InstallBundle\Configurator\Step\DoctrineStep;
|
|
use Mautic\InstallBundle\Install\InstallService;
|
|
use Symfony\Component\Console\Attribute\AsCommand;
|
|
use Symfony\Component\Console\Command\Command;
|
|
use Symfony\Component\Console\Helper\QuestionHelper;
|
|
use Symfony\Component\Console\Input\InputArgument;
|
|
use Symfony\Component\Console\Input\InputInterface;
|
|
use Symfony\Component\Console\Input\InputOption;
|
|
use Symfony\Component\Console\Output\OutputInterface;
|
|
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
|
|
|
/**
|
|
* CLI Command to install Mautic.
|
|
*/
|
|
#[AsCommand(
|
|
name: InstallCommand::COMMAND,
|
|
description: 'Installs Mautic'
|
|
)]
|
|
class InstallCommand extends Command
|
|
{
|
|
public const COMMAND = 'mautic:install';
|
|
|
|
public function __construct(
|
|
private InstallService $installer,
|
|
private ManagerRegistry $doctrineRegistry,
|
|
) {
|
|
parent::__construct();
|
|
}
|
|
|
|
/**
|
|
* Note: in every option (addOption()), please leave the default value empty to prevent problems with values from local.php being overwritten.
|
|
*/
|
|
protected function configure()
|
|
{
|
|
$this
|
|
->setName(self::COMMAND)
|
|
->setHelp('This command allows you to trigger the install process. It will try to get configuration values both from the local config file and command line options/arguments, where the latter takes precedence.')
|
|
->addArgument(
|
|
'site_url',
|
|
InputArgument::REQUIRED,
|
|
'Site URL.',
|
|
null
|
|
)
|
|
->addArgument(
|
|
'step',
|
|
InputArgument::OPTIONAL,
|
|
'Install process start index. 0 for requirements check, 1 for database, 2 for admin, 3 for configuration, 4 for final step. Each successful step will trigger the next until completion.',
|
|
'0'
|
|
)
|
|
->addOption(
|
|
'--force',
|
|
'-f',
|
|
InputOption::VALUE_NONE,
|
|
'Do not ask confirmation if recommendations triggered.',
|
|
null
|
|
)
|
|
->addOption(
|
|
'--db_driver',
|
|
null,
|
|
InputOption::VALUE_REQUIRED,
|
|
'Database driver.',
|
|
null
|
|
)
|
|
->addOption(
|
|
'--db_host',
|
|
null,
|
|
InputOption::VALUE_REQUIRED,
|
|
'Database host.',
|
|
null
|
|
)
|
|
->addOption(
|
|
'--db_port',
|
|
null,
|
|
InputOption::VALUE_REQUIRED,
|
|
'Database port.',
|
|
null
|
|
)
|
|
->addOption(
|
|
'--db_name',
|
|
null,
|
|
InputOption::VALUE_REQUIRED,
|
|
'Database name.',
|
|
null
|
|
)
|
|
->addOption(
|
|
'--db_user',
|
|
null,
|
|
InputOption::VALUE_REQUIRED,
|
|
'Database user.',
|
|
null
|
|
)
|
|
->addOption(
|
|
'--db_password',
|
|
null,
|
|
InputOption::VALUE_REQUIRED,
|
|
'Database password.',
|
|
null
|
|
)
|
|
->addOption(
|
|
'--db_table_prefix',
|
|
null,
|
|
InputOption::VALUE_REQUIRED,
|
|
'Database tables prefix.',
|
|
null
|
|
)
|
|
->addOption(
|
|
'--db_backup_tables',
|
|
null,
|
|
InputOption::VALUE_REQUIRED,
|
|
'Backup database tables if they exist; otherwise drop them. (true|false)',
|
|
null
|
|
)
|
|
->addOption(
|
|
'--db_backup_prefix',
|
|
null,
|
|
InputOption::VALUE_REQUIRED,
|
|
'Database backup tables prefix.',
|
|
null
|
|
)
|
|
->addOption(
|
|
'--admin_firstname',
|
|
null,
|
|
InputOption::VALUE_REQUIRED,
|
|
'Admin first name.',
|
|
null
|
|
)
|
|
->addOption(
|
|
'--admin_lastname',
|
|
null,
|
|
InputOption::VALUE_REQUIRED,
|
|
'Admin last name.',
|
|
null
|
|
)
|
|
->addOption(
|
|
'--admin_username',
|
|
null,
|
|
InputOption::VALUE_REQUIRED,
|
|
'Admin username.',
|
|
null
|
|
)
|
|
->addOption(
|
|
'--admin_email',
|
|
null,
|
|
InputOption::VALUE_REQUIRED,
|
|
'Admin email.',
|
|
null
|
|
)
|
|
->addOption(
|
|
'--admin_password',
|
|
null,
|
|
InputOption::VALUE_REQUIRED,
|
|
'Admin user.',
|
|
null
|
|
);
|
|
|
|
parent::configure();
|
|
}
|
|
|
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
|
{
|
|
// Check Mautic is not already installed
|
|
if ($this->installer->checkIfInstalled()) {
|
|
$output->writeln('Mautic already installed');
|
|
|
|
return Command::SUCCESS;
|
|
}
|
|
|
|
$output->writeln([
|
|
'Mautic Install',
|
|
'==============',
|
|
'',
|
|
]);
|
|
|
|
if (!defined('IS_PHPUNIT')) {
|
|
// Prevents querying of database tables that do not exist during the installation process
|
|
define('MAUTIC_INSTALLER', 1);
|
|
}
|
|
|
|
// Build objects to pass to the install service from local.php or command line options
|
|
$output->writeln('Parsing options and arguments...');
|
|
$options = $input->getOptions();
|
|
|
|
// Convert boolean options to actual booleans.
|
|
$options['db_backup_tables'] = (bool) filter_var($options['db_backup_tables'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
|
|
|
/**
|
|
* We need to have some default database parameters, as it could be the case that the
|
|
* user didn't set them both in local.php and the command line options.
|
|
*/
|
|
$dbParams = [
|
|
'driver' => 'pdo_mysql',
|
|
'host' => null,
|
|
'port' => null,
|
|
'name' => null,
|
|
'user' => null,
|
|
'password' => null,
|
|
'table_prefix' => null,
|
|
'backup_tables' => true,
|
|
'backup_prefix' => 'bak_',
|
|
];
|
|
$adminParam = [
|
|
'firstname' => 'Admin',
|
|
'lastname' => 'Mautic',
|
|
'username' => 'admin',
|
|
];
|
|
$allParams = $this->installer->localConfigParameters();
|
|
|
|
// Initialize DB and admin params from local.php
|
|
foreach ((array) $allParams as $opt => $value) {
|
|
if (str_starts_with($opt, 'db_')) {
|
|
$dbParams[substr($opt, 3)] = $value;
|
|
} elseif (str_starts_with($opt, 'admin_')) {
|
|
$adminParam[substr($opt, 6)] = $value;
|
|
}
|
|
}
|
|
|
|
// Initialize DB and admin params from cli options
|
|
foreach ($options as $opt => $value) {
|
|
if (isset($value)) {
|
|
if (str_starts_with($opt, 'db_')) {
|
|
$dbParams[substr($opt, 3)] = $value;
|
|
$allParams[$opt] = $value;
|
|
} elseif (str_starts_with($opt, 'admin_')) {
|
|
$adminParam[substr($opt, 6)] = $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!empty($allParams['site_url'])) {
|
|
$siteUrl = $allParams['site_url'];
|
|
} else {
|
|
$siteUrl = $input->getArgument('site_url');
|
|
$allParams['site_url'] = $siteUrl;
|
|
}
|
|
|
|
$step = (float) $input->getArgument('step');
|
|
|
|
switch ($step) {
|
|
default:
|
|
case InstallService::CHECK_STEP:
|
|
$output->writeln($step.' - Checking installation requirements...');
|
|
$messages = $this->stepAction($this->installer, ['site_url' => $siteUrl], $step);
|
|
if (!empty($messages)) {
|
|
if (isset($messages['requirements']) && !empty($messages['requirements'])) {
|
|
// Stop install if requirements not met
|
|
$output->writeln('Missing requirements:');
|
|
$this->handleInstallerErrors($output, $messages['requirements']);
|
|
$output->writeln('Install canceled');
|
|
|
|
return (int) -$step;
|
|
} elseif (isset($messages['optional']) && !empty($messages['optional'])) {
|
|
$output->writeln('Missing optional settings:');
|
|
$this->handleInstallerErrors($output, $messages['optional']);
|
|
|
|
if (empty($options['force'])) {
|
|
// Ask user to confirm install when optional settings missing
|
|
|
|
/** @var QuestionHelper $helper */
|
|
$helper = $this->getHelper('question');
|
|
$question = new ConfirmationQuestion('Continue with install anyway? [yes/no]', false);
|
|
|
|
if (!$helper->ask($input, $output, $question)) {
|
|
return (int) -$step;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$output->writeln('Ready to Install!');
|
|
// Keep on with next step
|
|
$step = InstallService::DOCTRINE_STEP;
|
|
|
|
// no break
|
|
case InstallService::DOCTRINE_STEP:
|
|
$output->writeln($step.' - Creating database...');
|
|
|
|
/**
|
|
* This is needed for installations with database prefixes to work correctly.
|
|
*/
|
|
$connectionWrapper = $this->doctrineRegistry->getConnection();
|
|
$connectionWrapper->initConnection($dbParams);
|
|
|
|
$messages = $this->stepAction($this->installer, $dbParams, $step);
|
|
if (!empty($messages)) {
|
|
$output->writeln('Errors in database configuration/installation:');
|
|
$this->handleInstallerErrors($output, $messages);
|
|
|
|
$output->writeln('Install canceled');
|
|
|
|
return (int) -$step;
|
|
}
|
|
|
|
$step = InstallService::DOCTRINE_STEP + .1;
|
|
$output->writeln($step.' - Creating schema...');
|
|
$messages = $this->stepAction($this->installer, $dbParams, $step);
|
|
if (!empty($messages)) {
|
|
$output->writeln('Errors in schema configuration/installation:');
|
|
$this->handleInstallerErrors($output, $messages);
|
|
|
|
$output->writeln('Install canceled');
|
|
|
|
return -InstallService::DOCTRINE_STEP;
|
|
}
|
|
|
|
$step = InstallService::DOCTRINE_STEP + .2;
|
|
$output->writeln($step.' - Loading fixtures...');
|
|
$messages = $this->stepAction($this->installer, $dbParams, $step);
|
|
if (!empty($messages)) {
|
|
$output->writeln('Errors in fixtures configuration/installation:');
|
|
$this->handleInstallerErrors($output, $messages);
|
|
|
|
$output->writeln('Install canceled');
|
|
|
|
return -InstallService::DOCTRINE_STEP;
|
|
}
|
|
|
|
// Keep on with next step
|
|
$step = InstallService::USER_STEP;
|
|
|
|
// no break
|
|
case InstallService::USER_STEP:
|
|
$output->writeln($step.' - Creating admin user...');
|
|
$messages = $this->stepAction($this->installer, $adminParam, $step);
|
|
if (!empty($messages)) {
|
|
$output->writeln('Errors in admin user configuration/installation:');
|
|
$this->handleInstallerErrors($output, $messages);
|
|
|
|
$output->writeln('Install canceled');
|
|
|
|
return (int) -$step;
|
|
}
|
|
// Keep on with next step
|
|
$step = InstallService::FINAL_STEP;
|
|
|
|
// no break
|
|
case InstallService::FINAL_STEP:
|
|
$output->writeln($step.' - Final steps...');
|
|
$messages = $this->stepAction($this->installer, $allParams, $step);
|
|
if (!empty($messages)) {
|
|
$output->writeln('Errors in final step:');
|
|
$this->handleInstallerErrors($output, $messages);
|
|
|
|
$output->writeln('Install canceled');
|
|
|
|
return (int) -$step;
|
|
}
|
|
}
|
|
|
|
$output->writeln([
|
|
'',
|
|
'================',
|
|
'Install complete',
|
|
'================',
|
|
]);
|
|
|
|
return Command::SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Controller action for install steps.
|
|
*
|
|
* @param InstallService $installer The install process
|
|
* @param array $params The install parameters
|
|
* @param float $index The step number to process
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
protected function stepAction(InstallService $installer, array $params, float $index = 0): array
|
|
{
|
|
if ($index - floor($index) > 0) {
|
|
$subIndex = (int) (round($index - floor($index), 1) * 10);
|
|
$index = floor($index);
|
|
}
|
|
$index = (int) $index;
|
|
|
|
$messages = [];
|
|
|
|
switch ($index) {
|
|
case InstallService::CHECK_STEP:
|
|
// Check installation requirements
|
|
$step = $installer->getStep($index);
|
|
if ($step instanceof CheckStep) {
|
|
// Set all step fields based on parameters
|
|
$step->site_url = $params['site_url'];
|
|
}
|
|
|
|
$messages['requirements'] = $installer->checkRequirements($step);
|
|
$messages['optional'] = $installer->checkOptionalSettings($step);
|
|
break;
|
|
|
|
case InstallService::DOCTRINE_STEP:
|
|
$step = $installer->getStep($index);
|
|
if ($step instanceof DoctrineStep) {
|
|
// Set all step fields based on parameters
|
|
foreach ($step as $key => $value) {
|
|
if (isset($params[$key])) {
|
|
$step->$key = $params[$key];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isset($subIndex)) {
|
|
// Install database
|
|
$messages = $installer->createDatabaseStep($step, $params);
|
|
|
|
break;
|
|
}
|
|
|
|
switch ($subIndex) {
|
|
case 1:
|
|
// Install schema
|
|
$messages = $installer->createSchemaStep($params);
|
|
break;
|
|
|
|
case 2:
|
|
// Install fixtures
|
|
$messages = $installer->createFixturesStep();
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case InstallService::USER_STEP:
|
|
// Create admin user
|
|
$messages = $installer->createAdminUserStep($params);
|
|
break;
|
|
|
|
case InstallService::FINAL_STEP:
|
|
// Save final configuration
|
|
$siteUrl = $params['site_url'];
|
|
$messages = $installer->createFinalConfigStep($siteUrl);
|
|
if (empty($messages)) {
|
|
$installer->finalMigrationStep();
|
|
}
|
|
break;
|
|
}
|
|
|
|
return $messages;
|
|
}
|
|
|
|
/**
|
|
* Handle install command errors.
|
|
*
|
|
* @param array<string,string> $messages
|
|
*/
|
|
private function handleInstallerErrors(OutputInterface $output, array $messages): void
|
|
{
|
|
foreach ($messages as $type => $message) {
|
|
$output->writeln(" - [$type] $message");
|
|
}
|
|
}
|
|
}
|