Sindbad~EG File Manager
<?php
/**
* ---------------------------------------------------------------------
*
* GLPI - Gestionnaire Libre de Parc Informatique
*
* http://glpi-project.org
*
* @copyright 2015-2022 Teclib' and contributors.
* @copyright 2003-2014 by the INDEPNET Development Team.
* @licence https://www.gnu.org/licenses/gpl-3.0.html
*
* ---------------------------------------------------------------------
*
* LICENSE
*
* This file is part of GLPI.
*
* This program 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 3 of the License, or
* (at your option) any later version.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*
* ---------------------------------------------------------------------
*/
namespace Glpi\Console\Migration;
use CommonDBTM;
use Infocom;
use Plugin;
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;
use Toolbox;
abstract class AbstractPluginToCoreCommand 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 when import failed.
*
* @var integer
*/
const ERROR_PLUGIN_IMPORT_FAILED = 2;
/**
* Target items mapping.
*
* @var array
*/
private $target_items_mapping = [];
/**
* Matching items mapping.
* Acts as a local cache to prevent DB queries when matching was found earlier for same criteria.
*
* @var array
*/
private $matching_items_mapping = [];
protected function configure()
{
parent::configure();
$this->addOption(
'skip-errors',
's',
InputOption::VALUE_NONE,
__('Do not exit on import errors')
);
}
protected function initialize(InputInterface $input, OutputInterface $output)
{
parent::initialize($input, $output);
$this->checkPlugin();
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$no_interaction = $input->getOption('no-interaction');
if (!$no_interaction) {
// Ask for confirmation (unless --no-interaction)
$output->writeln(
[
sprintf(__('You are about to launch migration of "%s" plugin data into GLPI core tables.'), $this->getPluginKey()),
__('It is better to make a backup of your existing data before continuing.')
],
OutputInterface::VERBOSITY_QUIET
);
$this->askForConfirmation();
}
$this->migratePlugin();
$output->writeln('<info>' . __('Migration done.') . '</info>');
return 0; // Success
}
/**
* Check that required tables exists and fields are OK for migration.
*
* @return void
*/
protected function checkPlugin(): void
{
$required_version = $this->getRequiredMinimalPluginVersion();
if ($required_version !== null) {
$plugin = new Plugin();
if ($plugin->getFromDBbyDir($this->getPluginKey())) {
if (version_compare($plugin->fields['version'], $required_version, '<=')) {
$msg = sprintf(
__('Previously installed installed plugin %s version was %s. Minimal version supported by migration is %s.'),
$this->getPluginKey(),
$plugin->fields['version'],
$required_version
);
throw new \Glpi\Console\Exception\EarlyExitException(
'<error>' . $msg . '</error>',
self::ERROR_PLUGIN_VERSION_OR_DATA_INVALID
);
}
} else {
$msg = sprintf(
__('Unable to validate that previously installed plugin %s version was %s.'),
$this->getPluginKey(),
$required_version
);
$this->output->writeln(
'<comment>' . $msg . '</comment>',
OutputInterface::VERBOSITY_QUIET
);
$this->askForConfirmation(false);
}
}
$required_fields = $this->getRequiredDatabasePluginFields();
$missing_fields = false;
foreach ($required_fields as $field) {
if (!preg_match('/^[a-z_]+\.[a-z_]+$/', $field)) {
trigger_error(sprintf('Invalid format for "%s" value', $field), E_USER_WARNING);
$missing_fields = true;
continue;
}
list($tablename, $fieldname) = explode('.', $field);
if (!$this->db->tableExists($tablename) || !$this->db->fieldExists($tablename, $fieldname)) {
$this->output->writeln(
'<error>' . sprintf(__('Plugin database field "%s" is missing.'), $field) . '</error>',
OutputInterface::VERBOSITY_QUIET
);
$missing_fields = true;
}
}
if ($missing_fields) {
throw new \Glpi\Console\Exception\EarlyExitException(
'<error>' . __('Migration cannot be done.') . '</error>',
self::ERROR_PLUGIN_VERSION_OR_DATA_INVALID
);
}
}
/**
* Handle import error message.
* Throws a `\Glpi\Console\Exception\EarlyExitException` unless `skip-errors` option is used.
*
* @param string $message
* @param ProgressBar|null $progress_bar
* @param bool $prevent_exit
*
* @return void
*/
protected function handleImportError($message, ProgressBar $progress_bar = null, bool $prevent_exit = false): void
{
$skip_errors = $this->input->getOption('skip-errors');
$verbosity = $skip_errors
? OutputInterface::VERBOSITY_NORMAL
: OutputInterface::VERBOSITY_QUIET;
$message = '<error>' . $message . '</error>';
if ($progress_bar instanceof ProgressBar) {
if (!$skip_errors && !$prevent_exit) {
$this->output->write(PHP_EOL); // Keep progress bar last state and go to next line
}
$this->writelnOutputWithProgressBar(
$message,
$progress_bar,
$verbosity
);
} else {
$this->output->writeln(
$message,
$verbosity
);
}
if (!$skip_errors && !$prevent_exit) {
throw new \Glpi\Console\Exception\EarlyExitException(
'<error>' . __('Plugin data import failed.') . '</error>',
self::ERROR_PLUGIN_IMPORT_FAILED
);
}
}
/**
* Get ID of existing item that matches given fields, if any.
*
* @param string $itemtype
* @param array $criteria
*
* @return int|null
*/
protected function getMatchingElementId(string $itemtype, array $criteria): ?int
{
if (!array_key_exists($itemtype, $this->matching_items_mapping)) {
$this->matching_items_mapping[$itemtype] = [];
}
$criteria_sha = sha1(serialize($criteria));
if (array_key_exists($criteria_sha, $this->matching_items_mapping[$itemtype])) {
return $this->matching_items_mapping[$itemtype][$criteria_sha];
}
$item = getItemForItemtype($itemtype);
if ($item === false) {
return null;
}
if ($item->getFromDBByCrit(Toolbox::addslashes_deep($criteria))) {
$id = $item->getID();
$this->matching_items_mapping[$itemtype][$criteria_sha] = $id;
return $id;
}
return null;
}
/**
* Define target item for given source item.
*
* @param string $source_itemtype
* @param integer $source_id
* @param string $target_itemtype
* @param integer $target_id
*
* @return void
*/
protected function defineTargetItem(string $source_itemtype, int $source_id, string $target_itemtype, int $target_id): void
{
if (!array_key_exists($source_itemtype, $this->target_items_mapping)) {
$this->target_items_mapping[$source_itemtype] = [];
}
$this->target_items_mapping[$source_itemtype][$source_id] = [
'itemtype' => $target_itemtype,
'id' => $target_id,
];
}
/**
* Returns target item corresponding to given itemtype and id.
*
* @param string $source_itemtype
* @param integer $source_id
*
* @return null|CommonDBTM
*/
protected function getTargetItem(string $source_itemtype, int $source_id): ?CommonDBTM
{
if (
!array_key_exists($source_itemtype, $this->target_items_mapping)
|| !array_key_exists($source_id, $this->target_items_mapping[$source_itemtype])
) {
return null;
}
$mapping = $this->target_items_mapping[$source_itemtype][$source_id];
$id = $mapping['id'];
$itemtype = $mapping['itemtype'];
if (!is_a($itemtype, CommonDBTM::class, true)) {
return null;
}
$item = new $itemtype();
if (!$item->getFromDB($id)) {
return null;
}
return $item;
}
/**
* Store an item. Will update existing item if `$existing_item_id` is passed,
* otherwise will create a new item.
*
* @param string $itemtype
* @param int|null $existing_item_id
* @param array $input
* @param ProgressBar|null $progress_bar
*
* @return null|CommonDBTM Stored item.
*/
protected function storeItem(string $itemtype, ?int $existing_item_id, array $input, ProgressBar $progress_bar = null): ?CommonDBTM
{
if (!is_a($itemtype, CommonDBTM::class, true)) {
throw new \LogicException(sprintf('Invalid itemtype "%s".', $itemtype));
}
$item = new $itemtype();
if ($existing_item_id !== null) {
$input[$itemtype::getIndexName()] = $existing_item_id;
if ($item->update($input) === false) {
$this->handleImportError(
sprintf(
__('Unable to update %s "%s" (%d).'),
$itemtype::getTypeName(),
$input['name'] ?? NOT_AVAILABLE,
(int) $existing_item_id
),
$progress_bar
);
return null;
}
} else {
if ($item->add($input) === false) {
$this->handleImportError(
sprintf(
__('Unable to create %s "%s" (%d).'),
$itemtype::getTypeName(),
$input['name'] ?? NOT_AVAILABLE,
(int) $input[$itemtype::getIndexName()]
),
$progress_bar
);
return null;
}
}
return $item;
}
/**
* Store infocom data related to given item.
*
* @param CommonDBTM $item
* @param array $infocom_input
* @param ProgressBar|null $progress_bar
*
* @return void
*/
protected function storeInfocomForItem(CommonDBTM $item, array $infocom_input, ProgressBar $progress_bar = null): void
{
$infocom = new Infocom();
$exists = $infocom->getFromDBByCrit(
[
'itemtype' => $item->getType(),
'items_id' => $item->getID(),
]
);
if ($exists) {
$infocom_input += [
'id' => $infocom->fields['id']
];
$success = $infocom->update($infocom_input);
} else {
$infocom_input += [
'itemtype' => $item->getType(),
'items_id' => $item->getID(),
'entities_id' => $item->fields['entities_id'] ?? 0,
'is_recursive' => $item->fields['is_recursive'] ?? 0,
];
$success = $infocom->add($infocom_input);
}
if (!$success) {
$this->handleImportError(
sprintf(
__('Unable to financial and administrative information for %s "%s" (%d).'),
$item->getType(),
$item->getName(),
$item->getID()
),
$progress_bar
);
}
}
/**
* Returns key of the plugin handled by this migration.
*
* @return string
*/
abstract protected function getPluginKey(): string;
/**
* Returns the minimal version of plugin supported by this migration.
*
* @return string|null
*/
abstract protected function getRequiredMinimalPluginVersion(): ?string;
/**
* Returns the list of database plugin fields by this migration.
* Expected returned value is a string array containing values in `table_name.field_name` format.
*
* @return array
*/
abstract protected function getRequiredDatabasePluginFields(): array;
/**
* Migrate plugin data.
*
* @return void
*/
abstract protected function migratePlugin(): void;
}
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists