Files
MYSOPHAL/inc/console/commandloader.class.php
2025-08-07 13:15:31 +01:00

310 lines
8.3 KiB
PHP

<?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);
}
}