. * --------------------------------------------------------------------- */ 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); } }