first commit
This commit is contained in:
202
inc/console/abstractcommand.class.php
Normal file
202
inc/console/abstractcommand.class.php
Normal file
@ -0,0 +1,202 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use DB;
|
||||
|
||||
use Glpi\Console\Command\GlpiCommandInterface;
|
||||
use Glpi\System\RequirementsManager;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
abstract class AbstractCommand extends Command implements GlpiCommandInterface {
|
||||
|
||||
/**
|
||||
* @var DB
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* @var InputInterface
|
||||
*/
|
||||
protected $input;
|
||||
|
||||
/**
|
||||
* @var OutputInterface
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
* Flag to indicate if command requires a BD connection.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $requires_db = true;
|
||||
|
||||
protected function initialize(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$this->input = $input;
|
||||
$this->output = $output;
|
||||
|
||||
$this->initDbConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check database connection.
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function initDbConnection() {
|
||||
|
||||
global $DB;
|
||||
|
||||
if ($this->requires_db && (!($DB instanceof DB) || !$DB->connected)) {
|
||||
throw new RuntimeException(__('Unable to connect to database.'));
|
||||
}
|
||||
|
||||
$this->db = $DB;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correctly write output messages when a progress bar is displayed.
|
||||
*
|
||||
* @param string|array $messages
|
||||
* @param ProgressBar $progress_bar
|
||||
* @param integer $verbosity
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function writelnOutputWithProgressBar($messages,
|
||||
ProgressBar $progress_bar,
|
||||
$verbosity = OutputInterface::VERBOSITY_NORMAL) {
|
||||
|
||||
if ($verbosity > $this->output->getVerbosity()) {
|
||||
return; // Do nothing if message will not be output due to its too high verbosity
|
||||
}
|
||||
|
||||
$progress_bar->clear();
|
||||
$this->output->writeln(
|
||||
$messages,
|
||||
$verbosity
|
||||
);
|
||||
$progress_bar->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Output session buffered messages.
|
||||
*
|
||||
* @param array $levels_to_output
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function outputSessionBufferedMessages($levels_to_output = [INFO, WARNING, ERROR]) {
|
||||
|
||||
if (empty($_SESSION['MESSAGE_AFTER_REDIRECT'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$msg_levels = [
|
||||
INFO => [
|
||||
'tag' => 'info',
|
||||
'verbosity' => OutputInterface::VERBOSITY_VERBOSE,
|
||||
],
|
||||
WARNING => [
|
||||
'tag' => 'comment',
|
||||
'verbosity' => OutputInterface::VERBOSITY_NORMAL,
|
||||
],
|
||||
ERROR => [
|
||||
'tag' => 'error',
|
||||
'verbosity' => OutputInterface::VERBOSITY_QUIET,
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($msg_levels as $key => $options) {
|
||||
if (!in_array($key, $levels_to_output)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!array_key_exists($key, $_SESSION['MESSAGE_AFTER_REDIRECT'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($_SESSION['MESSAGE_AFTER_REDIRECT'][$key] as $message) {
|
||||
$message = strip_tags(preg_replace('/<br\s*\/?>/', ' ', $message)); // Output raw text
|
||||
$this->output->writeln(
|
||||
"<{$options['tag']}>{$message}</{$options['tag']}>",
|
||||
$options['verbosity']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output a warning in an optionnal requirement is missing.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function outputWarningOnMissingOptionnalRequirements() {
|
||||
if ($this->output->isQuiet()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$db = property_exists($this, 'db') ? $this->db : null;
|
||||
|
||||
$requirements_manager = new RequirementsManager();
|
||||
$core_requirements = $requirements_manager->getCoreRequirementList(
|
||||
$db instanceof \DBmysql && $db->connected ? $db : null
|
||||
);
|
||||
if ($core_requirements->hasMissingOptionalRequirements()) {
|
||||
$message = __('Some optional system requirements are missing.')
|
||||
. ' '
|
||||
. __('Run "php bin/console glpi:system:check_requirements" for more details.');
|
||||
$this->output->writeln(
|
||||
'<comment>' . $message . '</comment>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function mustCheckMandatoryRequirements(): bool {
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
476
inc/console/application.class.php
Normal file
476
inc/console/application.class.php
Normal file
@ -0,0 +1,476 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use Config;
|
||||
use DB;
|
||||
use GLPI;
|
||||
use Glpi\Application\ErrorHandler;
|
||||
use Glpi\Console\Command\ForceNoPluginsOptionCommandInterface;
|
||||
use Glpi\Console\Command\GlpiCommandInterface;
|
||||
use Glpi\System\RequirementsManager;
|
||||
use Plugin;
|
||||
use Session;
|
||||
use Toolbox;
|
||||
|
||||
use Symfony\Component\Console\Application as BaseApplication;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\Helper;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputDefinition;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Exception\CommandNotFoundException;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class Application extends BaseApplication {
|
||||
|
||||
/**
|
||||
* Error code returned when system requirements are missing.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_MISSING_REQUIREMENTS = 128; // start application codes at 128 be sure to be different from commands codes
|
||||
|
||||
/**
|
||||
* Pointer to $CFG_GLPI.
|
||||
* @var array
|
||||
*/
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* @var ErrorHandler
|
||||
*/
|
||||
private $error_handler;
|
||||
|
||||
/**
|
||||
* @var DB
|
||||
*/
|
||||
private $db;
|
||||
|
||||
/**
|
||||
* @var OutputInterface
|
||||
*/
|
||||
private $output;
|
||||
|
||||
public function __construct() {
|
||||
|
||||
parent::__construct('GLPI CLI', GLPI_VERSION);
|
||||
|
||||
$this->initApplication();
|
||||
$this->initDb();
|
||||
$this->initSession();
|
||||
$this->initCache();
|
||||
$this->initConfig();
|
||||
|
||||
$this->computeAndLoadOutputLang();
|
||||
|
||||
// Load core commands only to check if called command prevent or not usage of plugins
|
||||
// Plugin commands will be loaded later
|
||||
$loader = new CommandLoader(false);
|
||||
$this->setCommandLoader($loader);
|
||||
|
||||
$use_plugins = $this->usePlugins();
|
||||
if ($use_plugins) {
|
||||
$this->loadActivePlugins();
|
||||
$loader->registerPluginsCommands();
|
||||
}
|
||||
}
|
||||
|
||||
protected function getDefaultInputDefinition() {
|
||||
|
||||
$definition = new InputDefinition(
|
||||
[
|
||||
new InputArgument(
|
||||
'command',
|
||||
InputArgument::REQUIRED,
|
||||
__('The command to execute')
|
||||
),
|
||||
|
||||
new InputOption(
|
||||
'--help',
|
||||
'-h',
|
||||
InputOption::VALUE_NONE,
|
||||
__('Display this help message')
|
||||
),
|
||||
new InputOption(
|
||||
'--quiet',
|
||||
'-q',
|
||||
InputOption::VALUE_NONE,
|
||||
__('Do not output any message')
|
||||
),
|
||||
new InputOption(
|
||||
'--verbose',
|
||||
'-v|vv|vvv',
|
||||
InputOption::VALUE_NONE,
|
||||
__('Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug')
|
||||
),
|
||||
new InputOption(
|
||||
'--version',
|
||||
'-V',
|
||||
InputOption::VALUE_NONE,
|
||||
__('Display this application version')
|
||||
),
|
||||
new InputOption(
|
||||
'--ansi',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
__('Force ANSI output')
|
||||
),
|
||||
new InputOption(
|
||||
'--no-ansi',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
__('Disable ANSI output')
|
||||
),
|
||||
new InputOption(
|
||||
'--no-interaction',
|
||||
'-n',
|
||||
InputOption::VALUE_NONE,
|
||||
__('Do not ask any interactive question')
|
||||
),
|
||||
new InputOption(
|
||||
'--config-dir',
|
||||
null,
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
__('Configuration directory to use')
|
||||
),
|
||||
new InputOption(
|
||||
'--no-plugins',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
__('Disable GLPI plugins (unless commands forces plugins loading)')
|
||||
),
|
||||
new InputOption(
|
||||
'--lang',
|
||||
null,
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
__('Output language (default value is existing GLPI "language" configuration or "en_GB")')
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
return $definition;
|
||||
}
|
||||
|
||||
protected function configureIO(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
global $CFG_GLPI;
|
||||
|
||||
$this->output = $output;
|
||||
$this->error_handler->setOutputHandler($output);
|
||||
|
||||
parent::configureIO($input, $output);
|
||||
|
||||
// Trigger error on invalid lang. This is not done before as error handler would not be set.
|
||||
$lang = $input->getParameterOption('--lang', null, true);
|
||||
if (null !== $lang && !array_key_exists($lang, $CFG_GLPI['languages'])) {
|
||||
throw new RuntimeException(
|
||||
sprintf(__('Invalid "--lang" option value "%s".'), $lang)
|
||||
);
|
||||
}
|
||||
|
||||
if ($output->getVerbosity() === OutputInterface::VERBOSITY_DEBUG) {
|
||||
Toolbox::setDebugMode(Session::DEBUG_MODE, 0, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns output handler.
|
||||
*
|
||||
* @return OutputInterface
|
||||
*/
|
||||
public function getOutput() {
|
||||
return $this->output;
|
||||
}
|
||||
|
||||
protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$begin_time = microtime(true);
|
||||
|
||||
if ($command instanceof GlpiCommandInterface && $command->mustCheckMandatoryRequirements()
|
||||
&& !$this->checkCoreMandatoryRequirements()) {
|
||||
return self::ERROR_MISSING_REQUIREMENTS;
|
||||
}
|
||||
|
||||
$result = parent::doRunCommand($command, $input, $output);
|
||||
|
||||
if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE) {
|
||||
$output->writeln(
|
||||
sprintf(
|
||||
__('Time elapsed: %s.'),
|
||||
Helper::formatTime(microtime(true) - $begin_time)
|
||||
)
|
||||
);
|
||||
$output->writeln(
|
||||
sprintf(
|
||||
__('Memory usage: %s.'),
|
||||
Helper::formatMemory(memory_get_peak_usage(true))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initalize GLPI.
|
||||
*
|
||||
* @global array $CFG_GLPI
|
||||
* @global GLPI $GLPI
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function initApplication() {
|
||||
|
||||
// Disable debug at bootstrap (will be re-enabled later if requested by verbosity level).
|
||||
global $CFG_GLPI;
|
||||
$CFG_GLPI = array_merge(
|
||||
$CFG_GLPI,
|
||||
[
|
||||
'debug_sql' => 0,
|
||||
'debug_vars' => 0,
|
||||
]
|
||||
);
|
||||
|
||||
global $GLPI;
|
||||
$GLPI = new GLPI();
|
||||
$GLPI->initLogger();
|
||||
$this->error_handler = $GLPI->initErrorHandler();
|
||||
|
||||
Config::detectRootDoc();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize database connection.
|
||||
*
|
||||
* @global DB $DB
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private function initDb() {
|
||||
|
||||
if (!class_exists('DB', false) || !class_exists('mysqli', false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $DB;
|
||||
$DB = new DB();
|
||||
$this->db = $DB;
|
||||
|
||||
if (!$this->db->connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
$checkdb = Config::displayCheckDbEngine();
|
||||
$message = ob_get_clean();
|
||||
if ($checkdb > 0) {
|
||||
throw new RuntimeException($message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize GLPI session.
|
||||
* This is mandatory to init cache and load languages.
|
||||
*
|
||||
* @TODO Do not use session for console.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function initSession() {
|
||||
|
||||
if (!is_writable(GLPI_SESSION_DIR)) {
|
||||
throw new RuntimeException(
|
||||
sprintf(__('Cannot write in "%s" directory.'), GLPI_SESSION_DIR)
|
||||
);
|
||||
}
|
||||
|
||||
Session::setPath();
|
||||
Session::start();
|
||||
|
||||
// Default value for use mode
|
||||
$_SESSION['glpi_use_mode'] = Session::NORMAL_MODE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize GLPI cache.
|
||||
*
|
||||
* @global Laminas\Cache\Storage\StorageInterface $GLPI_CACHE
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function initCache() {
|
||||
|
||||
global $GLPI_CACHE;
|
||||
$GLPI_CACHE = Config::getCache('cache_db');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize GLPI configuration.
|
||||
*
|
||||
* @global array $CFG_GLPI
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function initConfig() {
|
||||
|
||||
global $CFG_GLPI;
|
||||
$this->config = &$CFG_GLPI;
|
||||
|
||||
if (!($this->db instanceof DB) || !$this->db->connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
Config::loadLegacyConfiguration(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute and load output language.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private function computeAndLoadOutputLang() {
|
||||
|
||||
// 1. Check in command line arguments
|
||||
$input = new ArgvInput();
|
||||
$lang = $input->getParameterOption('--lang', null, true);
|
||||
|
||||
if (null !== $lang && !$this->isLanguageValid($lang)) {
|
||||
// Unset requested lang if invalid
|
||||
$lang = null;
|
||||
}
|
||||
|
||||
// 2. Check in GLPI configuration
|
||||
if (null === $lang && array_key_exists('language', $this->config)
|
||||
&& $this->isLanguageValid($this->config['language'])) {
|
||||
$lang = $this->config['language'];
|
||||
}
|
||||
|
||||
// 3. Use default value
|
||||
if (null === $lang) {
|
||||
$lang = 'en_GB';
|
||||
}
|
||||
|
||||
$_SESSION['glpilanguage'] = $lang;
|
||||
|
||||
Session::loadLanguage('', $this->usePlugins());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a language is valid.
|
||||
*
|
||||
* @param string $language
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function isLanguageValid($language) {
|
||||
return is_array($this->config)
|
||||
&& array_key_exists('languages', $this->config)
|
||||
&& array_key_exists($language, $this->config['languages']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load active plugins.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function loadActivePlugins() {
|
||||
|
||||
if (!($this->db instanceof DB) || !$this->db->connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
$plugin = new Plugin();
|
||||
$plugin->init(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not plugins have to be used.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function usePlugins() {
|
||||
|
||||
$input = new ArgvInput();
|
||||
|
||||
try {
|
||||
$command = $this->find($this->getCommandName($input));
|
||||
if ($command instanceof ForceNoPluginsOptionCommandInterface) {
|
||||
return !$command->getNoPluginsOptionValue();
|
||||
}
|
||||
} catch (CommandNotFoundException $e) {
|
||||
// Command will not be found at this point if it is a plugin command
|
||||
$command = null; // Say hello to CS checker
|
||||
}
|
||||
|
||||
return !$input->hasParameterOption('--no-plugins', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if core mandatory requirements are OK.
|
||||
*
|
||||
* @return boolean true if requirements are OK, false otherwise
|
||||
*/
|
||||
private function checkCoreMandatoryRequirements(): bool {
|
||||
$db = property_exists($this, 'db') ? $this->db : null;
|
||||
|
||||
$requirements_manager = new RequirementsManager();
|
||||
$core_requirements = $requirements_manager->getCoreRequirementList(
|
||||
$db instanceof \DBmysql && $db->connected ? $db : null
|
||||
);
|
||||
|
||||
if ($core_requirements->hasMissingMandatoryRequirements()) {
|
||||
$message = __('Some mandatory system requirements are missing.')
|
||||
. ' '
|
||||
. __('Run "php bin/console glpi:system:check_requirements" for more details.');
|
||||
$this->output->writeln(
|
||||
'<error>' . $message . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Command;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
interface ForceNoPluginsOptionCommandInterface {
|
||||
|
||||
/**
|
||||
* Defines whether or not command prevents plugins to be loaded.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function getNoPluginsOptionValue();
|
||||
}
|
||||
47
inc/console/command/glpicommandinterface.class.php
Normal file
47
inc/console/command/glpicommandinterface.class.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Command;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
interface GlpiCommandInterface {
|
||||
|
||||
/**
|
||||
* Defines whether or mandatory requirements must be checked before running command.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function mustCheckMandatoryRequirements(): bool;
|
||||
}
|
||||
309
inc/console/commandloader.class.php
Normal file
309
inc/console/commandloader.class.php
Normal file
@ -0,0 +1,309 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use DirectoryIterator;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use ReflectionClass;
|
||||
use SplFileInfo;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
|
||||
use Symfony\Component\Console\Exception\CommandNotFoundException;
|
||||
|
||||
/**
|
||||
* Core and plugins command loader.
|
||||
*
|
||||
* @since 9.4.0
|
||||
*/
|
||||
class CommandLoader implements CommandLoaderInterface {
|
||||
|
||||
/**
|
||||
* Root directory path to search on.
|
||||
* @var string
|
||||
*/
|
||||
private $rootdir;
|
||||
|
||||
/**
|
||||
* Found commands.
|
||||
*
|
||||
* @var Command[]
|
||||
*/
|
||||
private $commands = [];
|
||||
|
||||
/**
|
||||
* @param string $rootdir Root directory path of application.
|
||||
*/
|
||||
public function __construct($include_plugins = true, $rootdir = GLPI_ROOT) {
|
||||
|
||||
$this->rootdir = $rootdir;
|
||||
|
||||
$this->findCoreCommands();
|
||||
$this->findToolsCommands();
|
||||
|
||||
if ($include_plugins) {
|
||||
$this->findPluginCommands();
|
||||
}
|
||||
}
|
||||
|
||||
public function get($name) {
|
||||
if (!array_key_exists($name, $this->commands)) {
|
||||
throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
|
||||
}
|
||||
|
||||
return $this->commands[$name];
|
||||
}
|
||||
|
||||
public function has($name) {
|
||||
return array_key_exists($name, $this->commands);
|
||||
}
|
||||
|
||||
public function getNames() {
|
||||
return array_keys($this->commands);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register plugin commands in command list.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function registerPluginsCommands() {
|
||||
|
||||
$this->findPluginCommands();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all core commands.
|
||||
*
|
||||
* return void
|
||||
*/
|
||||
private function findCoreCommands() {
|
||||
|
||||
$basedir = $this->rootdir . DIRECTORY_SEPARATOR . 'inc';
|
||||
|
||||
$core_files = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($basedir),
|
||||
RecursiveIteratorIterator::SELF_FIRST
|
||||
);
|
||||
/** @var SplFileInfo $file */
|
||||
foreach ($core_files as $file) {
|
||||
if (!$file->isReadable() || !$file->isFile()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$class = $this->getCommandClassnameFromFile(
|
||||
$file,
|
||||
$basedir,
|
||||
['', NS_GLPI]
|
||||
);
|
||||
|
||||
if (null === $class) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->registerCommand(new $class());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all plugins (active or not) commands.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function findPluginCommands() {
|
||||
|
||||
$basedir = $this->rootdir . DIRECTORY_SEPARATOR . 'plugins';
|
||||
|
||||
$plugins_directories = new DirectoryIterator($basedir);
|
||||
/** @var SplFileInfo $plugin_directory */
|
||||
foreach ($plugins_directories as $plugin_directory) {
|
||||
if (in_array($plugin_directory->getFilename(), ['.', '..'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$plugin_basedir = $plugin_directory->getPathname() . DIRECTORY_SEPARATOR . 'inc';
|
||||
if (!is_readable($plugin_basedir) || !is_dir($plugin_basedir)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$plugin_files = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($plugin_basedir),
|
||||
RecursiveIteratorIterator::SELF_FIRST
|
||||
);
|
||||
/** @var SplFileInfo $file */
|
||||
foreach ($plugin_files as $file) {
|
||||
if (!$file->isReadable() || !$file->isFile()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$class = $this->getCommandClassnameFromFile(
|
||||
$file,
|
||||
$plugin_basedir,
|
||||
[
|
||||
NS_PLUG . ucfirst($plugin_directory->getFilename()) . '\\',
|
||||
'Plugin' . ucfirst($plugin_directory->getFilename()),
|
||||
]
|
||||
);
|
||||
|
||||
if (null === $class) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->registerCommand(new $class());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all "tools" commands.
|
||||
*
|
||||
* return void
|
||||
*/
|
||||
private function findToolsCommands() {
|
||||
|
||||
$basedir = $this->rootdir . DIRECTORY_SEPARATOR . 'tools';
|
||||
|
||||
if (!is_dir($basedir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tools_files = new DirectoryIterator($basedir);
|
||||
/** @var SplFileInfo $file */
|
||||
foreach ($tools_files as $file) {
|
||||
if (!$file->isReadable() || !$file->isFile()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$class = $this->getCommandClassnameFromFile(
|
||||
$file,
|
||||
$basedir
|
||||
);
|
||||
|
||||
if (null === $class) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->registerCommand(new $class());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a command on self.
|
||||
*
|
||||
* @param Command $command
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function registerCommand(Command $command) {
|
||||
|
||||
$this->commands[$command->getName()] = $command;
|
||||
|
||||
$aliases = $command->getAliases();
|
||||
foreach ($aliases as $alias) {
|
||||
$this->commands[$alias] = $command;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return classname of command contained in file, if file contains one.
|
||||
*
|
||||
* @param SplFileInfo $file File to inspect
|
||||
* @param string $basedir Directory containing classes (eg GLPI_ROOT . '/inc')
|
||||
* @param string $prefixes Possible prefixes to add to classname (eg 'PluginExample', 'GlpiPlugin\Example')
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
private function getCommandClassnameFromFile(SplFileInfo $file, $basedir, array $prefixes = []) {
|
||||
|
||||
// Check if file is readable and contained classname finishes by "command"
|
||||
if (!$file->isReadable() || !$file->isFile()
|
||||
|| !preg_match('/^(.*)command\.class\.php$/', $file->getFilename())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract expected classname from filename.
|
||||
// Classname will be lowercased, but it is ok for PHP.
|
||||
$classname = str_replace(
|
||||
['.class.php', DIRECTORY_SEPARATOR],
|
||||
['', '\\'],
|
||||
$this->getRelativePath($basedir, $file->getPathname())
|
||||
);
|
||||
|
||||
if (empty($prefixes)) {
|
||||
$prefixes = [''];
|
||||
}
|
||||
foreach ($prefixes as $prefix) {
|
||||
$classname_to_check = $prefix . $classname;
|
||||
|
||||
include_once($file->getPathname()); // Required as ReflectionClass will not use autoload
|
||||
|
||||
if (!class_exists($classname_to_check, false)) {
|
||||
// Try with other prefixes.
|
||||
// Needed as a file located in root source dir of Glpi can be either namespaced either not.
|
||||
continue;
|
||||
}
|
||||
|
||||
$reflectionClass = new ReflectionClass($classname_to_check);
|
||||
if ($reflectionClass->isInstantiable() && $reflectionClass->isSubclassOf(Command::class)) {
|
||||
return $classname_to_check;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns path relative to basedir.
|
||||
*
|
||||
* @param string $basedir
|
||||
* @param string $filepath
|
||||
* @return string
|
||||
*/
|
||||
private function getRelativePath($basedir, $filepath) {
|
||||
|
||||
// Strip (multiple) ending directory separator to normalize input
|
||||
while (strrpos($basedir, DIRECTORY_SEPARATOR) == strlen($basedir) - 1) {
|
||||
$basedir = substr($basedir, 0, -1);
|
||||
}
|
||||
|
||||
// Assume that filepath is prefixed by basedir
|
||||
// Cannot use realpath to normalize path as it will not work when using a virtual fs (unit tests)
|
||||
return str_replace($basedir . DIRECTORY_SEPARATOR, '', $filepath);
|
||||
}
|
||||
|
||||
}
|
||||
348
inc/console/database/abstractconfigurecommand.class.php
Normal file
348
inc/console/database/abstractconfigurecommand.class.php
Normal file
@ -0,0 +1,348 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Database;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use Config;
|
||||
use DBConnection;
|
||||
use Glpi\Console\AbstractCommand;
|
||||
use Glpi\Console\Command\ForceNoPluginsOptionCommandInterface;
|
||||
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
|
||||
abstract class AbstractConfigureCommand extends AbstractCommand implements ForceNoPluginsOptionCommandInterface {
|
||||
|
||||
/**
|
||||
* Error code returned if DB configuration is aborted by user.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ABORTED_BY_USER = -1;
|
||||
|
||||
/**
|
||||
* Error code returned if DB configuration succeed.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const SUCCESS = 0;
|
||||
|
||||
/**
|
||||
* Error code returned if DB connection initialization fails.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_DB_CONNECTION_FAILED = 1;
|
||||
|
||||
/**
|
||||
* Error code returned if DB engine is unsupported.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_DB_ENGINE_UNSUPPORTED = 2;
|
||||
|
||||
/**
|
||||
* Error code returned when trying to configure and having a DB config already set.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_DB_CONFIG_ALREADY_SET = 3;
|
||||
|
||||
/**
|
||||
* Error code returned when failing to save database configuration file.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_DB_CONFIG_FILE_NOT_SAVED = 4;
|
||||
|
||||
protected function configure() {
|
||||
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:database:install');
|
||||
$this->setAliases(['db:install']);
|
||||
$this->setDescription('Install database schema');
|
||||
|
||||
$this->addOption(
|
||||
'db-host',
|
||||
'H',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
__('Database host'),
|
||||
'localhost'
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'db-name',
|
||||
'd',
|
||||
InputOption::VALUE_REQUIRED,
|
||||
__('Database name')
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'db-password',
|
||||
'p',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
__('Database password (will be prompted for value if option passed without value)'),
|
||||
'' // Empty string by default (enable detection of null if passed without value)
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'db-port',
|
||||
'P',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
__('Database port')
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'db-user',
|
||||
'u',
|
||||
InputOption::VALUE_REQUIRED,
|
||||
__('Database user')
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'reconfigure',
|
||||
'r',
|
||||
InputOption::VALUE_NONE,
|
||||
__('Reconfigure database, override configuration file if it already exists')
|
||||
);
|
||||
}
|
||||
|
||||
protected function interact(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$questions = [
|
||||
'db-name' => new Question(__('Database name:'), ''), // Required
|
||||
'db-user' => new Question(__('Database user:'), ''), // Required
|
||||
'db-password' => new Question(__('Database password:'), ''), // Prompt if null (passed without value)
|
||||
];
|
||||
$questions['db-password']->setHidden(true); // Make password input hidden
|
||||
|
||||
foreach ($questions as $name => $question) {
|
||||
if (null === $input->getOption($name)) {
|
||||
/** @var \Symfony\Component\Console\Helper\QuestionHelper $question_helper */
|
||||
$question_helper = $this->getHelper('question');
|
||||
$value = $question_helper->ask($input, $output, $question);
|
||||
$input->setOption($name, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function initDbConnection() {
|
||||
|
||||
return; // Prevent DB connection
|
||||
}
|
||||
|
||||
/**
|
||||
* Save database configuration file.
|
||||
*
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
* @throws InvalidArgumentException
|
||||
* @return string
|
||||
*/
|
||||
protected function configureDatabase(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$db_pass = $input->getOption('db-password');
|
||||
$db_host = $input->getOption('db-host');
|
||||
$db_name = $input->getOption('db-name');
|
||||
$db_port = $input->getOption('db-port');
|
||||
$db_user = $input->getOption('db-user');
|
||||
$db_hostport = $db_host . (!empty($db_port) ? ':' . $db_port : '');
|
||||
|
||||
$reconfigure = $input->getOption('reconfigure');
|
||||
|
||||
if (file_exists(GLPI_CONFIG_DIR . '/config_db.php') && !$reconfigure) {
|
||||
// Prevent overriding of existing DB
|
||||
$output->writeln(
|
||||
'<error>' . __('Database configuration already exists. Use --reconfigure option to override existing configuration.') . '</error>'
|
||||
);
|
||||
return self::ERROR_DB_CONFIG_ALREADY_SET;
|
||||
}
|
||||
|
||||
$this->validateConfigInput($input);
|
||||
|
||||
$run = $this->askForDbConfigConfirmation(
|
||||
$input,
|
||||
$output,
|
||||
$db_hostport,
|
||||
$db_name,
|
||||
$db_user
|
||||
);
|
||||
if (!$run) {
|
||||
$output->writeln(
|
||||
'<comment>' . __('Configuration aborted.') . '</comment>',
|
||||
OutputInterface::VERBOSITY_VERBOSE
|
||||
);
|
||||
return self::ABORTED_BY_USER;
|
||||
}
|
||||
|
||||
$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('<error>' . $message . '</error>', OutputInterface::VERBOSITY_QUIET);
|
||||
return self::ERROR_DB_CONNECTION_FAILED;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
$db_version_data = $mysqli->query('SELECT version()')->fetch_array();
|
||||
$checkdb = Config::displayCheckDbEngine(false, $db_version_data[0]);
|
||||
$message = ob_get_clean();
|
||||
if ($checkdb > 0) {
|
||||
$output->writeln('<error>' . $message . '</error>', OutputInterface::VERBOSITY_QUIET);
|
||||
return self::ERROR_DB_ENGINE_UNSUPPORTED;
|
||||
}
|
||||
|
||||
$db_name = $mysqli->real_escape_string($db_name);
|
||||
|
||||
$output->writeln(
|
||||
'<comment>' . __('Saving configuration file...') . '</comment>',
|
||||
OutputInterface::VERBOSITY_VERBOSE
|
||||
);
|
||||
if (!DBConnection::createMainConfig($db_hostport, $db_user, $db_pass, $db_name)) {
|
||||
$message = sprintf(
|
||||
__('Cannot write configuration file "%s".'),
|
||||
GLPI_CONFIG_DIR . DIRECTORY_SEPARATOR . 'config_db.php'
|
||||
);
|
||||
$output->writeln(
|
||||
'<error>' . $message . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
return self::ERROR_DB_CONFIG_FILE_NOT_SAVED;
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
public function getNoPluginsOptionValue() {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if DB is already configured.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function isDbAlreadyConfigured() {
|
||||
|
||||
return file_exists(GLPI_CONFIG_DIR . '/config_db.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate configuration variables from input.
|
||||
*
|
||||
* @param InputInterface $input
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected function validateConfigInput(InputInterface $input) {
|
||||
|
||||
$db_name = $input->getOption('db-name');
|
||||
$db_user = $input->getOption('db-user');
|
||||
$db_pass = $input->getOption('db-password');
|
||||
|
||||
if (empty($db_name)) {
|
||||
throw new InvalidArgumentException(
|
||||
__('Database name defined by --db-name option cannot be empty.')
|
||||
);
|
||||
}
|
||||
|
||||
if (empty($db_user)) {
|
||||
throw new InvalidArgumentException(
|
||||
__('Database user defined by --db-user option cannot be empty.')
|
||||
);
|
||||
}
|
||||
|
||||
if (null === $db_pass) {
|
||||
// Will be null if option used without value and without interaction
|
||||
throw new InvalidArgumentException(
|
||||
__('--db-password option value cannot be null.')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask user to confirm DB configuration.
|
||||
*
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function askForDbConfigConfirmation(
|
||||
InputInterface $input,
|
||||
OutputInterface $output,
|
||||
$db_hostport,
|
||||
$db_name,
|
||||
$db_user) {
|
||||
|
||||
$informations = new Table($output);
|
||||
$informations->addRow([__('Database host'), $db_hostport]);
|
||||
$informations->addRow([__('Database name'), $db_name]);
|
||||
$informations->addRow([__('Database user'), $db_user]);
|
||||
$informations->render();
|
||||
|
||||
if ($input->getOption('no-interaction')) {
|
||||
// Consider that config is validated if user require no interaction
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @var \Symfony\Component\Console\Helper\QuestionHelper $question_helper */
|
||||
$question_helper = $this->getHelper('question');
|
||||
return $question_helper->ask(
|
||||
$input,
|
||||
$output,
|
||||
new ConfirmationQuestion(__('Do you want to continue ?') . ' [Yes/no]', true)
|
||||
);
|
||||
}
|
||||
}
|
||||
106
inc/console/database/checkcommand.class.php
Normal file
106
inc/console/database/checkcommand.class.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Database;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use Glpi\Console\AbstractCommand;
|
||||
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use SebastianBergmann\Diff\Differ;
|
||||
|
||||
class CheckCommand extends AbstractCommand {
|
||||
|
||||
/**
|
||||
* Error code returned when failed to read empty SQL file.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_UNABLE_TO_READ_EMPTYSQL = 1;
|
||||
|
||||
protected function configure() {
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:database:check');
|
||||
$this->setAliases(['db:check']);
|
||||
$this->setDescription(__('Check for schema differences between current database and installation file.'));
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$differ = new Differ();
|
||||
|
||||
if (false === ($empty_file = realpath(GLPI_ROOT . '/install/mysql/glpi-empty.sql'))
|
||||
|| false === ($empty_sql = file_get_contents($empty_file))) {
|
||||
$message = sprintf(__('Unable to read installation file "%s".'), $empty_file);
|
||||
$output->writeln(
|
||||
'<error>' . $message . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
return self::ERROR_UNABLE_TO_READ_EMPTYSQL;
|
||||
}
|
||||
|
||||
$matches = [];
|
||||
preg_match_all('/CREATE TABLE `(.+)`[^;]+/', $empty_sql, $matches);
|
||||
$empty_tables_names = $matches[1];
|
||||
$empty_tables_schemas = $matches[0];
|
||||
|
||||
foreach ($empty_tables_schemas as $index => $table_schema) {
|
||||
$table_name = $empty_tables_names[$index];
|
||||
|
||||
$output->writeln(
|
||||
sprintf(__('Processing table "%s"...'), $table_name),
|
||||
OutputInterface::VERBOSITY_VERY_VERBOSE
|
||||
);
|
||||
|
||||
$base_table_struct = $this->db->getTableSchema($table_name, $table_schema);
|
||||
$existing_table_struct = $this->db->getTableSchema($table_name);
|
||||
|
||||
if ($existing_table_struct['schema'] != $base_table_struct['schema']) {
|
||||
$message = sprintf(__('Table schema differs for table "%s".'), $table_name);
|
||||
$output->writeln(
|
||||
'<info>' . $message . '</info>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
$output->write(
|
||||
$differ->diff($base_table_struct['schema'], $existing_table_struct['schema'])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
}
|
||||
63
inc/console/database/configurecommand.class.php
Normal file
63
inc/console/database/configurecommand.class.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Database;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class ConfigureCommand extends AbstractConfigureCommand {
|
||||
|
||||
protected function configure() {
|
||||
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:database:configure');
|
||||
$this->setAliases(['db:configure']);
|
||||
$this->setDescription('Define database configuration');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$result = $this->configureDatabase($input, $output);
|
||||
|
||||
if (self::ABORTED_BY_USER === $result) {
|
||||
return 0; // Considered as success
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
342
inc/console/database/installcommand.class.php
Normal file
342
inc/console/database/installcommand.class.php
Normal file
@ -0,0 +1,342 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
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(
|
||||
'<error>' . __('Database configuration already exists. Use --reconfigure option to override existing configuration.') . '</error>'
|
||||
);
|
||||
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(
|
||||
'<comment>' . __('Installation aborted.') . '</comment>',
|
||||
OutputInterface::VERBOSITY_VERBOSE
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Create security key
|
||||
$glpikey = new GLPIKey();
|
||||
if (!$glpikey->keyExists() && !$glpikey->generate()) {
|
||||
$message = __('Security key cannot be generated!');
|
||||
$output->writeln('<error>' . $message . '</error>', 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('<error>' . $message . '</error>', OutputInterface::VERBOSITY_QUIET);
|
||||
return self::ERROR_DB_CONNECTION_FAILED;
|
||||
}
|
||||
|
||||
// Create database or select existing one
|
||||
$output->writeln(
|
||||
'<comment>' . __('Creating the database...') . '</comment>',
|
||||
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('<error>' . $message . '</error>', 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(
|
||||
'<error>' . __('Database already contains "glpi_*" tables. Use --force option to override existing database.') . '</error>'
|
||||
);
|
||||
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(
|
||||
'<comment>' . __('Loading default schema...') . '</comment>',
|
||||
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('<error>' . $message . '</error>', OutputInterface::VERBOSITY_QUIET);
|
||||
return self::ERROR_SCHEMA_CREATION_FAILED;
|
||||
}
|
||||
|
||||
$output->writeln('<info>' . __('Installation done.') . '</info>');
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
203
inc/console/database/updatecommand.class.php
Normal file
203
inc/console/database/updatecommand.class.php
Normal file
@ -0,0 +1,203 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Database;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use Glpi\Console\AbstractCommand;
|
||||
use Glpi\Console\Command\ForceNoPluginsOptionCommandInterface;
|
||||
use Migration;
|
||||
use Session;
|
||||
use Update;
|
||||
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
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 UpdateCommand extends AbstractCommand implements ForceNoPluginsOptionCommandInterface {
|
||||
|
||||
/**
|
||||
* Error code returned when trying to update from an unstable version.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_NO_UNSTABLE_UPDATE = 1;
|
||||
|
||||
/**
|
||||
* Error code returned when security key file is missing.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_MISSING_SECURITY_KEY_FILE = 2;
|
||||
|
||||
protected function configure() {
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:database:update');
|
||||
$this->setAliases(['db:update']);
|
||||
$this->setDescription(__('Update database schema to new version'));
|
||||
|
||||
$this->addOption(
|
||||
'allow-unstable',
|
||||
'u',
|
||||
InputOption::VALUE_NONE,
|
||||
__('Allow update to an unstable version')
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'force',
|
||||
'f',
|
||||
InputOption::VALUE_NONE,
|
||||
__('Force execution of update from v-1 version of GLPI even if schema did not changed')
|
||||
);
|
||||
}
|
||||
|
||||
protected function initialize(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
parent::initialize($input, $output);
|
||||
|
||||
$this->outputWarningOnMissingOptionnalRequirements();
|
||||
|
||||
$this->db->disableTableCaching();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$allow_unstable = $input->getOption('allow-unstable');
|
||||
$force = $input->getOption('force');
|
||||
$no_interaction = $input->getOption('no-interaction'); // Base symfony/console option
|
||||
|
||||
$update = new Update($this->db);
|
||||
|
||||
// Initialize entities
|
||||
$_SESSION['glpidefault_entity'] = 0;
|
||||
Session::initEntityProfiles(2);
|
||||
Session::changeProfile(4);
|
||||
|
||||
// Display current/future state informations
|
||||
$currents = $update->getCurrents();
|
||||
$current_version = $currents['version'];
|
||||
$current_db_version = $currents['dbversion'];
|
||||
|
||||
global $migration; // Migration scripts are using global migrations
|
||||
$migration = new Migration(GLPI_SCHEMA_VERSION);
|
||||
$migration->setOutputHandler($output);
|
||||
$update->setMigration($migration);
|
||||
|
||||
$informations = new Table($output);
|
||||
$informations->setHeaders(['', __('Current'), _n('Target', 'Targets', 1)]);
|
||||
$informations->addRow([__('Database host'), $this->db->dbhost, '']);
|
||||
$informations->addRow([__('Database name'), $this->db->dbdefault, '']);
|
||||
$informations->addRow([__('Database user'), $this->db->dbuser, '']);
|
||||
$informations->addRow([__('GLPI version'), $current_version, GLPI_VERSION]);
|
||||
$informations->addRow([__('GLPI database version'), $current_db_version, GLPI_SCHEMA_VERSION]);
|
||||
$informations->render();
|
||||
|
||||
if (defined('GLPI_PREVER')) {
|
||||
// Prevent unstable update unless explicitly asked
|
||||
if (!$allow_unstable && version_compare($current_db_version, GLPI_SCHEMA_VERSION, 'ne')) {
|
||||
$output->writeln(
|
||||
sprintf(
|
||||
'<error>' . __('%s is not a stable release. Please upgrade manually or add --allow-unstable option.') . '</error>',
|
||||
GLPI_SCHEMA_VERSION
|
||||
),
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
return self::ERROR_NO_UNSTABLE_UPDATE;
|
||||
}
|
||||
}
|
||||
|
||||
if (version_compare($current_db_version, GLPI_SCHEMA_VERSION, 'eq') && !$force) {
|
||||
$output->writeln('<info>' . __('No migration needed.') . '</info>');
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($update->isExpectedSecurityKeyFileMissing()) {
|
||||
$output->writeln(
|
||||
sprintf(
|
||||
'<error>' . __('The key file "%s" used to encrypt/decrypt sensitive data is missing. You should retrieve it from your previous installation or encrypted data will be unreadable.') . '</error>',
|
||||
$update->getExpectedSecurityKeyFilePath()
|
||||
),
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
|
||||
if ($no_interaction) {
|
||||
return self::ERROR_MISSING_SECURITY_KEY_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$no_interaction) {
|
||||
// Ask for confirmation (unless --no-interaction)
|
||||
/** @var \Symfony\Component\Console\Helper\QuestionHelper $question_helper */
|
||||
$question_helper = $this->getHelper('question');
|
||||
$run = $question_helper->ask(
|
||||
$input,
|
||||
$output,
|
||||
new ConfirmationQuestion(__('Do you want to continue ?') . ' [Yes/no]', true)
|
||||
);
|
||||
if (!$run) {
|
||||
$output->writeln(
|
||||
'<comment>' . __('Update aborted.') . '</comment>',
|
||||
OutputInterface::VERBOSITY_VERBOSE
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
$update->doUpdates($current_version);
|
||||
|
||||
if (version_compare($current_db_version, GLPI_SCHEMA_VERSION, 'ne')) {
|
||||
// Migration is considered as done as Update class has the responsibility
|
||||
// to run updates if schema has changed (even for "pre-versions".
|
||||
$output->writeln('<info>' . __('Migration done.') . '</info>');
|
||||
} else if ($force) {
|
||||
// Replay last update script even if there is no schema change.
|
||||
// It can be used in dev environment when update script has been updated/fixed.
|
||||
include_once(GLPI_ROOT . '/install/update_951_952.php');
|
||||
update951to952();
|
||||
|
||||
$output->writeln('<info>' . __('Last migration replayed.') . '</info>');
|
||||
}
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
public function getNoPluginsOptionValue() {
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
479
inc/console/ldap/synchronizeuserscommand.class.php
Normal file
479
inc/console/ldap/synchronizeuserscommand.class.php
Normal file
@ -0,0 +1,479 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Ldap;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use AuthLDAP;
|
||||
use User;
|
||||
use Glpi\Console\AbstractCommand;
|
||||
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
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 SynchronizeUsersCommand extends AbstractCommand {
|
||||
|
||||
/**
|
||||
* Error code returned if LDAP connection failed.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_LDAP_CONNECTION_FAILED = 1;
|
||||
|
||||
/**
|
||||
* Error code returned if LDAP limit exceeded.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_LDAP_LIMIT_EXCEEDED = 2;
|
||||
|
||||
protected function configure() {
|
||||
|
||||
global $CFG_GLPI;
|
||||
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:ldap:synchronize_users');
|
||||
$this->setAliases(['ldap:sync']);
|
||||
$this->setDescription(__('Synchronize users against LDAP server informations'));
|
||||
|
||||
$this->addOption(
|
||||
'only-create-new',
|
||||
'c',
|
||||
InputOption::VALUE_NONE,
|
||||
__('Only create new users')
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'only-update-existing',
|
||||
'u',
|
||||
InputOption::VALUE_NONE,
|
||||
__('Only update existing users')
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'ldap-server-id',
|
||||
's',
|
||||
InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
|
||||
__('Synchronize only users attached to this LDAP server')
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'ldap-filter',
|
||||
'f',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
__('Filter to apply on LDAP search')
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'begin-date',
|
||||
null,
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
sprintf(
|
||||
__('Begin date to apply in "modifyTimestamp" filter (see %s for supported formats)'),
|
||||
'http://php.net/manual/en/datetime.formats.php'
|
||||
)
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'end-date',
|
||||
null,
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
sprintf(
|
||||
__('End date to apply in "modifyTimestamp" filter (see %s for supported formats)'),
|
||||
'http://php.net/manual/en/datetime.formats.php'
|
||||
)
|
||||
);
|
||||
|
||||
$strategies = AuthLDAP::getLdapDeletedUserActionOptions();
|
||||
$description = sprintf(
|
||||
__('Force strategy used for deleted users (current configured action: "%s")'),
|
||||
(isset($CFG_GLPI['user_deleted_ldap']) ? $CFG_GLPI['user_deleted_ldap'] : __('unknown'))
|
||||
);
|
||||
$description .= "\n" . __('Possible values are:') . "\n";
|
||||
$description .= implode(
|
||||
"\n",
|
||||
array_map(
|
||||
function ($key, $value) { return '- ' . sprintf(__('%1$s: %2$s'), $key, $value); },
|
||||
array_keys($strategies),
|
||||
$strategies
|
||||
)
|
||||
);
|
||||
$this->addOption(
|
||||
'deleted-user-strategy',
|
||||
'd',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
$description
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
global $CFG_GLPI;
|
||||
|
||||
$this->validateInput($input);
|
||||
|
||||
$only_create = $input->getOption('only-create-new');
|
||||
$only_update = $input->getOption('only-update-existing');
|
||||
|
||||
$actions = [];
|
||||
if ($only_create) {
|
||||
$actions = [
|
||||
AuthLDAP::ACTION_IMPORT, // Import unexisting users
|
||||
];
|
||||
} else if ($only_update) {
|
||||
$actions = [
|
||||
AuthLDAP::ACTION_SYNCHRONIZE, // Update existing users but does not handle deleted ones
|
||||
];
|
||||
} else {
|
||||
$actions = [
|
||||
AuthLDAP::ACTION_IMPORT, // Import unexisting users
|
||||
AuthLDAP::ACTION_ALL, // Update existing users and handle deleted ones
|
||||
];
|
||||
$deleted_user_strategy = $input->getOption('deleted-user-strategy');
|
||||
if (null !== $deleted_user_strategy) {
|
||||
$CFG_GLPI['user_deleted_ldap'] = $deleted_user_strategy;
|
||||
}
|
||||
}
|
||||
|
||||
$servers_id = $input->getOption('ldap-server-id');
|
||||
if (empty($servers_id)) {
|
||||
$servers_iterator = $this->db->request(
|
||||
[
|
||||
'SELECT' => 'id',
|
||||
'FROM' => AuthLDAP::getTable(),
|
||||
'WHERE' => [
|
||||
'is_active' => 1,
|
||||
],
|
||||
]
|
||||
);
|
||||
if ($servers_iterator->count() === 0) {
|
||||
$output->writeln('<info>' . __('No active LDAP server found.') . '</info>');
|
||||
return 0;
|
||||
}
|
||||
foreach ($servers_iterator as $server) {
|
||||
$servers_id[] = $server['id'];
|
||||
}
|
||||
}
|
||||
|
||||
$ldap_filter = $input->getOption('ldap-filter');
|
||||
$begin_date = $input->getOption('begin-date');
|
||||
$end_date = $input->getOption('end-date');
|
||||
|
||||
if (!$input->getOption('no-interaction')) {
|
||||
// Ask for confirmation (unless --no-interaction)
|
||||
|
||||
$servers_iterator = $this->db->request(
|
||||
[
|
||||
'SELECT' => ['id', 'name'],
|
||||
'FROM' => AuthLDAP::getTable(),
|
||||
'WHERE' => [
|
||||
'id' => $servers_id,
|
||||
],
|
||||
]
|
||||
);
|
||||
$servers_names = [];
|
||||
foreach ($servers_iterator as $server) {
|
||||
$servers_names[] = sprintf(__('%1$s (%2$s)'), $server['name'], $server['id']);
|
||||
}
|
||||
|
||||
$informations = new Table($output);
|
||||
$informations->addRow([__('LDAP servers'), implode(', ', $servers_names)]);
|
||||
$informations->addRow([__('LDAP filter'), $ldap_filter]);
|
||||
$informations->addRow([__('Begin date'), $begin_date]);
|
||||
$informations->addRow([__('End date'), $end_date]);
|
||||
$informations->render();
|
||||
|
||||
/** @var \Symfony\Component\Console\Helper\QuestionHelper $question_helper */
|
||||
$question_helper = $this->getHelper('question');
|
||||
$run = $question_helper->ask(
|
||||
$input,
|
||||
$output,
|
||||
new ConfirmationQuestion(__('Do you want to continue ?') . ' [Yes/no]', true)
|
||||
);
|
||||
if (!$run) {
|
||||
$output->writeln(
|
||||
'<comment>' . __('Synchronization aborted.') . '</comment>',
|
||||
OutputInterface::VERBOSITY_VERBOSE
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($servers_id as $server_id) {
|
||||
$server = new AuthLDAP();
|
||||
if (!$server->getFromDB($server_id)) {
|
||||
throw new RuntimeException(__('Unable to load LDAP server informations.'));
|
||||
}
|
||||
if (!$server->isActive()) {
|
||||
// Can happen if id is specified in command call
|
||||
$message = sprintf(
|
||||
__('LDAP server "%s" is inactive, no synchronization will be done against it.'),
|
||||
$server_id
|
||||
);
|
||||
$output->writeln('<info>' . $message . '</info>');
|
||||
continue;
|
||||
}
|
||||
|
||||
$output->writeln(
|
||||
'<info>' . sprintf(__('Processing LDAP server "%s"...'), $server_id) . '</info>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
|
||||
foreach ($actions as $action) {
|
||||
$results = [
|
||||
AuthLDAP::USER_IMPORTED => 0,
|
||||
AuthLDAP::USER_SYNCHRONIZED => 0,
|
||||
AuthLDAP::USER_DELETED_LDAP => 0,
|
||||
];
|
||||
$limitexceeded = false;
|
||||
|
||||
$users = AuthLDAP::getAllUsers(
|
||||
[
|
||||
'authldaps_id' => $server_id,
|
||||
'mode' => $action,
|
||||
'ldap_filter' => null !== $ldap_filter ? $ldap_filter : '',
|
||||
'script' => true,
|
||||
'begin_date' => null !== $begin_date ? $begin_date : '',
|
||||
'end_date' => null !== $end_date ? $end_date : '',
|
||||
],
|
||||
$results,
|
||||
$limitexceeded
|
||||
);
|
||||
|
||||
if (false === $users) {
|
||||
if ($limitexceeded) {
|
||||
$message = sprintf(
|
||||
__('LDAP server "%s" size limit exceeded.'),
|
||||
$server_id
|
||||
);
|
||||
$code = self::ERROR_LDAP_LIMIT_EXCEEDED;
|
||||
} else {
|
||||
$message = sprintf(
|
||||
__('Error while contacting the LDAP server "%s".'),
|
||||
$server_id
|
||||
);
|
||||
$code = self::ERROR_LDAP_CONNECTION_FAILED;
|
||||
}
|
||||
$output->writeln(
|
||||
'<error>' . $message . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
return $code;
|
||||
}
|
||||
|
||||
$action_message = '';
|
||||
switch ($action) {
|
||||
case AuthLDAP::ACTION_IMPORT;
|
||||
$action_message = __('Import new users from server "%s"...');
|
||||
break;
|
||||
case AuthLDAP::ACTION_SYNCHRONIZE;
|
||||
$action_message = __('Update existing users with server "%s"...');
|
||||
break;
|
||||
case AuthLDAP::ACTION_ALL;
|
||||
$action_message = __('Synchronize users with server "%s"...');
|
||||
break;
|
||||
}
|
||||
|
||||
$output->writeln(
|
||||
'<info>' . sprintf($action_message, $server_id) . '</info>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
|
||||
if (count($users) === 0) {
|
||||
$output->writeln(
|
||||
'<info>' . __('No users found.') . '</info>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$users_progress_bar = new ProgressBar($output, count($users));
|
||||
$users_progress_bar->start();
|
||||
|
||||
foreach ($users as $user) {
|
||||
$users_progress_bar->advance(1);
|
||||
|
||||
$user_sync_field = null;
|
||||
if ($server->isSyncFieldEnabled()) {
|
||||
$sync_field = $server->fields['sync_field'];
|
||||
if (isset($user[$sync_field])) {
|
||||
$user_sync_field = $server->getFieldValue($user, $sync_field);
|
||||
}
|
||||
}
|
||||
|
||||
$existing_user = $server->getLdapExistingUser(
|
||||
$user['user'],
|
||||
$server_id,
|
||||
$user_sync_field
|
||||
);
|
||||
|
||||
if ($existing_user instanceof User && $action == AuthLDAP::ACTION_IMPORT) {
|
||||
continue; // Do not update existing user if current action is only import
|
||||
}
|
||||
|
||||
$user_field = 'name';
|
||||
$id_field = $server->fields['login_field'];
|
||||
$value = $user['user'];
|
||||
if ($server->isSyncFieldEnabled()
|
||||
&& (!($existing_user instanceof User)
|
||||
|| !empty($existing_user->fields['sync_field']))) {
|
||||
$value = $user_sync_field;
|
||||
$user_field = 'sync_field';
|
||||
$id_field = $server->fields['sync_field'];
|
||||
}
|
||||
|
||||
$result = AuthLDAP::ldapImportUserByServerId(
|
||||
[
|
||||
'method' => AuthLDAP::IDENTIFIER_LOGIN,
|
||||
'value' => $value,
|
||||
'identifier_field' => $id_field,
|
||||
'user_field' => $user_field
|
||||
],
|
||||
$action,
|
||||
$server_id
|
||||
);
|
||||
|
||||
if (false !== $result) {
|
||||
$results[$result['action']] += 1;
|
||||
} else {
|
||||
$this->writelnOutputWithProgressBar(
|
||||
sprintf(__('Unable to synchronize user "%s".'), $user['user']),
|
||||
$users_progress_bar,
|
||||
OutputInterface::VERBOSITY_VERBOSE
|
||||
);
|
||||
}
|
||||
}
|
||||
$users_progress_bar->finish();
|
||||
$output->write(PHP_EOL);
|
||||
}
|
||||
|
||||
if ($output->getVerbosity() > OutputInterface::VERBOSITY_QUIET) {
|
||||
$result_output = new Table($output);
|
||||
$result_output->setHeaders(
|
||||
[
|
||||
__('LDAP server'),
|
||||
__('Imported'),
|
||||
__('Synchronized'),
|
||||
__('Deleted from LDAP'),
|
||||
]
|
||||
);
|
||||
$result_output->addRow(
|
||||
[
|
||||
$server_id,
|
||||
$results[AuthLDAP::USER_IMPORTED],
|
||||
$results[AuthLDAP::USER_SYNCHRONIZED],
|
||||
$results[AuthLDAP::USER_DELETED_LDAP],
|
||||
]
|
||||
);
|
||||
$result_output->render();
|
||||
}
|
||||
}
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate command input.
|
||||
*
|
||||
* @param InputInterface $input
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function validateInput(InputInterface $input) {
|
||||
|
||||
$only_create = $input->getOption('only-create-new');
|
||||
$only_update = $input->getOption('only-update-existing');
|
||||
if (false !== $only_create && false !== $only_update) {
|
||||
throw new InvalidArgumentException(
|
||||
__('Option --only-create-new is not compatible with option --only-update-existing.')
|
||||
);
|
||||
}
|
||||
|
||||
$servers_id = $input->getOption('ldap-server-id');
|
||||
$server = new AuthLDAP();
|
||||
foreach ($servers_id as $server_id) {
|
||||
if (!$server->getFromDB($server_id)) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(__('--ldap-server-id value "%s" is not a valid LDAP server id.'), $server_id)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (['begin-date', 'end-date'] as $option_name) {
|
||||
// Convert date to 'Y:m:d H:i:s' formatted string
|
||||
$date = $input->getOption($option_name);
|
||||
|
||||
if (null !== $date) {
|
||||
$parsed_date = strtotime($date);
|
||||
if (false === $parsed_date) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(__('Unable to parse --%1$s value "%2$s".'), $option_name, $date)
|
||||
);
|
||||
}
|
||||
$input->setOption($option_name, date('Y:m:d H:i:s', $parsed_date));
|
||||
}
|
||||
}
|
||||
|
||||
$begin_date = $input->getOption('begin-date');
|
||||
$end_date = $input->getOption('end-date');
|
||||
if ($begin_date > $end_date) {
|
||||
throw new InvalidArgumentException(
|
||||
__('Option --begin-date value has to be lower than option --end-date value.')
|
||||
);
|
||||
}
|
||||
|
||||
$deleted_user_strategy = $input->getOption('deleted-user-strategy');
|
||||
if (null !== $deleted_user_strategy) {
|
||||
$strategies = AuthLDAP::getLdapDeletedUserActionOptions();
|
||||
if (!in_array($deleted_user_strategy, array_keys($strategies))) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
__('--deleted-user-strategy value "%s" is not valid.'),
|
||||
$deleted_user_strategy
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Maintenance;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use Config;
|
||||
use Glpi\Console\AbstractCommand;
|
||||
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class DisableMaintenanceModeCommand extends AbstractCommand {
|
||||
|
||||
protected function configure() {
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:maintenance:disable');
|
||||
$this->setAliases(
|
||||
[
|
||||
'maintenance:disable',
|
||||
]
|
||||
);
|
||||
$this->setDescription(__('Disable maintenance mode'));
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$config = new Config();
|
||||
$config->setConfigurationValues('core', ['maintenance_mode' => '0']);
|
||||
|
||||
$output->writeln('<info>' . __('Maintenance mode disabled.') . '</info>');
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Maintenance;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use Config;
|
||||
use Glpi\Console\AbstractCommand;
|
||||
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class EnableMaintenanceModeCommand extends AbstractCommand {
|
||||
|
||||
protected function configure() {
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:maintenance:enable');
|
||||
$this->setAliases(
|
||||
[
|
||||
'maintenance:enable',
|
||||
]
|
||||
);
|
||||
$this->setDescription(__('Enable maintenance mode'));
|
||||
|
||||
$this->addOption(
|
||||
'text',
|
||||
't',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
__('Text to display during maintenance')
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
global $CFG_GLPI;
|
||||
|
||||
$values = [
|
||||
'maintenance_mode' => '1'
|
||||
];
|
||||
if ($input->hasOption('text')) {
|
||||
$values['maintenance_text'] = $input->getOption('text');
|
||||
}
|
||||
$config = new Config();
|
||||
$config->setConfigurationValues('core', $values);
|
||||
|
||||
$message = sprintf(
|
||||
__('Maintenance mode activated. Backdoor using: %s'),
|
||||
$CFG_GLPI['url_base'] . '/index.php?skipMaintenance=1'
|
||||
);
|
||||
$output->writeln('<info>' . $message . '</info>');
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
}
|
||||
746
inc/console/migration/appliancesplugintocorecommand.class.php
Normal file
746
inc/console/migration/appliancesplugintocorecommand.class.php
Normal file
@ -0,0 +1,746 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
* Yves TESNIERE
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
namespace Glpi\Console\Migration;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use Appliance;
|
||||
use ApplianceType;
|
||||
use ApplianceEnvironment;
|
||||
use Appliance_Item;
|
||||
use Appliance_Item_Relation;
|
||||
use Change_Item;
|
||||
use Contract_Item;
|
||||
use DB;
|
||||
use Document_Item;
|
||||
use Domain;
|
||||
use Infocom;
|
||||
use Location;
|
||||
use Network;
|
||||
use Toolbox;
|
||||
use Glpi\Console\AbstractCommand;
|
||||
use Item_Problem;
|
||||
use Item_Project;
|
||||
use Item_Ticket;
|
||||
use KnowbaseItem_Item;
|
||||
use Log;
|
||||
use Profile;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
|
||||
class AppliancesPluginToCoreCommand extends AbstractCommand {
|
||||
|
||||
/**
|
||||
* Error code returned if plugin version or plugin data is invalid.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_PLUGIN_VERSION_OR_DATA_INVALID = 1;
|
||||
|
||||
/**
|
||||
* Error code returned if import failed.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_PLUGIN_IMPORT_FAILED = 2;
|
||||
|
||||
/**
|
||||
* list of possible relations of the plugin indexed by their correspond integer in the plugin
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
const PLUGIN_RELATION_TYPES = [
|
||||
1 => Location::class,
|
||||
2 => Network::class,
|
||||
3 => Domain::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* list of usefull plugin tables and fields
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
const PLUGIN_APPLIANCE_TABLES = [
|
||||
"glpi_plugin_appliances_appliances" => [
|
||||
"id",
|
||||
"entities_id",
|
||||
"is_recursive",
|
||||
"name",
|
||||
"is_deleted",
|
||||
"plugin_appliances_appliancetypes_id",
|
||||
"comment",
|
||||
"locations_id",
|
||||
"plugin_appliances_environments_id",
|
||||
"users_id",
|
||||
"users_id_tech",
|
||||
"groups_id",
|
||||
"groups_id_tech",
|
||||
"relationtype",
|
||||
"date_mod",
|
||||
"states_id",
|
||||
"externalid",
|
||||
"serial",
|
||||
"otherserial"
|
||||
],
|
||||
"glpi_plugin_appliances_appliancetypes" => ["id","entities_id","is_recursive","name","comment"],
|
||||
"glpi_plugin_appliances_appliances_items" => ["id", "plugin_appliances_appliances_id","items_id","itemtype"],
|
||||
"glpi_plugin_appliances_environments" => ["id","name","comment" ],
|
||||
"glpi_plugin_appliances_relations" => ["id","plugin_appliances_appliances_items_id","relations_id"]
|
||||
];
|
||||
|
||||
/**
|
||||
* itemtype corresponding to appliance in plugin
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PLUGIN_APPLIANCE_ITEMTYPE = "PluginAppliancesAppliance";
|
||||
|
||||
/**
|
||||
* itemtype corresponding to appliance in core
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const CORE_APPLIANCE_ITEMTYPE = "Appliance";
|
||||
|
||||
protected function configure() {
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:migration:appliances_plugin_to_core');
|
||||
$this->setDescription(__('Migrate Appliances plugin data into GLPI core tables'));
|
||||
|
||||
$this->addOption(
|
||||
'skip-errors',
|
||||
's',
|
||||
InputOption::VALUE_NONE,
|
||||
__('Do not exit on import errors')
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
$no_interaction = $input->getOption('no-interaction');
|
||||
if (!$no_interaction) {
|
||||
// Ask for confirmation (unless --no-interaction)
|
||||
$output->writeln([
|
||||
__('You are about to launch migration of Appliances plugin data into GLPI core tables.'),
|
||||
__('Any previous appliance created in core will be lost.'),
|
||||
__('It is better to make a backup of your existing data before continuing.')
|
||||
]
|
||||
);
|
||||
|
||||
/**
|
||||
* @var QuestionHelper $question_helper
|
||||
*/
|
||||
$question_helper = $this->getHelper('question');
|
||||
$run = $question_helper->ask(
|
||||
$input,
|
||||
$output,
|
||||
new ConfirmationQuestion(
|
||||
'<comment>' . __('Do you want to launch migration ?') . ' [yes/No]</comment>',
|
||||
false
|
||||
)
|
||||
);
|
||||
if (!$run) {
|
||||
$output->writeln(
|
||||
'<comment>' . __('Migration aborted.') . '</comment>',
|
||||
OutputInterface::VERBOSITY_VERBOSE
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->checkPlugin()) {
|
||||
return self::ERROR_PLUGIN_VERSION_OR_DATA_INVALID;
|
||||
}
|
||||
|
||||
if (!$this->migratePlugin()) {
|
||||
return self::ERROR_PLUGIN_IMPORT_FAILED;
|
||||
}
|
||||
|
||||
$output->writeln('<info>' . __('Migration done.') . '</info>');
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that required tables exists and fields are OK for migration.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function checkPlugin(): bool {
|
||||
$missing_tables = false;
|
||||
foreach (self::PLUGIN_APPLIANCE_TABLES as $table => $fields) {
|
||||
if (!$this->db->tableExists($table)) {
|
||||
$this->output->writeln(
|
||||
'<error>' . sprintf(__('Appliances plugin table "%s" is missing.'), $table) . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
$missing_tables = true;
|
||||
} else {
|
||||
foreach ($fields as $field) {
|
||||
if (!$this->db->fieldExists($table, $field)) {
|
||||
$this->output->writeln(
|
||||
'<error>' . sprintf(__('Appliances plugin field "%s" is missing.'), $table.'.'.$field) . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
$missing_tables = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($missing_tables) {
|
||||
$this->output->writeln(
|
||||
'<error>' . __('Migration cannot be done.') . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean data from core tables.
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private function cleanCoreTables() {
|
||||
$core_tables = [
|
||||
Appliance::getTable(),
|
||||
ApplianceType::getTable(),
|
||||
ApplianceEnvironment::getTable(),
|
||||
Appliance_Item::getTable(),
|
||||
Appliance_Item_Relation::getTable(),
|
||||
];
|
||||
|
||||
foreach ($core_tables as $table) {
|
||||
$result = $this->db->query('TRUNCATE ' . DB::quoteName($table));
|
||||
|
||||
if (!$result) {
|
||||
throw new RuntimeException(
|
||||
sprintf('Unable to truncate table "%s"', $table)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$table = Infocom::getTable();
|
||||
$result = $this->db->delete($table, [
|
||||
'itemtype' => self::CORE_APPLIANCE_ITEMTYPE
|
||||
]);
|
||||
if (!$result) {
|
||||
throw new RuntimeException(
|
||||
sprintf('Unable to clean table "%s"', $table)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copy plugin tables to backup tables from plugin to core keeping same ID.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function migratePlugin(): bool {
|
||||
$this->cleanCoreTables();
|
||||
|
||||
return $this->createApplianceTypes()
|
||||
&& $this->createApplianceEnvironments()
|
||||
&& $this->createApplianceRelations()
|
||||
&& $this->createApplianceItems()
|
||||
&& $this->createAppliances()
|
||||
&& $this->updateItemtypes()
|
||||
&& $this->updateProfilesApplianceRights();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update profile rights (Associable items to a ticket).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function updateProfilesApplianceRights(): bool {
|
||||
$this->output->writeln(
|
||||
'<comment>' . __('Updating profiles...') . '</comment>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
|
||||
$table = Profile::getTable();
|
||||
$result = $this->db->query(
|
||||
sprintf(
|
||||
"UPDATE %s SET helpdesk_item_type = REPLACE(helpdesk_item_type, '%s', '%s')",
|
||||
DB::quoteName($table),
|
||||
self::PLUGIN_APPLIANCE_ITEMTYPE,
|
||||
self::CORE_APPLIANCE_ITEMTYPE
|
||||
)
|
||||
);
|
||||
if (false === $result) {
|
||||
$this->outputImportError(
|
||||
sprintf(__('Unable to update "%s" in profiles.'), __('Associable items to a ticket'))
|
||||
);
|
||||
if (!$this->input->getOption('skip-errors')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename itemtype in core tables.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function updateItemtypes(): bool {
|
||||
$this->output->writeln(
|
||||
'<comment>' . __('Updating GLPI itemtypes...') . '</comment>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
$itemtypes_tables = [
|
||||
Item_Ticket::getTable(),
|
||||
Item_Problem::getTable(),
|
||||
Change_Item::getTable(),
|
||||
Item_Project::getTable(),
|
||||
Log::getTable(),
|
||||
Infocom::getTable(),
|
||||
Document_Item::getTable(),
|
||||
Contract_Item::getTable(),
|
||||
KnowbaseItem_Item::getTable(),
|
||||
];
|
||||
|
||||
foreach ($itemtypes_tables as $itemtype_table) {
|
||||
$result = $this->db->update($itemtype_table, [
|
||||
'itemtype' => self::CORE_APPLIANCE_ITEMTYPE,
|
||||
], [
|
||||
'itemtype' => self::PLUGIN_APPLIANCE_ITEMTYPE,
|
||||
]);
|
||||
|
||||
if (false === $result) {
|
||||
$this->outputImportError(
|
||||
sprintf(
|
||||
__('Migration of table "%s" failed with message "(%s) %s".'),
|
||||
$itemtype_table,
|
||||
$this->db->errno(),
|
||||
$this->db->error()
|
||||
)
|
||||
);
|
||||
if (!$this->input->getOption('skip-errors')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create appliance items.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function createApplianceItems(): bool {
|
||||
$this->output->writeln(
|
||||
'<comment>' . __('Creating Appliance Items...') . '</comment>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
|
||||
$iterator = $this->db->request([
|
||||
'FROM' => 'glpi_plugin_appliances_appliances_items'
|
||||
]);
|
||||
|
||||
if (!count($iterator)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$progress_bar = new ProgressBar($this->output);
|
||||
|
||||
foreach ($progress_bar->iterate($iterator) as $item) {
|
||||
$this->writelnOutputWithProgressBar(
|
||||
sprintf(
|
||||
__('Importing Appliance item "%d"...'),
|
||||
(int) $item['id']
|
||||
),
|
||||
$progress_bar,
|
||||
OutputInterface::VERBOSITY_VERY_VERBOSE
|
||||
);
|
||||
|
||||
$app_fields = Toolbox::sanitize([
|
||||
'id' => $item['id'],
|
||||
'appliances_id' => $item['plugin_appliances_appliances_id'],
|
||||
'items_id' => $item['items_id'],
|
||||
'itemtype' => $item['itemtype']
|
||||
]);
|
||||
|
||||
$appi = new Appliance_Item();
|
||||
if (!($appi_id = $appi->getFromDBByCrit($app_fields))) {
|
||||
$appi_id = $appi->add($app_fields);
|
||||
}
|
||||
|
||||
if (false === $appi_id) {
|
||||
$this->outputImportError(
|
||||
sprintf(__('Unable to create Appliance item %d.'), (int) $item['id']),
|
||||
$progress_bar
|
||||
);
|
||||
if (!$this->input->getOption('skip-errors')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->output->write(PHP_EOL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create appliance environments.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function createApplianceEnvironments(): bool {
|
||||
$this->output->writeln(
|
||||
'<comment>' . __('Creating Appliance Environment...') . '</comment>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
|
||||
$iterator = $this->db->request([
|
||||
'FROM' => 'glpi_plugin_appliances_environments'
|
||||
]);
|
||||
|
||||
if (!count($iterator)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$progress_bar = new ProgressBar($this->output);
|
||||
|
||||
foreach ($progress_bar->iterate($iterator) as $env) {
|
||||
$this->writelnOutputWithProgressBar(
|
||||
sprintf(
|
||||
__('Importing environment "%s"...'),
|
||||
$env['name']
|
||||
),
|
||||
$progress_bar,
|
||||
OutputInterface::VERBOSITY_VERY_VERBOSE
|
||||
);
|
||||
|
||||
$app_fields = Toolbox::sanitize([
|
||||
'id' => $env['id'],
|
||||
'name' => $env['name'],
|
||||
'comment' => $env['comment']
|
||||
]);
|
||||
|
||||
$appe = new ApplianceEnvironment();
|
||||
if (!($appe_id = $appe->getFromDBByCrit($app_fields))) {
|
||||
$appe_id = $appe->add($app_fields);
|
||||
}
|
||||
|
||||
if (false === $appe_id) {
|
||||
$this->outputImportError(
|
||||
sprintf(__('Unable to create Appliance environment %s (%d).'), $env['name'], (int) $env['id']),
|
||||
$progress_bar
|
||||
);
|
||||
if (!$this->input->getOption('skip-errors')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->output->write(PHP_EOL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create appliances.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function createAppliances(): bool {
|
||||
$this->output->writeln(
|
||||
'<comment>'. __('Creating Appliances...') . '</comment>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
$iterator = $this->db->request([
|
||||
'FROM' => 'glpi_plugin_appliances_appliances'
|
||||
]);
|
||||
|
||||
if (!count($iterator)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$progress_bar = new ProgressBar($this->output);
|
||||
|
||||
foreach ($progress_bar->iterate($iterator) as $appliance) {
|
||||
$this->writelnOutputWithProgressBar(
|
||||
sprintf(
|
||||
__('Importing appliance "%s"...'),
|
||||
$appliance['name']
|
||||
),
|
||||
$progress_bar,
|
||||
OutputInterface::VERBOSITY_VERY_VERBOSE
|
||||
);
|
||||
|
||||
$app_fields = Toolbox::sanitize([
|
||||
'id' => $appliance['id'],
|
||||
'entities_id' => $appliance['entities_id'],
|
||||
'is_recursive' => $appliance['is_recursive'],
|
||||
'name' => $appliance['name'],
|
||||
'is_deleted' => $appliance['is_deleted'],
|
||||
'appliancetypes_id' => $appliance['plugin_appliances_appliancetypes_id'],
|
||||
'comment' => $appliance['comment'],
|
||||
'locations_id' => $appliance['locations_id'],
|
||||
'manufacturers_id' => '0',
|
||||
'applianceenvironments_id' => $appliance['plugin_appliances_environments_id'],
|
||||
'users_id' => $appliance['users_id'],
|
||||
'users_id_tech' => $appliance['users_id_tech'],
|
||||
'groups_id' => $appliance['groups_id'],
|
||||
'groups_id_tech' => $appliance['groups_id_tech'],
|
||||
'date_mod' => $appliance['date_mod'],
|
||||
'is_helpdesk_visible' => $appliance['is_helpdesk_visible'],
|
||||
'states_id' => $appliance['states_id'],
|
||||
'externalidentifier' => $appliance['externalid'],
|
||||
'serial' => $appliance['serial'],
|
||||
'otherserial' => $appliance['otherserial']
|
||||
]);
|
||||
|
||||
$app = new Appliance();
|
||||
if (!($app_id = $app->getFromDBByCrit($app_fields))) {
|
||||
$app_id = $app->add($app_fields);
|
||||
}
|
||||
|
||||
if (false === $app_id) {
|
||||
$this->outputImportError(
|
||||
sprintf(__('Unable to create Appliance %s (%d).'), $appliance['name'], (int) $appliance['id']),
|
||||
$progress_bar
|
||||
);
|
||||
if (!$this->input->getOption('skip-errors')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->output->write(PHP_EOL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create appliance types.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function createApplianceTypes(): bool {
|
||||
$this->output->writeln(
|
||||
'<comment>' . __('Creating Appliance types...') . '</comment>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
|
||||
$iterator = $this->db->request([
|
||||
'FROM' => 'glpi_plugin_appliances_appliancetypes'
|
||||
]);
|
||||
|
||||
if (!count($iterator)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$progress_bar = new ProgressBar($this->output);
|
||||
|
||||
foreach ($progress_bar->iterate($iterator) as $type) {
|
||||
$this->writelnOutputWithProgressBar(
|
||||
sprintf(
|
||||
__('Importing type "%s"...'),
|
||||
$type['name']
|
||||
),
|
||||
$progress_bar,
|
||||
OutputInterface::VERBOSITY_VERY_VERBOSE
|
||||
);
|
||||
|
||||
$appt_fields = Toolbox::sanitize([
|
||||
'id' => $type['id'],
|
||||
'entities_id' => $type['entities_id'],
|
||||
'is_recursive' => $type['is_recursive'],
|
||||
'name' => $type['name'],
|
||||
'comment' => $type['comment'],
|
||||
'externalidentifier' => $type['externalid']
|
||||
]);
|
||||
|
||||
$appt = new ApplianceType();
|
||||
if (!($appt_id = $appt->getFromDBByCrit($appt_fields))) {
|
||||
$appt_id = $appt->add($appt_fields);
|
||||
}
|
||||
|
||||
if (false === $appt_id) {
|
||||
$this->outputImportError(
|
||||
sprintf(__('Unable to create Appliance environment %s (%d).'), $type['name'], (int) $type['id']),
|
||||
$progress_bar
|
||||
);
|
||||
if (!$this->input->getOption('skip-errors')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->output->write(PHP_EOL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create appliance relations.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function createApplianceRelations(): bool {
|
||||
$this->output->writeln(
|
||||
'<comment>' . __('Creating Appliance relations...') . '</comment>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
|
||||
$iterator = $this->db->request([
|
||||
'SELECT' => ['rel.*', 'app.relationtype'],
|
||||
'FROM' => 'glpi_plugin_appliances_relations AS rel',
|
||||
'INNER JOIN' => [
|
||||
'glpi_plugin_appliances_appliances_items AS items' => [
|
||||
'ON' => [
|
||||
'items' => 'id',
|
||||
'rel' => 'plugin_appliances_appliances_items_id'
|
||||
]
|
||||
],
|
||||
'glpi_plugin_appliances_appliances AS app' => [
|
||||
'ON' => [
|
||||
'app' => 'id',
|
||||
'items' => 'plugin_appliances_appliances_id'
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
if (!count($iterator)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$progress_bar = new ProgressBar($this->output);
|
||||
|
||||
foreach ($progress_bar->iterate($iterator) as $row) {
|
||||
$this->writelnOutputWithProgressBar(
|
||||
sprintf(
|
||||
__('Importing relation "%s"...'),
|
||||
$row['id']
|
||||
),
|
||||
$progress_bar,
|
||||
OutputInterface::VERBOSITY_VERY_VERBOSE
|
||||
);
|
||||
|
||||
$itemtype = self::PLUGIN_RELATION_TYPES[$row['relationtype']] ?? "";
|
||||
if ($itemtype == "") {
|
||||
$this->outputImportError(
|
||||
sprintf(
|
||||
__('Unable to found relation type %s from Appliance Item Relation %d.'),
|
||||
$row['relationtype'],
|
||||
(int) $row['id']
|
||||
),
|
||||
$progress_bar
|
||||
);
|
||||
if (!$this->input->getOption('skip-errors')) {
|
||||
return false;
|
||||
} else {
|
||||
continue; // Skip this relation
|
||||
}
|
||||
}
|
||||
|
||||
$appr_fields = Toolbox::sanitize([
|
||||
'id' => $row['id'],
|
||||
'appliances_items_id' => $row['plugin_appliances_appliances_items_id'],
|
||||
'itemtype' => $itemtype,
|
||||
'items_id' => $row['relations_id']
|
||||
]);
|
||||
|
||||
$appr = new Appliance_Item_Relation();
|
||||
if (!($appr_id = $appr->getFromDBByCrit($appr_fields))) {
|
||||
$appr_id = $appr->add($appr_fields);
|
||||
}
|
||||
|
||||
if (false === $appr_id) {
|
||||
$this->outputImportError(
|
||||
sprintf(__('Unable to create Appliance Item Relation %d.'), (int) $row['id']),
|
||||
$progress_bar
|
||||
);
|
||||
if (!$this->input->getOption('skip-errors')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->output->write(PHP_EOL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Output import error message.
|
||||
*
|
||||
* @param string $message
|
||||
* @param ProgressBar|null $progress_bar
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function outputImportError($message, ProgressBar $progress_bar = null) {
|
||||
|
||||
$skip_errors = $this->input->getOption('skip-errors');
|
||||
|
||||
$verbosity = $skip_errors
|
||||
? OutputInterface::VERBOSITY_NORMAL
|
||||
: OutputInterface::VERBOSITY_QUIET;
|
||||
|
||||
$message = '<error>' . $message . '</error>';
|
||||
|
||||
if ($skip_errors && $progress_bar instanceof ProgressBar) {
|
||||
$this->writelnOutputWithProgressBar(
|
||||
$message,
|
||||
$progress_bar,
|
||||
$verbosity
|
||||
);
|
||||
} else {
|
||||
if (!$skip_errors && $progress_bar instanceof ProgressBar) {
|
||||
$this->output->write(PHP_EOL); // Keep progress bar last state and go to next line
|
||||
}
|
||||
$this->output->writeln(
|
||||
$message,
|
||||
$verbosity
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
129
inc/console/migration/buildmissingtimestampscommand.class.php
Normal file
129
inc/console/migration/buildmissingtimestampscommand.class.php
Normal file
@ -0,0 +1,129 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Migration;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use CommonDBTM;
|
||||
use Glpi\Console\AbstractCommand;
|
||||
use Log;
|
||||
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class BuildMissingTimestampsCommand extends AbstractCommand {
|
||||
|
||||
protected function configure() {
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:migration:build_missing_timestamps');
|
||||
$this->setDescription(__('Set missing `date_creation` and `date_mod` values using log entries.'));
|
||||
$this->setHidden(true); // Hide this command as it is when migrating from really old GLPI version
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$tables_iterator = $this->db->request(
|
||||
[
|
||||
'SELECT' => [
|
||||
'table_name AS TABLE_NAME',
|
||||
'column_name AS COLUMN_NAME',
|
||||
],
|
||||
'FROM' => 'information_schema.columns',
|
||||
'WHERE' => [
|
||||
'table_schema' => $this->db->dbdefault,
|
||||
'table_name' => ['LIKE', 'glpi_%'],
|
||||
'column_name' => ['date_creation', 'date_mod'],
|
||||
],
|
||||
'ORDER' => ['table_name', 'column_name'],
|
||||
]
|
||||
);
|
||||
|
||||
$log_table = Log::getTable();
|
||||
|
||||
foreach ($tables_iterator as $table_info) {
|
||||
$table = $table_info['TABLE_NAME'];
|
||||
$itemtype = getItemTypeForTable($table);
|
||||
$column = $table_info['COLUMN_NAME'];
|
||||
|
||||
if (!is_a($itemtype, CommonDBTM::class, true)) {
|
||||
continue; // getItemTypeForTable() may not return a class name ("UNKNOWN" for example)
|
||||
}
|
||||
/* @var $item CommonDBTM */
|
||||
$item = new $itemtype();
|
||||
|
||||
if (!$item->dohistory) {
|
||||
continue; // Skip items that does not have an history
|
||||
}
|
||||
|
||||
$output->writeln(
|
||||
'<comment>' . sprintf(__('Filling `%s`.`%s`...'), $table, $column) . '</comment>',
|
||||
OutputInterface::VERBOSITY_VERBOSE
|
||||
);
|
||||
|
||||
$target_date = $column === 'date_creation' ? 'MIN(`date_mod`)' : 'MAX(`date_mod`)';
|
||||
|
||||
$result = $this->db->query(
|
||||
"
|
||||
UPDATE `$table`
|
||||
LEFT JOIN (
|
||||
SELECT $target_date AS `date_mod`, `itemtype`, `items_id`
|
||||
FROM `$log_table`
|
||||
GROUP BY `itemtype`, `items_id`
|
||||
) as `logs`
|
||||
ON `logs`.`itemtype` = '$itemtype' AND `logs`.`items_id` = `$table`.`id`
|
||||
SET `$table`.`$column` = `logs`.`date_mod` WHERE `$table`.`$column` IS NULL
|
||||
"
|
||||
);
|
||||
if (false === $result) {
|
||||
$message = sprintf(
|
||||
__('Update of `%s`.`%s` failed with message "(%s) %s".'),
|
||||
$table,
|
||||
$column,
|
||||
$this->db->errno(),
|
||||
$this->db->error()
|
||||
);
|
||||
$output->writeln(
|
||||
'<error>' . $message . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$output->writeln('<info>' . __('Migration done.') . '</info>');
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
}
|
||||
758
inc/console/migration/domainsplugintocorecommand.class.php
Normal file
758
inc/console/migration/domainsplugintocorecommand.class.php
Normal file
@ -0,0 +1,758 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Migration;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use CommonDBTM;
|
||||
use DB;
|
||||
use DomainType;
|
||||
use Domain;
|
||||
use Domain_Item;
|
||||
use Plugin;
|
||||
use Toolbox;
|
||||
use Glpi\Console\AbstractCommand;
|
||||
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Exception\LogicException;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||
|
||||
class DomainsPluginToCoreCommand extends AbstractCommand {
|
||||
|
||||
/**
|
||||
* Error code returned if plugin version or plugin data is invalid.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_PLUGIN_VERSION_OR_DATA_INVALID = 1;
|
||||
|
||||
/**
|
||||
* Error code returned if import failed.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_PLUGIN_IMPORT_FAILED = 1;
|
||||
|
||||
/**
|
||||
* Version of Domains plugin required for this migration.
|
||||
* @var string
|
||||
*/
|
||||
const DOMAINS_REQUIRED_VERSION = '2.1.0';
|
||||
|
||||
/**
|
||||
* Imported elements mapping.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $elements_mapping;
|
||||
|
||||
protected function configure() {
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:migration:domains_plugin_to_core');
|
||||
$this->setDescription(__('Migrate Domains plugin data into GLPI core tables'));
|
||||
|
||||
$this->addOption(
|
||||
'update-plugin',
|
||||
'u',
|
||||
InputOption::VALUE_NONE,
|
||||
sprintf(
|
||||
__('Run Domains plugin update (you need version %s files to do this)'),
|
||||
self::DOMAINS_REQUIRED_VERSION
|
||||
)
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'without-plugin',
|
||||
'w',
|
||||
InputOption::VALUE_NONE,
|
||||
sprintf(
|
||||
__('Enable migration without plugin files (we cannot validate that plugin data are compatible with supported %s version)'),
|
||||
self::DOMAINS_REQUIRED_VERSION
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$this->elements_mapping = []; // Clear elements mapping
|
||||
|
||||
$no_interaction = $input->getOption('no-interaction');
|
||||
|
||||
if (!$no_interaction) {
|
||||
// Ask for confirmation (unless --no-interaction)
|
||||
$output->writeln(
|
||||
[
|
||||
__('You are about to launch migration of Domains plugin data into GLPI core tables.'),
|
||||
__('It is better to make a backup of your existing data before continuing.')
|
||||
]
|
||||
);
|
||||
|
||||
/** @var QuestionHelper $question_helper */
|
||||
$question_helper = $this->getHelper('question');
|
||||
$run = $question_helper->ask(
|
||||
$input,
|
||||
$output,
|
||||
new ConfirmationQuestion(
|
||||
'<comment>' . __('Do you want to launch migration ?') . ' [yes/No]</comment>',
|
||||
false
|
||||
)
|
||||
);
|
||||
if (!$run) {
|
||||
$output->writeln(
|
||||
'<comment>' . __('Migration aborted.') . '</comment>',
|
||||
OutputInterface::VERBOSITY_VERBOSE
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->checkPlugin()) {
|
||||
return self::ERROR_PLUGIN_VERSION_OR_DATA_INVALID;
|
||||
}
|
||||
|
||||
if (!$this->migratePlugin()) {
|
||||
return self::ERROR_PLUGIN_IMPORT_FAILED;
|
||||
}
|
||||
|
||||
$output->writeln('<info>' . __('Migration done.') . '</info>');
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that plugin state and existing data are OK for migration.
|
||||
*
|
||||
* @throws LogicException
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function checkPlugin() {
|
||||
|
||||
$check_version = !$this->input->getOption('without-plugin');
|
||||
|
||||
if ($check_version) {
|
||||
$this->output->writeln(
|
||||
'<comment>' . __('Checking plugin version...') . '</comment>',
|
||||
OutputInterface::VERBOSITY_VERBOSE
|
||||
);
|
||||
|
||||
$plugin = new Plugin();
|
||||
$plugin->checkPluginState('domains');
|
||||
|
||||
if (!$plugin->getFromDBbyDir('domains')) {
|
||||
$message = __('Domains plugin is not part of GLPI plugin list. It has never been installed or has been cleaned.')
|
||||
. ' '
|
||||
. sprintf(
|
||||
__('You have to install Domains plugin files in version %s to be able to continue.'),
|
||||
self::DOMAINS_REQUIRED_VERSION
|
||||
);
|
||||
$this->output->writeln(
|
||||
[
|
||||
'<error>' . $message . '</error>',
|
||||
],
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$is_version_ok = self::DOMAINS_REQUIRED_VERSION === $plugin->fields['version'];
|
||||
if (!$is_version_ok) {
|
||||
$message = sprintf(
|
||||
__('You have to install Domains plugin files in version %s to be able to continue.'),
|
||||
self::DOMAINS_REQUIRED_VERSION
|
||||
);
|
||||
$this->output->writeln(
|
||||
'<error>' . $message . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$is_installable = in_array(
|
||||
$plugin->fields['state'],
|
||||
[
|
||||
Plugin::TOBECLEANED, // Can be in this state if check was done without the plugin dir
|
||||
Plugin::NOTINSTALLED, // Can be not installed if plugin has been cleaned in plugin list
|
||||
Plugin::NOTUPDATED, // Plugin 1.8.0 version has never been installed
|
||||
]
|
||||
);
|
||||
if ($is_installable) {
|
||||
if ($this->input->getOption('update-plugin')) {
|
||||
$message = sprintf(
|
||||
__('Migrating plugin to %s version...'),
|
||||
self::DOMAINS_REQUIRED_VERSION
|
||||
);
|
||||
$this->output->writeln(
|
||||
'<info>' . $message . '</info>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
|
||||
ob_start();
|
||||
$plugin->install($plugin->fields['id']);
|
||||
ob_end_clean();
|
||||
|
||||
// Reload and check migration result
|
||||
$plugin->getFromDB($plugin->fields['id']);
|
||||
if (!in_array($plugin->fields['state'], [Plugin::TOBECONFIGURED, Plugin::NOTACTIVATED])) {
|
||||
$message = sprintf(
|
||||
__('Plugin migration to %s version failed.'),
|
||||
self::DOMAINS_REQUIRED_VERSION
|
||||
);
|
||||
$this->output->writeln(
|
||||
'<error>' . $message . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$message = sprintf(
|
||||
__('Domains plugin data has to be updated to %s version. It can be done using the --update-plugin option.'),
|
||||
self::DOMAINS_REQUIRED_VERSION
|
||||
);
|
||||
$this->output->writeln(
|
||||
'<comment>' . $message . '</comment>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$is_state_ok = in_array(
|
||||
$plugin->fields['state'],
|
||||
[
|
||||
Plugin::ACTIVATED, // Should not be possible as 1.8.0 is not compatible with 9.3
|
||||
Plugin::TOBECONFIGURED, // Should not be possible as check_config of plugin returns always true
|
||||
Plugin::NOTACTIVATED,
|
||||
]
|
||||
);
|
||||
if (!$is_state_ok) {
|
||||
// Should not happens as installation should put plugin in awaited state
|
||||
throw new LogicException('Unexpected plugin state.');
|
||||
}
|
||||
}
|
||||
|
||||
$domains_tables = [
|
||||
'glpi_plugin_domains_configs',
|
||||
'glpi_plugin_domains_domains',
|
||||
'glpi_plugin_domains_domains_items',
|
||||
'glpi_plugin_domains_domaintypes',
|
||||
];
|
||||
$missing_tables = false;
|
||||
foreach ($domains_tables as $table) {
|
||||
if (!$this->db->tableExists($table)) {
|
||||
$this->output->writeln(
|
||||
'<error>' . sprintf(__('Domains plugin table "%s" is missing.'), $table) . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
$missing_tables = true;
|
||||
}
|
||||
}
|
||||
if ($missing_tables) {
|
||||
$this->output->writeln(
|
||||
'<error>' . __('Migration cannot be done.') . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private function migratePlugin() {
|
||||
|
||||
$failure = !$this->importDomainTypes()
|
||||
|| !$this->importDomains()
|
||||
|| !$this->importDomainItems();
|
||||
|
||||
return !$failure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate domain types
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function importDomainTypes() {
|
||||
$has_errors = false;
|
||||
|
||||
$this->output->writeln(
|
||||
'<comment>' . __('Importing domains types...') . '</comment>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
|
||||
$types_iterator = $this->db->request([
|
||||
'FROM' => 'glpi_plugin_domains_domaintypes',
|
||||
'ORDER' => 'id ASC'
|
||||
]);
|
||||
|
||||
$core_types = [];
|
||||
$coret_iterator = $this->db->request([
|
||||
'SELECT' => ['id', 'name'],
|
||||
'FROM' => DomainType::getTable()
|
||||
]);
|
||||
while ($row = $coret_iterator->next()) {
|
||||
$core_types[$row['name']] = $row['id'];
|
||||
}
|
||||
|
||||
if ($types_iterator->count()) {
|
||||
$progress_bar = new ProgressBar($this->output, $types_iterator->count());
|
||||
$progress_bar->start();
|
||||
|
||||
foreach ($types_iterator as $typ) {
|
||||
$progress_bar->advance(1);
|
||||
$core_type = null;
|
||||
|
||||
if (isset($core_types[$typ['name']])) {
|
||||
$core_type = $core_types[$typ['name']];
|
||||
$message = sprintf(
|
||||
__('Updating existing domain type %s...'),
|
||||
$typ['name']
|
||||
);
|
||||
} else {
|
||||
$message = sprintf(
|
||||
__('Importing domain type %s...'),
|
||||
$typ['name']
|
||||
);
|
||||
}
|
||||
$this->writelnOutputWithProgressBar(
|
||||
$message,
|
||||
$progress_bar,
|
||||
OutputInterface::VERBOSITY_VERY_VERBOSE
|
||||
);
|
||||
|
||||
$type_input = [
|
||||
'name' => $typ['name'],
|
||||
'entities_id' => $typ['entities_id'],
|
||||
'comment' => $typ['comment'],
|
||||
];
|
||||
$type_input = Toolbox::addslashes_deep($type_input);
|
||||
|
||||
$domaintype = new DomainType();
|
||||
if ($core_type !== null) {
|
||||
$res = (bool)$domaintype->update($type_input + ['id' => $core_type]);
|
||||
} else {
|
||||
$new_tid = (int)$domaintype->add($type_input);
|
||||
$res = $new_tid > 0;
|
||||
if ($res) {
|
||||
$core_types[$typ['name']] = $new_tid;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$res) {
|
||||
$has_errors = true;
|
||||
|
||||
$message = sprintf(
|
||||
$core_type === null ?
|
||||
__('Unable to add domain type %s.') :
|
||||
__('Unable to update domain type %s.'),
|
||||
$typ['name']
|
||||
);
|
||||
$this->outputImportError($message, $progress_bar);
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->addElementToMapping(
|
||||
'PluginDomainsDomaintype',
|
||||
$typ['id'],
|
||||
'DomainType',
|
||||
$new_tid ?? $core_type
|
||||
);
|
||||
}
|
||||
|
||||
$progress_bar->finish();
|
||||
$this->output->write(PHP_EOL);
|
||||
} else {
|
||||
$this->output->writeln(
|
||||
'<comment>' . __('No domains types found.') . '</comment>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
}
|
||||
|
||||
return !$has_errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate domains
|
||||
*
|
||||
* @throws LogicException
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function importDomains() {
|
||||
$has_errors = false;
|
||||
|
||||
$this->output->writeln(
|
||||
'<comment>' . __('Importing domains...') . '</comment>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
|
||||
$domains_iterator = $this->db->request([
|
||||
'FROM' => 'glpi_plugin_domains_domains',
|
||||
'ORDER' => 'id ASC'
|
||||
]);
|
||||
|
||||
$core_domains = [];
|
||||
$cored_iterator = $this->db->request([
|
||||
'SELECT' => ['id', 'name'],
|
||||
'FROM' => Domain::getTable()
|
||||
]);
|
||||
while ($row = $cored_iterator->next()) {
|
||||
$core_domains[$row['name']] = $row['id'];
|
||||
}
|
||||
|
||||
if ($domains_iterator->count()) {
|
||||
$progress_bar = new ProgressBar($this->output, $domains_iterator->count());
|
||||
$progress_bar->start();
|
||||
|
||||
foreach ($domains_iterator as $dom) {
|
||||
$progress_bar->advance(1);
|
||||
$core_dom = null;
|
||||
|
||||
if (isset($core_domains[$dom['name']])) {
|
||||
$core_dom = $core_domains[$dom['name']];
|
||||
$message = sprintf(
|
||||
__('Updating existing domain %s...'),
|
||||
$dom['name']
|
||||
);
|
||||
} else {
|
||||
$message = sprintf(
|
||||
__('Importing domain %s...'),
|
||||
$dom['name']
|
||||
);
|
||||
}
|
||||
$this->writelnOutputWithProgressBar(
|
||||
$message,
|
||||
$progress_bar,
|
||||
OutputInterface::VERBOSITY_VERY_VERBOSE
|
||||
);
|
||||
|
||||
$mapped_type = $this->getCorrespondingItem('PluginDomainsDomaintype', $dom['plugin_domains_domaintypes_id']);
|
||||
if ($dom['plugin_domains_domaintypes_id'] != 0 && $mapped_type === null) {
|
||||
$message = sprintf(
|
||||
__('Unable to find mapping for type %s.'),
|
||||
$dom['plugin_domains_domaintypes_id']
|
||||
);
|
||||
$this->outputImportError($message, $progress_bar);
|
||||
return false;
|
||||
}
|
||||
$types_id = $mapped_type !== null ? $mapped_type->fields['id'] : 0;
|
||||
$domain_input = [
|
||||
'name' => $dom['name'],
|
||||
'entities_id' => $dom['entities_id'],
|
||||
'is_recursive' => $dom['is_recursive'],
|
||||
'domaintypes_id' => $types_id,
|
||||
'date_creation' => $dom['date_creation'],
|
||||
'date_expiration' => $dom['date_expiration'],
|
||||
'users_id_tech' => $dom['users_id_tech'],
|
||||
'groups_id_tech' => $dom['groups_id_tech'],
|
||||
//suppliers_id not present in core
|
||||
'comment' => $dom['comment'],
|
||||
'others' => $dom['others'],
|
||||
'is_helpdesk_visible' => $dom['is_helpdesk_visible'],
|
||||
'date_mod' => $dom['date_mod'],
|
||||
'is_deleted' => $dom['is_deleted']
|
||||
];
|
||||
$domain_input = Toolbox::addslashes_deep($domain_input);
|
||||
|
||||
$domain = new Domain();
|
||||
if ($core_dom !== null) {
|
||||
$res = (bool)$domain->update($domain_input + ['id' => $core_dom]);
|
||||
} else {
|
||||
$new_did = (int)$domain->add($domain_input);
|
||||
$res = $new_did > 0;
|
||||
if ($res) {
|
||||
$core_domains[$dom['name']] = $new_did;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$res) {
|
||||
$has_errors = true;
|
||||
|
||||
$message = sprintf(
|
||||
$core_dom === null ?
|
||||
__('Unable to add domain %s.') :
|
||||
__('Unable to update domain %s.'),
|
||||
$dom['name']
|
||||
);
|
||||
$this->outputImportError($message, $progress_bar);
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->addElementToMapping(
|
||||
'PluginDomainsDomains',
|
||||
$dom['id'],
|
||||
'Domain',
|
||||
$new_did ?? $core_dom
|
||||
);
|
||||
|
||||
//handle infocoms
|
||||
$infocom = new \Infocom();
|
||||
$infocom_input = [
|
||||
'itemtype' => 'Domain',
|
||||
'items_id' => $new_did ?? $core_dom,
|
||||
'suppliers_id' => $dom['suppliers_id'],
|
||||
'entities_id' => $dom['entities_id'],
|
||||
'is_recursive' => $dom['is_recursive']
|
||||
];
|
||||
if ($core_dom === null) {
|
||||
$infocom->add($infocom_input);
|
||||
} else {
|
||||
$found = $infocom->getFromDBByCrit([
|
||||
'itemtype' => 'Domain',
|
||||
'items_id' => $core_dom
|
||||
]);
|
||||
if ($found) {
|
||||
$infocom_input['id'] = $infocom->fields['id'];
|
||||
$infocom->update($infocom_input);
|
||||
} else {
|
||||
$infocom->add($infocom_input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$progress_bar->finish();
|
||||
$this->output->write(PHP_EOL);
|
||||
} else {
|
||||
$this->output->writeln(
|
||||
'<comment>' . __('No domains found.') . '</comment>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
}
|
||||
|
||||
return !$has_errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate domain items
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function importDomainItems() {
|
||||
$has_errors = false;
|
||||
|
||||
$this->output->writeln(
|
||||
'<comment>' . __('Importing domains items...') . '</comment>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
|
||||
$items_iterator = $this->db->request([
|
||||
'FROM' => 'glpi_plugin_domains_domains_items',
|
||||
'ORDER' => 'id ASC'
|
||||
]);
|
||||
|
||||
$core_items = [];
|
||||
$coreitems_iterator = $this->db->request([
|
||||
'FROM' => Domain_Item::getTable()
|
||||
]);
|
||||
while ($row = $coreitems_iterator->next()) {
|
||||
$core_items[$row['domains_id'].$row['itemtype'].$row['items_id']] = $row['id'];
|
||||
}
|
||||
|
||||
if ($items_iterator->count()) {
|
||||
$progress_bar = new ProgressBar($this->output, $items_iterator->count());
|
||||
$progress_bar->start();
|
||||
|
||||
foreach ($items_iterator as $itm) {
|
||||
$progress_bar->advance(1);
|
||||
$core_item = null;
|
||||
$mapped_domain = $this->getCorrespondingItem('PluginDomainsDomains', $itm['plugin_domains_domains_id']);
|
||||
if ($mapped_domain === null) {
|
||||
$message = sprintf(
|
||||
__('Unable to find corresponding domain for item %s (%s).'),
|
||||
$itm['itemtype'],
|
||||
$itm['items_id']
|
||||
);
|
||||
$this->outputImportError($message, $progress_bar);
|
||||
// Do not block migration as this error is probably resulting in presence of obsolete data in DB
|
||||
continue;
|
||||
}
|
||||
$domains_id = $mapped_domain->fields['id'];
|
||||
|
||||
if (isset($core_items[$domains_id.$itm['itemtype'].$itm['items_id']])) {
|
||||
$core_item = $core_items[$domains_id.$itm['itemtype'].$itm['items_id']];
|
||||
$message = sprintf(
|
||||
__('Skip existing domain item %s...'),
|
||||
$domains_id . ' ' . $itm['itemtype'] . ' ' . $itm['items_id']
|
||||
);
|
||||
} else {
|
||||
$message = sprintf(
|
||||
__('Importing domain item %s...'),
|
||||
$domains_id . ' ' . $itm['itemtype'] . ' ' . $itm['items_id']
|
||||
);
|
||||
}
|
||||
$this->writelnOutputWithProgressBar(
|
||||
$message,
|
||||
$progress_bar,
|
||||
OutputInterface::VERBOSITY_VERY_VERBOSE
|
||||
);
|
||||
|
||||
if ($core_item !== null) {
|
||||
//if it already exist in DB, there is nothing to change
|
||||
continue;
|
||||
}
|
||||
|
||||
$item_input = [
|
||||
'domains_id' => $domains_id,
|
||||
'itemtype' => $itm['itemtype'],
|
||||
'items_id' => $itm['items_id'],
|
||||
'domainrelations_id' => 0
|
||||
];
|
||||
$item_input = Toolbox::addslashes_deep($item_input);
|
||||
|
||||
$item = new Domain_Item();
|
||||
$new_iid = (int)$item->add($item_input);
|
||||
$res = $new_iid > 0;
|
||||
if ($res) {
|
||||
$core_items[$domains_id.$itm['itemtype'].$itm['items_id']] = $new_iid;
|
||||
}
|
||||
|
||||
if (!$res) {
|
||||
$has_errors = true;
|
||||
|
||||
$message = sprintf(
|
||||
$core_item === null ?
|
||||
__('Unable to add domain item %s.') :
|
||||
__('Unable to update domain item %s.'),
|
||||
$domains_id . ' ' . $itm['itemtype'] . ' ' . $itm['items_id']
|
||||
);
|
||||
$this->outputImportError($message, $progress_bar);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$progress_bar->finish();
|
||||
$this->output->write(PHP_EOL);
|
||||
} else {
|
||||
$this->output->writeln(
|
||||
'<comment>' . __('No domains items found.') . '</comment>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
}
|
||||
|
||||
return !$has_errors;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add an element to mapping.
|
||||
*
|
||||
* @param string $old_itemtype
|
||||
* @param integer $old_id
|
||||
* @param string $new_itemtype
|
||||
* @param integer $new_id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function addElementToMapping($old_itemtype, $old_id, $new_itemtype, $new_id) {
|
||||
|
||||
if (!array_key_exists($old_itemtype, $this->elements_mapping)) {
|
||||
$this->elements_mapping[$old_itemtype] = [];
|
||||
}
|
||||
$this->elements_mapping[$old_itemtype][$old_id] = [
|
||||
'itemtype' => $new_itemtype,
|
||||
'id' => $new_id,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns item corresponding to itemtype and id.
|
||||
* If item has been migrated to another itemtype, il will return the new item.
|
||||
*
|
||||
* @param string $itemtype
|
||||
* @param integer $id
|
||||
*
|
||||
* @return null|CommonDBTM
|
||||
*/
|
||||
private function getCorrespondingItem($itemtype, $id) {
|
||||
|
||||
if (array_key_exists($itemtype, $this->elements_mapping)
|
||||
&& array_key_exists($id, $this->elements_mapping[$itemtype])) {
|
||||
// Element exists in mapping, get new element
|
||||
$mapping = $this->elements_mapping[$itemtype][$id];
|
||||
$id = $mapping['id'];
|
||||
$itemtype = $mapping['itemtype'];
|
||||
}
|
||||
|
||||
if (!class_exists($itemtype)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$item = new $itemtype();
|
||||
if ($id !== 0 && !$item->getFromDB($id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output import error message.
|
||||
*
|
||||
* @param string $message
|
||||
* @param ProgressBar|null $progress_bar
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function outputImportError($message, ProgressBar $progress_bar = null) {
|
||||
|
||||
$verbosity = OutputInterface::VERBOSITY_QUIET;
|
||||
|
||||
$message = '<error>' . $message . '</error>';
|
||||
|
||||
if ($progress_bar instanceof ProgressBar) {
|
||||
$this->writelnOutputWithProgressBar(
|
||||
$message,
|
||||
$progress_bar,
|
||||
$verbosity
|
||||
);
|
||||
} else {
|
||||
if ($progress_bar instanceof ProgressBar) {
|
||||
$this->output->write(PHP_EOL); // Keep progress bar last state and go to next line
|
||||
}
|
||||
$this->output->writeln(
|
||||
$message,
|
||||
$verbosity
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
126
inc/console/migration/myisamtoinnodbcommand.class.php
Normal file
126
inc/console/migration/myisamtoinnodbcommand.class.php
Normal file
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Migration;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use DB;
|
||||
use Glpi\Console\AbstractCommand;
|
||||
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
|
||||
class MyIsamToInnoDbCommand extends AbstractCommand {
|
||||
|
||||
/**
|
||||
* Error code returned when failed to migrate one table.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_TABLE_MIGRATION_FAILED = 1;
|
||||
|
||||
protected function configure() {
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:migration:myisam_to_innodb');
|
||||
$this->setDescription(__('Migrate MyISAM tables to InnoDB'));
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$no_interaction = $input->getOption('no-interaction'); // Base symfony/console option
|
||||
|
||||
$myisam_tables = $this->db->getMyIsamTables();
|
||||
|
||||
$output->writeln(
|
||||
sprintf(
|
||||
'<info>' . __('Found %s table(s) using MyISAM engine.') . '</info>',
|
||||
$myisam_tables->count()
|
||||
)
|
||||
);
|
||||
|
||||
if (0 === $myisam_tables->count()) {
|
||||
$output->writeln('<info>' . __('No migration needed.') . '</info>');
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!$no_interaction) {
|
||||
// Ask for confirmation (unless --no-interaction)
|
||||
/** @var QuestionHelper $question_helper */
|
||||
$question_helper = $this->getHelper('question');
|
||||
$run = $question_helper->ask(
|
||||
$input,
|
||||
$output,
|
||||
new ConfirmationQuestion(__('Do you want to continue ?') . ' [Yes/no]', true)
|
||||
);
|
||||
if (!$run) {
|
||||
$output->writeln(
|
||||
'<comment>' . __('Migration aborted.') . '</comment>',
|
||||
OutputInterface::VERBOSITY_VERBOSE
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
while ($table = $myisam_tables->next()) {
|
||||
$table_name = DB::quoteName($table['TABLE_NAME']);
|
||||
$output->writeln(
|
||||
'<comment>' . sprintf(__('Migrating table "%s"...'), $table_name) . '</comment>',
|
||||
OutputInterface::VERBOSITY_VERBOSE
|
||||
);
|
||||
$result = $this->db->query(sprintf('ALTER TABLE %s ENGINE = InnoDB', $table_name));
|
||||
|
||||
if (false === $result) {
|
||||
$message = sprintf(
|
||||
__('Migration of table "%s" failed with message "(%s) %s".'),
|
||||
$table_name,
|
||||
$this->db->errno(),
|
||||
$this->db->error()
|
||||
);
|
||||
$output->writeln(
|
||||
'<error>' . $message . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
return self::ERROR_TABLE_MIGRATION_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
$output->writeln('<info>' . __('Migration done.') . '</info>');
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
}
|
||||
1500
inc/console/migration/racksplugintocorecommand.class.php
Normal file
1500
inc/console/migration/racksplugintocorecommand.class.php
Normal file
File diff suppressed because it is too large
Load Diff
216
inc/console/migration/timestampscommand.class.php
Normal file
216
inc/console/migration/timestampscommand.class.php
Normal file
@ -0,0 +1,216 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Migration;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use Glpi\Console\AbstractCommand;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
|
||||
class TimestampsCommand extends AbstractCommand {
|
||||
|
||||
protected function configure() {
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:migration:timestamps');
|
||||
$this->setDescription(__('Convert "datetime" fields to "timestamp" to use timezones.'));
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
//convert db
|
||||
|
||||
// we are going to update datetime, date and time (?) types to timestamp type
|
||||
$tbl_iterator = $this->db->request([
|
||||
'SELECT' => ['information_schema.columns.table_name as TABLE_NAME'],
|
||||
'DISTINCT' => true,
|
||||
'FROM' => 'information_schema.columns',
|
||||
'INNER JOIN' => [
|
||||
'information_schema.tables' => [
|
||||
'ON' => [
|
||||
'information_schema.tables.table_name',
|
||||
'information_schema.columns.table_name', [
|
||||
'AND' => ['information_schema.tables.table_type' => 'BASE TABLE']
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
'WHERE' => [
|
||||
'information_schema.columns.table_schema' => $this->db->dbdefault,
|
||||
'information_schema.columns.data_type' => 'datetime'
|
||||
],
|
||||
'ORDER' => [
|
||||
'information_schema.columns.table_name'
|
||||
]
|
||||
]);
|
||||
|
||||
$output->writeln(
|
||||
sprintf(
|
||||
'<info>' . __('Found %s table(s) requiring migration.') . '</info>',
|
||||
$tbl_iterator->count()
|
||||
)
|
||||
);
|
||||
|
||||
if ($tbl_iterator->count() === 0) {
|
||||
$output->writeln('<info>' . __('No migration needed.') . '</info>');
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
if (!$input->getOption('no-interaction')) {
|
||||
// Ask for confirmation (unless --no-interaction)
|
||||
/** @var \Symfony\Component\Console\Helper\QuestionHelper $question_helper */
|
||||
$question_helper = $this->getHelper('question');
|
||||
$run = $question_helper->ask(
|
||||
$input,
|
||||
$output,
|
||||
new ConfirmationQuestion(__('Do you want to continue ?') . ' [Yes/no]', true)
|
||||
);
|
||||
if (!$run) {
|
||||
$output->writeln(
|
||||
'<comment>' . __('Migration aborted.') . '</comment>',
|
||||
OutputInterface::VERBOSITY_VERBOSE
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
$progress_bar = new ProgressBar($output, $tbl_iterator->count());
|
||||
$progress_bar->start();
|
||||
|
||||
while ($table = $tbl_iterator->next()) {
|
||||
$progress_bar->advance(1);
|
||||
|
||||
$tablealter = ''; // init by default
|
||||
|
||||
// get accurate info from information_schema to perform correct alter
|
||||
$col_iterator = $this->db->request([
|
||||
'SELECT' => [
|
||||
'table_name AS TABLE_NAME',
|
||||
'column_name AS COLUMN_NAME',
|
||||
'column_default AS COLUMN_DEFAULT',
|
||||
'column_comment AS COLUMN_COMMENT',
|
||||
'is_nullable AS IS_NULLABLE',
|
||||
],
|
||||
'FROM' => 'information_schema.columns',
|
||||
'WHERE' => [
|
||||
'table_schema' => $this->db->dbdefault,
|
||||
'table_name' => $table['TABLE_NAME'],
|
||||
'data_type' => 'datetime'
|
||||
]
|
||||
]);
|
||||
|
||||
while ($column = $col_iterator->next()) {
|
||||
$nullable = false;
|
||||
$default = null;
|
||||
//check if nullable
|
||||
if ('YES' === $column['IS_NULLABLE']) {
|
||||
$nullable = true;
|
||||
}
|
||||
|
||||
//guess default value
|
||||
if (is_null($column['COLUMN_DEFAULT']) && !$nullable) { // no default
|
||||
$default = null;
|
||||
} else if ((is_null($column['COLUMN_DEFAULT']) || strtoupper($column['COLUMN_DEFAULT']) == 'NULL') && $nullable) {
|
||||
$default = "NULL";
|
||||
} else if (!is_null($column['COLUMN_DEFAULT']) && strtoupper($column['COLUMN_DEFAULT']) != 'NULL') {
|
||||
if ($column['COLUMN_DEFAULT'] < '1970-01-01 00:00:01') {
|
||||
// Prevent default value to be out of range (lower to min possible value)
|
||||
$defaultDate = new \DateTime('1970-01-01 00:00:01', new \DateTimeZone('UTC'));
|
||||
$defaultDate->setTimezone(new \DateTimeZone(date_default_timezone_get()));
|
||||
$default = $defaultDate->format("Y-m-d H:i:s");
|
||||
} else if ($column['COLUMN_DEFAULT'] > '2038-01-19 03:14:07') {
|
||||
// Prevent default value to be out of range (greater to max possible value)
|
||||
$defaultDate = new \DateTime('2038-01-19 03:14:07', new \DateTimeZone('UTC'));
|
||||
$defaultDate->setTimezone(new \DateTimeZone(date_default_timezone_get()));
|
||||
$default = $defaultDate->format("Y-m-d H:i:s");
|
||||
} else {
|
||||
$default = $column['COLUMN_DEFAULT'];
|
||||
}
|
||||
}
|
||||
|
||||
//build alter
|
||||
$tablealter .= "\n\t MODIFY COLUMN ".$this->db->quoteName($column['COLUMN_NAME'])." TIMESTAMP";
|
||||
if ($nullable) {
|
||||
$tablealter .= " NULL";
|
||||
} else {
|
||||
$tablealter .= " NOT NULL";
|
||||
}
|
||||
if ($default !== null) {
|
||||
if ($default !== 'NULL') {
|
||||
$default = "'" . $this->db->escape($default) . "'";
|
||||
}
|
||||
$tablealter .= " DEFAULT $default";
|
||||
}
|
||||
if ($column['COLUMN_COMMENT'] != '') {
|
||||
$tablealter .= " COMMENT '".$this->db->escape($column['COLUMN_COMMENT'])."'";
|
||||
}
|
||||
$tablealter .= ",";
|
||||
}
|
||||
$tablealter = rtrim($tablealter, ",");
|
||||
|
||||
// apply alter to table
|
||||
$query = "ALTER TABLE " . $this->db->quoteName($table['TABLE_NAME']) . " " . $tablealter.";\n";
|
||||
$this->writelnOutputWithProgressBar(
|
||||
'<comment>' . sprintf(__('Running %s'), $query) . '</comment>',
|
||||
$progress_bar,
|
||||
OutputInterface::VERBOSITY_VERBOSE
|
||||
);
|
||||
|
||||
$result = $this->db->query($query);
|
||||
if (false === $result) {
|
||||
$message = sprintf(
|
||||
__('Update of `%s` failed with message "(%s) %s".'),
|
||||
$table['TABLE_NAME'],
|
||||
$this->db->errno(),
|
||||
$this->db->error()
|
||||
);
|
||||
$this->writelnOutputWithProgressBar(
|
||||
'<error>' . $message . '</error>',
|
||||
$progress_bar,
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$progress_bar->finish();
|
||||
$this->output->write(PHP_EOL);
|
||||
|
||||
$output->writeln('<info>' . __('Migration done.') . '</info>');
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
}
|
||||
151
inc/console/plugin/abstractplugincommand.class.php
Normal file
151
inc/console/plugin/abstractplugincommand.class.php
Normal file
@ -0,0 +1,151 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Plugin;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use Glpi\Console\AbstractCommand;
|
||||
use Glpi\Console\Command\ForceNoPluginsOptionCommandInterface;
|
||||
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
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\ChoiceQuestion;
|
||||
|
||||
abstract class AbstractPluginCommand extends AbstractCommand implements ForceNoPluginsOptionCommandInterface {
|
||||
|
||||
/**
|
||||
* Wildcard value to target all directories.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DIRECTORY_ALL = '*';
|
||||
|
||||
protected function configure() {
|
||||
parent::configure();
|
||||
|
||||
$this->addOption(
|
||||
'all',
|
||||
'a',
|
||||
InputOption::VALUE_NONE,
|
||||
__('Run command on all plugins')
|
||||
);
|
||||
|
||||
$this->addArgument(
|
||||
'directory',
|
||||
InputArgument::IS_ARRAY | InputArgument::OPTIONAL,
|
||||
__('Plugin directory')
|
||||
);
|
||||
}
|
||||
|
||||
protected function interact(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$all = $input->getOption('all');
|
||||
$directories = $input->getArgument('directory');
|
||||
|
||||
if ($all && !empty($directories)) {
|
||||
throw new InvalidArgumentException(
|
||||
__('Option --all is not compatible with usage of directory argument.')
|
||||
);
|
||||
}
|
||||
|
||||
if ($all) {
|
||||
// Set wildcard value in directory argument
|
||||
$input->setArgument('directory', [self::DIRECTORY_ALL]);
|
||||
} else if (empty($directories)) {
|
||||
// Ask for plugin list if directory argument is empty
|
||||
$choices = $this->getDirectoryChoiceChoices();
|
||||
$choices = array_merge(
|
||||
[self::DIRECTORY_ALL => __('All plugins')],
|
||||
$choices
|
||||
);
|
||||
|
||||
if (!empty($choices)) {
|
||||
/** @var \Symfony\Component\Console\Helper\QuestionHelper $question_helper */
|
||||
$question_helper = $this->getHelper('question');
|
||||
$question = new ChoiceQuestion(
|
||||
$this->getDirectoryChoiceQuestion(),
|
||||
$choices
|
||||
);
|
||||
$question->setAutocompleterValues(array_keys($choices));
|
||||
$question->setMultiselect(true);
|
||||
$answer = $question_helper->ask(
|
||||
$input,
|
||||
$output,
|
||||
$question
|
||||
);
|
||||
$input->setArgument('directory', $answer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getNoPluginsOptionValue() {
|
||||
|
||||
// Force no loading on plugins in plugin install process
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize input to symplify handling of specific arguments/options values.
|
||||
*
|
||||
* @param InputInterface $input
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function normalizeInput(InputInterface $input) {
|
||||
|
||||
if ($input->getArgument('directory') === [self::DIRECTORY_ALL]) {
|
||||
$input->setArgument('directory', array_keys($this->getDirectoryChoiceChoices()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns question to ask if no directory argument has been passed.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function getDirectoryChoiceQuestion();
|
||||
|
||||
|
||||
/**
|
||||
* Returns possible directory choices to suggest if no directory argument has been passed.
|
||||
* Returns an array usable in a ChoiceQuestion object.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
abstract protected function getDirectoryChoiceChoices();
|
||||
}
|
||||
172
inc/console/plugin/activatecommand.class.php
Normal file
172
inc/console/plugin/activatecommand.class.php
Normal file
@ -0,0 +1,172 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Plugin;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use Plugin;
|
||||
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class ActivateCommand extends AbstractPluginCommand {
|
||||
|
||||
protected function configure() {
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:plugin:activate');
|
||||
$this->setAliases(['plugin:activate']);
|
||||
$this->setDescription('Activate plugin(s)');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$this->normalizeInput($input);
|
||||
|
||||
$directories = $input->getArgument('directory');
|
||||
|
||||
foreach ($directories as $directory) {
|
||||
$output->writeln(
|
||||
'<info>' . sprintf(__('Processing plugin "%s"...'), $directory) . '</info>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
|
||||
if (!$this->canRunActivateMethod($directory)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$plugin = new Plugin();
|
||||
$plugin->checkPluginState($directory); // Be sure that plugin informations are up to date in DB
|
||||
if (!$plugin->getFromDBByCrit(['directory' => $directory])) {
|
||||
$this->output->writeln(
|
||||
'<error>' . sprintf(__('Unable to load plugin "%s" informations.'), $directory) . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$plugin->activate($plugin->fields['id'])) {
|
||||
$this->output->writeln(
|
||||
'<error>' . sprintf(__('Plugin "%s" activation failed.'), $directory) . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
$this->outputSessionBufferedMessages([WARNING, ERROR]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$output->writeln(
|
||||
'<info>' . sprintf(__('Plugin "%1$s" has been activated.'), $directory) . '</info>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
}
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if activate method can be run for given plugin.
|
||||
*
|
||||
* @param string $directory
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function canRunActivateMethod($directory) {
|
||||
|
||||
$plugin = new Plugin();
|
||||
|
||||
// Check that directory is valid
|
||||
$informations = $plugin->getInformationsFromDirectory($directory);
|
||||
if (empty($informations)) {
|
||||
$this->output->writeln(
|
||||
'<error>' . sprintf(__('Invalid plugin directory "%s".'), $directory) . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check current plugin state
|
||||
$is_already_known = $plugin->getFromDBByCrit(['directory' => $directory]);
|
||||
if (!$is_already_known) {
|
||||
$this->output->writeln(
|
||||
'<error>' . sprintf(__('Plugin "%s" is not yet installed.'), $directory) . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($plugin->fields['state'] == Plugin::ACTIVATED) {
|
||||
$this->output->writeln(
|
||||
'<info>' . sprintf(__('Plugin "%s" is already active.'), $directory) . '</info>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Plugin::NOTACTIVATED != $plugin->fields['state']) {
|
||||
$this->output->writeln(
|
||||
'<error>' . sprintf(__('Plugin "%s" have to be installed and configured prior to activation.'), $directory) . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getDirectoryChoiceQuestion() {
|
||||
|
||||
return __('Which plugin(s) do you want to activate (comma separated values) ?');
|
||||
}
|
||||
|
||||
protected function getDirectoryChoiceChoices() {
|
||||
|
||||
$choices = [];
|
||||
$plugin_iterator = $this->db->request(
|
||||
[
|
||||
'FROM' => Plugin::getTable(),
|
||||
'WHERE' => [
|
||||
'state' => Plugin::NOTACTIVATED
|
||||
]
|
||||
]
|
||||
);
|
||||
foreach ($plugin_iterator as $plugin) {
|
||||
$choices[$plugin['directory']] = $plugin['name'];
|
||||
}
|
||||
|
||||
ksort($choices, SORT_STRING);
|
||||
|
||||
return $choices;
|
||||
}
|
||||
}
|
||||
164
inc/console/plugin/deactivatecommand.class.php
Normal file
164
inc/console/plugin/deactivatecommand.class.php
Normal file
@ -0,0 +1,164 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Plugin;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use Plugin;
|
||||
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class DeactivateCommand extends AbstractPluginCommand {
|
||||
|
||||
protected function configure() {
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:plugin:deactivate');
|
||||
$this->setAliases(['plugin:deactivate']);
|
||||
$this->setDescription('Deactivate plugin(s)');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$this->normalizeInput($input);
|
||||
|
||||
$directories = $input->getArgument('directory');
|
||||
|
||||
foreach ($directories as $directory) {
|
||||
$output->writeln(
|
||||
'<info>' . sprintf(__('Processing plugin "%s"...'), $directory) . '</info>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
|
||||
if (!$this->canRunDeactivateMethod($directory)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$plugin = new Plugin();
|
||||
$plugin->checkPluginState($directory); // Be sure that plugin informations are up to date in DB
|
||||
if (!$plugin->getFromDBByCrit(['directory' => $directory])) {
|
||||
$this->output->writeln(
|
||||
'<error>' . sprintf(__('Unable to load plugin "%s" informations.'), $directory) . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$plugin->unactivate($plugin->fields['id'])) {
|
||||
$this->output->writeln(
|
||||
'<error>' . sprintf(__('Plugin "%s" deactivation failed.'), $directory) . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
$this->outputSessionBufferedMessages([WARNING, ERROR]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$output->writeln(
|
||||
'<info>' . sprintf(__('Plugin "%1$s" has been deactivated.'), $directory) . '</info>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
}
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if deactivate method can be run for given plugin.
|
||||
*
|
||||
* @param string $directory
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function canRunDeactivateMethod($directory) {
|
||||
|
||||
$plugin = new Plugin();
|
||||
|
||||
// Check that directory is valid
|
||||
$informations = $plugin->getInformationsFromDirectory($directory);
|
||||
if (empty($informations)) {
|
||||
$this->output->writeln(
|
||||
'<error>' . sprintf(__('Invalid plugin directory "%s".'), $directory) . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check current plugin state
|
||||
$is_already_known = $plugin->getFromDBByCrit(['directory' => $directory]);
|
||||
if (!$is_already_known) {
|
||||
$this->output->writeln(
|
||||
'<error>' . sprintf(__('Plugin "%s" is not yet installed.'), $directory) . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Plugin::ACTIVATED != $plugin->fields['state']) {
|
||||
$this->output->writeln(
|
||||
'<info>' . sprintf(__('Plugin "%s" is already inactive.'), $directory) . '</info>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getDirectoryChoiceQuestion() {
|
||||
|
||||
return __('Which plugin(s) do you want to deactivate (comma separated values) ?');
|
||||
}
|
||||
|
||||
protected function getDirectoryChoiceChoices() {
|
||||
|
||||
$choices = [];
|
||||
$plugin_iterator = $this->db->request(
|
||||
[
|
||||
'FROM' => Plugin::getTable(),
|
||||
'WHERE' => [
|
||||
'state' => Plugin::ACTIVATED
|
||||
]
|
||||
]
|
||||
);
|
||||
foreach ($plugin_iterator as $plugin) {
|
||||
$choices[$plugin['directory']] = $plugin['name'];
|
||||
}
|
||||
|
||||
ksort($choices, SORT_STRING);
|
||||
|
||||
return $choices;
|
||||
}
|
||||
}
|
||||
355
inc/console/plugin/installcommand.class.php
Normal file
355
inc/console/plugin/installcommand.class.php
Normal file
@ -0,0 +1,355 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Plugin;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use Auth;
|
||||
use Plugin;
|
||||
use Session;
|
||||
use User;
|
||||
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
|
||||
class InstallCommand extends AbstractPluginCommand {
|
||||
|
||||
protected function configure() {
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:plugin:install');
|
||||
$this->setAliases(['plugin:install']);
|
||||
$this->setDescription('Run plugin(s) installation script');
|
||||
|
||||
$this->addOption(
|
||||
'param',
|
||||
'p',
|
||||
InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
|
||||
__('Additionnal parameters to pass to the plugin install hook function')
|
||||
. PHP_EOL
|
||||
. __('"-p foo" will set "foo" param value to true')
|
||||
. PHP_EOL
|
||||
. __('"-p foo=bar" will set "foo" param value to "bar"')
|
||||
. PHP_EOL
|
||||
);
|
||||
$this->addUsage('-p foo=bar -p force myplugin');
|
||||
|
||||
$this->addOption(
|
||||
'username',
|
||||
'u',
|
||||
InputOption::VALUE_REQUIRED,
|
||||
__('Name of user used during installation script (among other things to set plugin admin rights)')
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'force',
|
||||
'f',
|
||||
InputOption::VALUE_NONE,
|
||||
__('Force execution of installation, even if plugin is already installed')
|
||||
);
|
||||
}
|
||||
|
||||
protected function interact(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
parent::interact($input, $output);
|
||||
|
||||
if (null === $input->getOption('username')) {
|
||||
/** @var \Symfony\Component\Console\Helper\QuestionHelper $question_helper */
|
||||
$question_helper = $this->getHelper('question');
|
||||
$value = $question_helper->ask(
|
||||
$input,
|
||||
$output,
|
||||
new Question('User to use:')
|
||||
);
|
||||
$input->setOption('username', $value);
|
||||
}
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$this->normalizeInput($input);
|
||||
|
||||
$this->loadUserSession($input->getOption('username'));
|
||||
|
||||
$directories = $input->getArgument('directory');
|
||||
$force = $input->getOption('force');
|
||||
|
||||
$params = $this->getAdditionnalParameters($input);
|
||||
|
||||
foreach ($directories as $directory) {
|
||||
$output->writeln(
|
||||
'<info>' . sprintf(__('Processing plugin "%s"...'), $directory) . '</info>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
|
||||
if (!$this->canRunInstallMethod($directory, $force)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$plugin = new Plugin();
|
||||
$plugin->checkPluginState($directory); // Be sure that plugin informations are up to date in DB
|
||||
if (!$plugin->getFromDBByCrit(['directory' => $directory])) {
|
||||
$this->output->writeln(
|
||||
'<error>' . sprintf(__('Unable to load plugin "%s" informations.'), $directory) . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
continue;
|
||||
}
|
||||
$plugin->install($plugin->fields['id'], $params);
|
||||
|
||||
// Check state after installation
|
||||
if (!in_array($plugin->fields['state'], [Plugin::NOTACTIVATED, Plugin::TOBECONFIGURED])) {
|
||||
$this->output->writeln(
|
||||
'<error>' . sprintf(__('Plugin "%s" installation failed.'), $directory) . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
$this->outputSessionBufferedMessages([WARNING, ERROR]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$message = Plugin::TOBECONFIGURED == $plugin->fields['state']
|
||||
? __('Plugin "%1$s" has been installed and must be configured.')
|
||||
: __('Plugin "%1$s" has been installed and can be activated.');
|
||||
|
||||
$output->writeln(
|
||||
'<info>' . sprintf($message, $directory) . '</info>',
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
}
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
protected function getDirectoryChoiceQuestion() {
|
||||
|
||||
return __('Which plugin(s) do you want to install (comma separated values) ?');
|
||||
}
|
||||
|
||||
protected function getDirectoryChoiceChoices() {
|
||||
|
||||
$only_not_installed = !$this->input->getOption('force');
|
||||
|
||||
// Fetch directory list
|
||||
$directories = [];
|
||||
foreach (PLUGINS_DIRECTORIES as $plugins_directory) {
|
||||
$directory_handle = opendir($plugins_directory);
|
||||
while (false !== ($filename = readdir($directory_handle))) {
|
||||
if (!in_array($filename, ['.svn', '.', '..'])
|
||||
&& is_dir($plugins_directory . DIRECTORY_SEPARATOR . $filename)) {
|
||||
$directories[] = $filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch plugins informations
|
||||
$choices = [];
|
||||
foreach ($directories as $directory) {
|
||||
$plugin = new Plugin();
|
||||
$informations = $plugin->getInformationsFromDirectory($directory);
|
||||
|
||||
if (empty($informations)) {
|
||||
continue; // Ignore directory if not able to load plugin informations.
|
||||
}
|
||||
|
||||
if ($only_not_installed
|
||||
&& ($this->isAlreadyInstalled($directory)
|
||||
|| (array_key_exists('oldname', $informations)
|
||||
&& $this->isAlreadyInstalled($informations['oldname'])))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$choices[$directory] = array_key_exists('name', $informations)
|
||||
? $informations['name']
|
||||
: $directory;
|
||||
}
|
||||
|
||||
ksort($choices, SORT_STRING);
|
||||
|
||||
return $choices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load user in session.
|
||||
*
|
||||
* @param string $username
|
||||
* @return void
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function loadUserSession($username) {
|
||||
|
||||
$user = new User();
|
||||
if ($user->getFromDBbyName($username)) {
|
||||
// Store computed output parameters
|
||||
$lang = $_SESSION['glpilanguage'];
|
||||
$session_use_mode = $_SESSION['glpi_use_mode'];
|
||||
|
||||
$auth = new Auth();
|
||||
$auth->auth_succeded = true;
|
||||
$auth->user = $user;
|
||||
Session::init($auth);
|
||||
|
||||
// Force usage of computed output parameters
|
||||
$_SESSION['glpilanguage'] = $lang;
|
||||
$_SESSION['glpi_use_mode'] = $session_use_mode;
|
||||
Session::loadLanguage();
|
||||
} else {
|
||||
throw new InvalidArgumentException(
|
||||
__('User name defined by --username option is invalid.')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if plugin is already installed.
|
||||
*
|
||||
* @param string $directory
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function isAlreadyInstalled($directory) {
|
||||
|
||||
$plugin = new Plugin();
|
||||
$is_already_known = $plugin->getFromDBByCrit(['directory' => $directory]);
|
||||
|
||||
$installed_states = [
|
||||
Plugin::ACTIVATED,
|
||||
Plugin::TOBECONFIGURED,
|
||||
Plugin::NOTACTIVATED,
|
||||
];
|
||||
return $is_already_known && in_array($plugin->fields['state'], $installed_states);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if install method can be run for given plugin.
|
||||
*
|
||||
* @param string $directory
|
||||
* @param boolean $allow_reinstall
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function canRunInstallMethod($directory, $allow_reinstall) {
|
||||
|
||||
$plugin = new Plugin();
|
||||
|
||||
// Check that directory is valid
|
||||
$informations = $plugin->getInformationsFromDirectory($directory);
|
||||
if (empty($informations)) {
|
||||
$this->output->writeln(
|
||||
'<error>' . sprintf(__('Invalid plugin directory "%s".'), $directory) . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if plugin is not already installed
|
||||
if (!$allow_reinstall
|
||||
&& ($this->isAlreadyInstalled($directory)
|
||||
|| (array_key_exists('oldname', $informations)
|
||||
&& $this->isAlreadyInstalled($informations['oldname'])))) {
|
||||
$message = sprintf(
|
||||
__('Plugin "%s" is already installed. Use --force option to force reinstallation.'),
|
||||
$directory
|
||||
);
|
||||
$this->output->writeln(
|
||||
'<error>' . $message . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
Plugin::load($directory, true);
|
||||
|
||||
// Check that required functions exists
|
||||
$function = 'plugin_' . $directory . '_install';
|
||||
if (!function_exists($function)) {
|
||||
$message = sprintf(
|
||||
__('Plugin "%s" function "%s" is missing.'),
|
||||
$directory,
|
||||
$function
|
||||
);
|
||||
$this->output->writeln(
|
||||
'<error>' . $message . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check prerequisites
|
||||
ob_start();
|
||||
$requirements_met = $plugin->checkVersions($directory);
|
||||
$check_function = 'plugin_' . $directory . '_check_prerequisites';
|
||||
if ($requirements_met && function_exists($check_function)) {
|
||||
$requirements_met = $check_function();
|
||||
}
|
||||
$ob_contents = ob_get_contents();
|
||||
ob_end_clean();
|
||||
if (!$requirements_met) {
|
||||
$this->output->writeln(
|
||||
[
|
||||
'<error>' . sprintf(__('Plugin "%s" requirements not met.'), $directory) . '</error>',
|
||||
'<error>' . $ob_contents . '</error>',
|
||||
],
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract additionnal parameters from input.
|
||||
*
|
||||
* @param InputInterface $input
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getAdditionnalParameters(InputInterface $input) {
|
||||
|
||||
$input_params = $input->getOption('param');
|
||||
|
||||
$params = [];
|
||||
foreach ($input_params as $input_param) {
|
||||
$parts = explode('=', $input_param);
|
||||
$params[$parts[0]] = isset($parts[1]) ? $parts[1] : true;
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
133
inc/console/rules/processsoftwarecategoryrulescommand.class.php
Normal file
133
inc/console/rules/processsoftwarecategoryrulescommand.class.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Rules;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use Glpi\Console\AbstractCommand;
|
||||
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class ProcessSoftwareCategoryRulesCommand extends AbstractCommand {
|
||||
|
||||
protected function configure() {
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:rules:process_software_category_rules');
|
||||
$this->setAliases(['rules:process_software_category_rules']);
|
||||
$this->setDescription(__('Process software category rules'));
|
||||
|
||||
$this->addOption(
|
||||
'all',
|
||||
'a',
|
||||
InputOption::VALUE_NONE,
|
||||
__('Process rule for all softwares, even those having already a defined category')
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$query = [
|
||||
'SELECT' => [
|
||||
'id',
|
||||
],
|
||||
'FROM' => \Software::getTable(),
|
||||
];
|
||||
if (!$input->getOption('all')) {
|
||||
$query['WHERE'] = [
|
||||
'softwarecategories_id' => 0
|
||||
];
|
||||
}
|
||||
|
||||
$software_iterator = $this->db->request($query);
|
||||
|
||||
$sofware_count = $software_iterator->count();
|
||||
if ($sofware_count === 0) {
|
||||
$output->writeln('<info>' . __('No software to process.') . '</info>');
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
$progress_bar = new ProgressBar($output, $sofware_count);
|
||||
$progress_bar->start();
|
||||
|
||||
$processed_count = 0;
|
||||
foreach ($software_iterator as $data) {
|
||||
$progress_bar->advance(1);
|
||||
|
||||
$this->writelnOutputWithProgressBar(
|
||||
sprintf(__('Processing software having id "%s".'), $data['id']),
|
||||
$progress_bar,
|
||||
OutputInterface::VERBOSITY_VERY_VERBOSE
|
||||
);
|
||||
|
||||
$software = new \Software();
|
||||
|
||||
if (!$software->getFromDB($data['id'])) {
|
||||
$this->writelnOutputWithProgressBar(
|
||||
sprintf(__('Unable to load software having id "%s".'), $data['id']),
|
||||
$progress_bar,
|
||||
OutputInterface::VERBOSITY_NORMAL
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$rule_collection = new \RuleSoftwareCategoryCollection();
|
||||
$input = $rule_collection->processAllRules(
|
||||
[],
|
||||
$software->fields,
|
||||
[
|
||||
'name' => $software->fields['name'],
|
||||
'manufacturers_id' => $software->fields['manufacturers_id'],
|
||||
]
|
||||
);
|
||||
|
||||
$software->update($input);
|
||||
|
||||
$processed_count++;
|
||||
}
|
||||
|
||||
$progress_bar->finish();
|
||||
$this->output->write(PHP_EOL);
|
||||
|
||||
$output->writeln(
|
||||
'<info>' .sprintf(__('Number of softwares processed: %d.'), $processed_count) . '</info>'
|
||||
);
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
}
|
||||
129
inc/console/rules/replaydictionnaryrulescommand.class.php
Normal file
129
inc/console/rules/replaydictionnaryrulescommand.class.php
Normal file
@ -0,0 +1,129 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Rules;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use Glpi\Console\AbstractCommand;
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||
|
||||
class ReplayDictionnaryRulesCommand extends AbstractCommand {
|
||||
|
||||
protected function configure() {
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:rules:replay_dictionnary_rules');
|
||||
$this->setAliases(['rules:replay_dictionnary_rules']);
|
||||
$this->setDescription(__('Replay dictionnary rules on existing items'));
|
||||
|
||||
$this->addOption(
|
||||
'dictionnary',
|
||||
'd',
|
||||
InputOption::VALUE_REQUIRED,
|
||||
sprintf(
|
||||
__('Dictionnary to use. Possible values are: %s'),
|
||||
implode(', ', $this->getDictionnaryTypes())
|
||||
)
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'manufacturer-id',
|
||||
'm',
|
||||
InputOption::VALUE_REQUIRED,
|
||||
__('If option is set, only items having given manufacturer ID will be processed.')
|
||||
. "\n" . __('Currently only available for Software dictionnary.')
|
||||
);
|
||||
}
|
||||
|
||||
protected function interact(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
if (empty($input->getOption('dictionnary'))) {
|
||||
// Ask for dictionnary argument is empty
|
||||
/** @var \Symfony\Component\Console\Helper\QuestionHelper $question_helper */
|
||||
$question_helper = $this->getHelper('question');
|
||||
$question = new ChoiceQuestion(
|
||||
__('Which dictionnary do you want to replay ?'),
|
||||
$this->getDictionnaryTypes()
|
||||
);
|
||||
$answer = $question_helper->ask(
|
||||
$input,
|
||||
$output,
|
||||
$question
|
||||
);
|
||||
$input->setOption('dictionnary', $answer);
|
||||
}
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$dictionnary = $input->getOption('dictionnary');
|
||||
$rulecollection = \RuleCollection::getClassByType($dictionnary);
|
||||
|
||||
if (!in_array($dictionnary, $this->getDictionnaryTypes())
|
||||
|| !($rulecollection instanceof \RuleCollection)) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(__('Invalid "dictionnary" value.'))
|
||||
);
|
||||
}
|
||||
|
||||
$params = [];
|
||||
if (null !== ($manufacturer_id = $input->getOption('manufacturer-id'))) {
|
||||
$params['manufacturer'] = $manufacturer_id;
|
||||
}
|
||||
|
||||
// Nota: implementations of RuleCollection::replayRulesOnExistingDB() are printing
|
||||
// messages during execution on CLI mode.
|
||||
// This could be improved by using the $output object to handle choosed verbosity level.
|
||||
$rulecollection->replayRulesOnExistingDB(0, 0, [], $params);
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of available disctionnary types.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function getDictionnaryTypes(): array {
|
||||
global $CFG_GLPI;
|
||||
$types = $CFG_GLPI['dictionnary_types'];
|
||||
sort($types);
|
||||
return $types;
|
||||
}
|
||||
}
|
||||
110
inc/console/security/changekeycommand.class.php
Normal file
110
inc/console/security/changekeycommand.class.php
Normal file
@ -0,0 +1,110 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Security;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use Glpi\Console\AbstractCommand;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
use GLPIKey;
|
||||
|
||||
class ChangekeyCommand extends AbstractCommand {
|
||||
/**
|
||||
* Error code returned when unable to renew key.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_UNABLE_TO_RENEW_KEY = 1;
|
||||
|
||||
protected function configure() {
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:security:change_key');
|
||||
$this->setDescription(__('Change password storage key and update values in database.'));
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
$glpikey = new GLPIKey();
|
||||
|
||||
$fields = $glpikey->getFields();
|
||||
$configs = $glpikey->getConfigs();
|
||||
$conf_count = 0;
|
||||
foreach ($configs as $config) {
|
||||
$conf_count += count($config);
|
||||
}
|
||||
|
||||
$output->writeln(
|
||||
sprintf(
|
||||
'<info>' . __('Found %1$s field(s) and %2$s configuration entries requiring migration.') . '</info>',
|
||||
count($fields),
|
||||
$conf_count
|
||||
)
|
||||
);
|
||||
|
||||
if (!$input->getOption('no-interaction')) {
|
||||
// Ask for confirmation (unless --no-interaction)
|
||||
$question_helper = $this->getHelper('question');
|
||||
$run = $question_helper->ask(
|
||||
$input,
|
||||
$output,
|
||||
new ConfirmationQuestion(__('Do you want to continue ?') . ' [Yes/no]', true)
|
||||
);
|
||||
if (!$run) {
|
||||
$output->writeln(
|
||||
'<comment>' . __('Aborted.') . '</comment>',
|
||||
OutputInterface::VERBOSITY_VERBOSE
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
$created = $glpikey->generate();
|
||||
if (!$created) {
|
||||
$output->writeln(
|
||||
'<error>' . __('Unable to change security key!') . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
return self::ERROR_UNABLE_TO_RENEW_KEY;
|
||||
}
|
||||
|
||||
$this->output->write(PHP_EOL);
|
||||
|
||||
$output->writeln('<info>' . __('New security key generated; database updated.') . '</info>');
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
}
|
||||
105
inc/console/system/checkrequirementscommand.class.php
Normal file
105
inc/console/system/checkrequirementscommand.class.php
Normal file
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\System;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use Glpi\Console\AbstractCommand;
|
||||
use Glpi\System\RequirementsManager;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class CheckRequirementsCommand extends AbstractCommand {
|
||||
|
||||
protected $requires_db = false;
|
||||
|
||||
protected function configure() {
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:system:check_requirements');
|
||||
$this->setAliases(['system:check_requirements']);
|
||||
$this->setDescription(__('Check system requirements'));
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$requirements_manager = new RequirementsManager();
|
||||
$core_requirements = $requirements_manager->getCoreRequirementList(
|
||||
$this->db instanceof \DBmysql && $this->db->connected ? $this->db : null
|
||||
);
|
||||
|
||||
$informations = new Table($output);
|
||||
$informations->setHeaders(
|
||||
[
|
||||
__('Requirement'),
|
||||
__('Status'),
|
||||
__('Messages'),
|
||||
]
|
||||
);
|
||||
|
||||
/* @var \Glpi\System\Requirement\RequirementInterface $requirement */
|
||||
foreach ($core_requirements as $requirement) {
|
||||
if ($requirement->isOutOfContext()) {
|
||||
continue; // skip requirement if not relevant
|
||||
}
|
||||
|
||||
if ($requirement->isValidated()) {
|
||||
$status = sprintf('<%s>[%s]</>', 'fg=black;bg=green', __('OK'));
|
||||
} else {
|
||||
$status = $requirement->isOptional()
|
||||
? sprintf('<%s>[%s]</> ', 'fg=white;bg=yellow', __('WARNING'))
|
||||
: sprintf('<%s>[%s]</> ', 'fg=white;bg=red', __('ERROR'));
|
||||
}
|
||||
|
||||
$informations->addRow(
|
||||
[
|
||||
$requirement->getTitle(),
|
||||
$status,
|
||||
$requirement->isValidated() ? '' : implode("\n", $requirement->getValidationMessages())
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$informations->render();
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
public function mustCheckMandatoryRequirements(): bool {
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
72
inc/console/system/checkstatuscommand.class.php
Normal file
72
inc/console/system/checkstatuscommand.class.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2018 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\System;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use Glpi\Console\AbstractCommand;
|
||||
use Glpi\System\Status\StatusChecker;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class CheckStatusCommand extends AbstractCommand {
|
||||
|
||||
protected function configure() {
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:system:status');
|
||||
$this->setAliases(['system:status']);
|
||||
$this->setDescription(__('Check system status'));
|
||||
$this->addOption('format', 'f', InputOption::VALUE_OPTIONAL,
|
||||
'Output format [plain or json]', 'plain');
|
||||
$this->addOption('private', 'p', InputOption::VALUE_NONE,
|
||||
'Status information publicity. Private status information may contain potentially sensitive information such as version information.');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$format = strtolower($input->getOption('format'));
|
||||
$status = StatusChecker::getFullStatus(!$input->getOption('private'), $format === 'json');
|
||||
|
||||
if ($format === 'json') {
|
||||
$output->writeln(json_encode($status, JSON_PRETTY_PRINT));
|
||||
} else {
|
||||
$output->writeln($status);
|
||||
}
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
}
|
||||
213
inc/console/task/unlockcommand.class.php
Normal file
213
inc/console/task/unlockcommand.class.php
Normal file
@ -0,0 +1,213 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Task;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use CronTask;
|
||||
use Glpi\Console\AbstractCommand;
|
||||
use Glpi\Event;
|
||||
use QueryExpression;
|
||||
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
|
||||
class UnlockCommand extends AbstractCommand {
|
||||
|
||||
protected function configure() {
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:task:unlock');
|
||||
$this->setAliases(['task:unlock']);
|
||||
$this->setDescription(__('Unlock automatic tasks'));
|
||||
|
||||
$this->addOption(
|
||||
'all',
|
||||
'a',
|
||||
InputOption::VALUE_NONE,
|
||||
__('Unlock all tasks')
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'cycle',
|
||||
'c',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
__('Execution time (in cycles) from which the task is considered as stuck (delay = task frequency * cycle)'),
|
||||
null // Has to be null to detect lack of definition of both 'cycle' and 'delay' to set default delay (see self::validateInput())
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'delay',
|
||||
'd',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
__('Execution time (in seconds) from which the task is considered as stuck (default: 1800)'),
|
||||
null // Has to be null to detect lack of definition of both 'cycle' and 'delay' to set default delay (see self::validateInput())
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'task',
|
||||
't',
|
||||
InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
|
||||
__('Itemtype::name of task to unlock (e.g: "MailCollector::mailgate")')
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$this->validateInput($input);
|
||||
|
||||
$all = $input->getOption('all');
|
||||
$cycle = $input->getOption('cycle');
|
||||
$delay = $input->getOption('delay');
|
||||
$tasks = $input->getOption('task');
|
||||
|
||||
if (null !== $cycle) {
|
||||
$delay = $cycle . ' * ' . $this->db->quoteName('frequency');
|
||||
}
|
||||
|
||||
$task_iterator = $this->db->request(
|
||||
[
|
||||
'SELECT' => [
|
||||
'id',
|
||||
new QueryExpression(
|
||||
'CONCAT('
|
||||
. $this->db->quoteName('itemtype')
|
||||
. ', ' . $this->db->quoteValue('::')
|
||||
. ', ' . $this->db->quoteName('name')
|
||||
. ') AS ' . $this->db->quoteName('task')
|
||||
)
|
||||
],
|
||||
'FROM' => CronTask::getTable(),
|
||||
'WHERE' => [
|
||||
'state' => CronTask::STATE_RUNNING,
|
||||
new QueryExpression(
|
||||
'UNIX_TIMESTAMP(' . $this->db->quoteName('lastrun') . ') + ' . $delay
|
||||
. ' < UNIX_TIMESTAMP(NOW())'
|
||||
)
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
$crontask = new CronTask();
|
||||
$unlocked_count = 0;
|
||||
|
||||
foreach ($task_iterator as $task) {
|
||||
if (!$all && !in_array($task['task'], $tasks)) {
|
||||
$output->writeln(
|
||||
'<comment>' . sprintf(__('Task "%s" is still running but not in the whitelist.'), $task['task']) . '</comment>',
|
||||
OutputInterface::VERBOSITY_VERBOSE
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$input = [
|
||||
'id' => $task['id'],
|
||||
'state' => CronTask::STATE_WAITING,
|
||||
];
|
||||
if ($crontask->update($input)) {
|
||||
$unlocked_count++;
|
||||
$message = sprintf(__('Task "%s" unlocked.'), $task['task']);
|
||||
$output->writeln('<info>' . $message . '</info>');
|
||||
Event::log($task['id'], 'CronTask', 5, 'Configuration', $message);
|
||||
} else {
|
||||
$output->writeln(
|
||||
sprintf(
|
||||
'<error>' . __('An error occurs while trying to unlock "%s" task.') . '</error>',
|
||||
$task['task']
|
||||
),
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
}
|
||||
}
|
||||
$output->writeln(
|
||||
'<info>' .sprintf(__('Number of tasks unlocked: %d.'), $unlocked_count) . '</info>'
|
||||
);
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate command input.
|
||||
*
|
||||
* @param InputInterface $input
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function validateInput(InputInterface $input) {
|
||||
|
||||
$all = $input->getOption('all');
|
||||
$cycle = $input->getOption('cycle');
|
||||
$delay = $input->getOption('delay');
|
||||
$tasks = $input->getOption('task');
|
||||
|
||||
if (null !== $cycle && null !== $delay) {
|
||||
throw new InvalidArgumentException(
|
||||
__('Option --cycle is not compatible with option --delay.')
|
||||
);
|
||||
}
|
||||
|
||||
if (null !== $cycle && !preg_match('/^\d+$/', $cycle)) {
|
||||
throw new InvalidArgumentException(
|
||||
__('Option --cycle has to be an integer.')
|
||||
);
|
||||
}
|
||||
|
||||
if (null !== $delay && !preg_match('/^\d+$/', $delay)) {
|
||||
throw new InvalidArgumentException(
|
||||
__('Option --delay has to be an integer.')
|
||||
);
|
||||
}
|
||||
|
||||
if (null === $cycle && null === $delay) {
|
||||
$input->setOption('delay', 1800); // Default delay
|
||||
}
|
||||
|
||||
if ($all && !empty($tasks)) {
|
||||
throw new InvalidArgumentException(
|
||||
__('Option --all is not compatible with option --task.')
|
||||
);
|
||||
}
|
||||
|
||||
if (!$all && empty($tasks)) {
|
||||
throw new InvalidArgumentException(
|
||||
__('You have to specify which tasks to unlock using --all or --task options.')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user