. * --------------------------------------------------------------------- */ namespace Glpi\Console\Database; if (!defined('GLPI_ROOT')) { die("Sorry. You can't access this file directly"); } use DB; use GLPIKey; use Toolbox; use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\ConfirmationQuestion; class InstallCommand extends AbstractConfigureCommand { /** * Error code returned when failing to create database. * * @var integer */ const ERROR_DB_CREATION_FAILED = 5; /** * Error code returned when trying to install and having a DB already containing glpi_* tables. * * @var integer */ const ERROR_DB_ALREADY_CONTAINS_TABLES = 6; /** * Error code returned when failing to create database schema. * * @var integer */ const ERROR_SCHEMA_CREATION_FAILED = 7; /** * Error code returned when failing to create encryption key file. * * @var integer */ const ERROR_CANNOT_CREATE_ENCRYPTION_KEY_FILE = 8; protected function configure() { parent::configure(); $this->setName('glpi:database:install'); $this->setAliases(['db:install']); $this->setDescription('Install database schema'); $this->addOption( 'default-language', 'L', InputOption::VALUE_OPTIONAL, __('Default language of GLPI'), 'en_GB' ); $this->addOption( 'force', 'f', InputOption::VALUE_NONE, __('Force execution of installation, overriding existing database') ); } protected function initialize(InputInterface $input, OutputInterface $output) { parent::initialize($input, $output); $this->outputWarningOnMissingOptionnalRequirements(); } protected function interact(InputInterface $input, OutputInterface $output) { if ($this->isDbAlreadyConfigured() && $this->isInputContainingConfigValues($input, $output) && !$input->getOption('reconfigure')) { /** @var \Symfony\Component\Console\Helper\QuestionHelper $question_helper */ $question_helper = $this->getHelper('question'); $reconfigure = $question_helper->ask( $input, $output, new ConfirmationQuestion( __('Command input contains configuration options that may override existing configuration.') . PHP_EOL . __('Do you want to reconfigure database ?') . ' [Yes/no]', true ) ); $input->setOption('reconfigure', $reconfigure); } if (!$this->isDbAlreadyConfigured() || $input->getOption('reconfigure')) { parent::interact($input, $output); } } protected function execute(InputInterface $input, OutputInterface $output) { global $DB; $default_language = $input->getOption('default-language'); $force = $input->getOption('force'); if ($this->isDbAlreadyConfigured() && $this->isInputContainingConfigValues($input, $output) && !$input->getOption('reconfigure')) { // Prevent overriding of existing DB when input contains configuration values and // --reconfigure option is not used. $output->writeln( '' . __('Database configuration already exists. Use --reconfigure option to override existing configuration.') . '' ); return self::ERROR_DB_CONFIG_ALREADY_SET; } if (!$this->isDbAlreadyConfigured() || $input->getOption('reconfigure')) { $result = $this->configureDatabase($input, $output); if (self::ABORTED_BY_USER === $result) { return 0; // Considered as success } else if (self::SUCCESS !== $result) { return $result; // Fail with error code } $db_host = $input->getOption('db-host'); $db_port = $input->getOption('db-port'); $db_hostport = $db_host . (!empty($db_port) ? ':' . $db_port : ''); $db_name = $input->getOption('db-name'); $db_user = $input->getOption('db-user'); $db_pass = $input->getOption('db-password'); } else { // Ask to confirm installation based on existing configuration. // $DB->dbhost can be array when using round robin feature $db_hostport = is_array($DB->dbhost) ? $DB->dbhost[0] : $DB->dbhost; $hostport = explode(':', $db_hostport); $db_host = $hostport[0]; if (count($hostport) < 2) { // Host only case $db_port = null; } else { // Host:port case or :Socket case $db_port = $hostport[1]; } $db_name = $DB->dbdefault; $db_user = $DB->dbuser; $db_pass = rawurldecode($DB->dbpassword); //rawurldecode as in DBmysql::connect() $run = $this->askForDbConfigConfirmation( $input, $output, $db_hostport, $db_name, $db_user ); if (!$run) { $output->writeln( '' . __('Installation aborted.') . '', OutputInterface::VERBOSITY_VERBOSE ); return 0; } } // Create security key $glpikey = new GLPIKey(); if (!$glpikey->keyExists() && !$glpikey->generate()) { $message = __('Security key cannot be generated!'); $output->writeln('' . $message . '', OutputInterface::VERBOSITY_QUIET); return self::ERROR_CANNOT_CREATE_ENCRYPTION_KEY_FILE; } $mysqli = new \mysqli(); if (intval($db_port) > 0) { // Network port @$mysqli->connect($db_host, $db_user, $db_pass, null, $db_port); } else { // Unix Domain Socket @$mysqli->connect($db_host, $db_user, $db_pass, null, 0, $db_port); } if (0 !== $mysqli->connect_errno) { $message = sprintf( __('Database connection failed with message "(%s) %s".'), $mysqli->connect_errno, $mysqli->connect_error ); $output->writeln('' . $message . '', OutputInterface::VERBOSITY_QUIET); return self::ERROR_DB_CONNECTION_FAILED; } // Create database or select existing one $output->writeln( '' . __('Creating the database...') . '', OutputInterface::VERBOSITY_VERBOSE ); if (!$mysqli->query('CREATE DATABASE IF NOT EXISTS `' . $db_name .'`') || !$mysqli->select_db($db_name)) { $message = sprintf( __('Database creation failed with message "(%s) %s".'), $mysqli->errno, $mysqli->error ); $output->writeln('' . $message . '', OutputInterface::VERBOSITY_QUIET); return self::ERROR_DB_CREATION_FAILED; } // Prevent overriding of existing DB $tables_result = $mysqli->query( "SELECT COUNT(table_name) FROM information_schema.tables WHERE table_schema = '{$db_name}' AND table_type = 'BASE TABLE' AND table_name LIKE 'glpi_%'" ); if (!$tables_result) { throw new RuntimeException('Unable to check GLPI tables existence.'); } if ($tables_result->fetch_array()[0] > 0 && !$force) { $output->writeln( '' . __('Database already contains "glpi_*" tables. Use --force option to override existing database.') . '' ); return self::ERROR_DB_ALREADY_CONTAINS_TABLES; } if ($DB instanceof DB) { // If global $DB is set at this point, it means that configuration file has been loaded // prior to reconfiguration. // As configuration is part of a class, it cannot be reloaded and class properties // have to be updated manually in order to make `Toolbox::createSchema()` work correctly. $DB->dbhost = $db_hostport; $DB->dbuser = $db_user; $DB->dbpassword = rawurlencode($db_pass); $DB->dbdefault = $db_name; $DB->clearSchemaCache(); $DB->connect(); $db_instance = $DB; } else { include_once (GLPI_CONFIG_DIR . "/config_db.php"); $db_instance = new DB(); } $output->writeln( '' . __('Loading default schema...') . '', OutputInterface::VERBOSITY_VERBOSE ); // TODO Get rid of output buffering ob_start(); Toolbox::createSchema($default_language, $db_instance); $message = ob_get_clean(); if (!empty($message)) { $output->writeln('' . $message . '', OutputInterface::VERBOSITY_QUIET); return self::ERROR_SCHEMA_CREATION_FAILED; } $output->writeln('' . __('Installation done.') . ''); return 0; // Success } /** * Check if DB config should be set by current command run. * * @param InputInterface $input * @param OutputInterface $output * * @return boolean */ private function shouldSetDBConfig(InputInterface $input, OutputInterface $output) { return $input->getOption('reconfigure') || !file_exists(GLPI_CONFIG_DIR . '/config_db.php'); } /** * Check if input contains DB config options. * * @param InputInterface $input * @param OutputInterface $output * * @return boolean */ private function isInputContainingConfigValues(InputInterface $input, OutputInterface $output) { $config_options = [ 'db-host', 'db-port', 'db-name', 'db-user', 'db-password', ]; foreach ($config_options as $option) { $default_value = $this->getDefinition()->getOption($option)->getDefault(); $input_value = $input->getOption($option); if ($default_value !== $input_value) { return true; } } return false; } }