diff --git a/.phpstan-baseline.php b/.phpstan-baseline.php index 0e2f15916a4..96896ab728f 100644 --- a/.phpstan-baseline.php +++ b/.phpstan-baseline.php @@ -2437,18 +2437,6 @@ 'count' => 1, 'path' => __DIR__ . '/src/Glpi/Console/Application.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Call to function property_exists\\(\\) with \\$this\\(Glpi\\\\Console\\\\Application\\) and \'db\' will always evaluate to true\\.$#', - 'identifier' => 'function.alreadyNarrowedType', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Console/Application.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Property Glpi\\\\Console\\\\Application\\:\\:\\$error_handler is unused\\.$#', - 'identifier' => 'property.unused', - 'count' => 1, - 'path' => __DIR__ . '/src/Glpi/Console/Application.php', -]; $ignoreErrors[] = [ 'message' => '#^Strict comparison using \\!\\=\\= between null and \'development\' will always evaluate to true\\.$#', 'identifier' => 'notIdentical.alwaysTrue', diff --git a/CHANGELOG.md b/CHANGELOG.md index 60d19da3ba1..505afcf813e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -370,6 +370,7 @@ The present file will list all changes made to the project; according to the - `Contract::showShort()` - `DbUtils::closeDBConnections()` - `DbUtils::regenerateTreeCompleteName()` +- `DBConnection::displayMySQLError()` - `DBmysql::error` property. - `Document::getImage()` - `Document::showUploadedFilesDropdown()` @@ -388,6 +389,7 @@ The present file will list all changes made to the project; according to the - `Glpi\Api\API::showDebug()` - `Glpi\Api\API::returnSanitizedContent()` - `Glpi\Application\ErrorHandler::handleSqlError()` +- `Glpi\Console\Command\ForceNoPluginsOptionCommandInterface` class - `Glpi\Dashboard\Filter::dates()` - `Glpi\Dashboard\Filter::dates_mod()` - `Glpi\Dashboard\Filter::itilcategory()` diff --git a/bin/console b/bin/console index 160c61e14f1..785e008e7a7 100755 --- a/bin/console +++ b/bin/console @@ -153,11 +153,5 @@ if (array_key_exists('config-dir', $options)) { require_once dirname(__DIR__) . '/vendor/autoload.php'; $kernel = new \Glpi\Kernel\Kernel($options['env'] ?? null); -$kernel->loadCliConsoleOnlyConfig(); - -if (file_exists(GLPI_CONFIG_DIR . '/config_db.php')) { - include_once GLPI_CONFIG_DIR . '/config_db.php'; -} - -$application = new \Glpi\Console\Application(); +$application = new \Glpi\Console\Application($kernel); $application->run(); diff --git a/dependency_injection/legacyConfigProviders.php b/dependency_injection/legacyConfigProviders.php index 5f8fc1ff2b9..f3156877b45 100644 --- a/dependency_injection/legacyConfigProviders.php +++ b/dependency_injection/legacyConfigProviders.php @@ -35,17 +35,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; use Glpi\Config\LegacyConfigProviderInterface; -use Glpi\Config\LegacyConfigurators\CleanPHPSelfParam; -use Glpi\Config\LegacyConfigurators\ConfigRest; -use Glpi\Config\LegacyConfigurators\CustomObjectsAutoloader; -use Glpi\Config\LegacyConfigurators\CustomObjectsBootstrap; -use Glpi\Config\LegacyConfigurators\InitializeDbConnection; -use Glpi\Config\LegacyConfigurators\InitializePlugins; -use Glpi\Config\LegacyConfigurators\LoadLegacyConfiguration; -use Glpi\Config\LegacyConfigurators\ProfilerStart; -use Glpi\Config\LegacyConfigurators\SessionConfig; -use Glpi\Config\LegacyConfigurators\SessionStart; -use Glpi\Config\LegacyConfigurators\StandardIncludes; +use Glpi\Config\LegacyConfigurators; return static function (ContainerConfigurator $container): void { $services = $container->services(); @@ -65,22 +55,10 @@ * ⚠ Here, ORDER of definition matters! */ - $services->set(ProfilerStart::class)->tag($tagName, ['priority' => 200]); - $services->set(InitializeDbConnection::class)->tag($tagName, ['priority' => 190]); - $services->set(LoadLegacyConfiguration::class)->tag($tagName, ['priority' => 180]); - $services->set(SessionStart::class)->tag($tagName, ['priority' => 170]); - $services->set(StandardIncludes::class)->tag($tagName, ['priority' => 160]); - $services->set(CleanPHPSelfParam::class)->tag($tagName, ['priority' => 150]); - $services->set(SessionConfig::class)->tag($tagName, ['priority' => 130]); - - // Must be done before plugins initialization, to allow plugin to work with concrete class names. - $services->set(CustomObjectsAutoloader::class)->tag($tagName, ['priority' => 120]); - - $services->set(InitializePlugins::class)->tag($tagName, ['priority' => 110]); - - // Must be done after plugins initialization, to allow plugin to register new capacities. - $services->set(CustomObjectsBootstrap::class)->tag($tagName, ['priority' => 100]); + $services->set(LegacyConfigurators\StandardIncludes::class)->tag($tagName, ['priority' => 160]); + $services->set(LegacyConfigurators\CleanPHPSelfParam::class)->tag($tagName, ['priority' => 150]); + $services->set(LegacyConfigurators\SessionConfig::class)->tag($tagName, ['priority' => 130]); // FIXME: This class MUST stay at the end until the entire config is revamped. - $services->set(ConfigRest::class)->tag($tagName, ['priority' => 10]); + $services->set(LegacyConfigurators\ConfigRest::class)->tag($tagName, ['priority' => 10]); }; diff --git a/dependency_injection/services.php b/dependency_injection/services.php index a7df2ee87a0..4aaeb34e507 100644 --- a/dependency_injection/services.php +++ b/dependency_injection/services.php @@ -57,6 +57,7 @@ $services->load('Glpi\Config\\', $projectDir . '/src/Glpi/Config'); $services->load('Glpi\Controller\\', $projectDir . '/src/Glpi/Controller'); $services->load('Glpi\Http\\', $projectDir . '/src/Glpi/Http'); + $services->load('Glpi\Kernel\\Listener\\', $projectDir . '/src/Glpi/Kernel/Listener'); $services->load('Glpi\DependencyInjection\\', $projectDir . '/src/Glpi/DependencyInjection'); $services->load('Glpi\Progress\\', $projectDir . '/src/Glpi/Progress')->exclude($projectDir . '/src/Glpi/Progress/SessionProgress.php'); diff --git a/front/cron.php b/front/cron.php index bb53cca0a9f..b1ebd677f0a 100644 --- a/front/cron.php +++ b/front/cron.php @@ -33,6 +33,12 @@ * --------------------------------------------------------------------- */ +/** + * @var \DBmysql|null $DB + * @var array $CFG_GLPI + */ +global $DB, $CFG_GLPI; + if (PHP_SAPI === 'cli') { // Check the resources state before trying to instanciate the Kernel. // It must be done here as this check must be done even when the Kernel @@ -43,13 +49,45 @@ require_once dirname(__DIR__) . '/vendor/autoload.php'; $kernel = new \Glpi\Kernel\Kernel(); - $kernel->loadCommonGlobalConfig(); -} + $kernel->boot(); -/** - * @var array $CFG_GLPI - */ -global $CFG_GLPI; + // Handle the `--debug` argument + $debug = array_search('--debug', $_SERVER['argv']); + if ($debug) { + $_SESSION['glpi_use_mode'] = Session::DEBUG_MODE; + unset($_SERVER['argv'][$debug]); + $_SERVER['argv'] = array_values($_SERVER['argv']); + $_SERVER['argc']--; + } + + if ($CFG_GLPI['maintenance_mode'] ?? false) { + echo 'Service is down for maintenance. It will be back shortly.' . PHP_EOL; + exit(); + } + + if (!($DB instanceof DBmysql)) { + echo sprintf( + 'ERROR: The database configuration file "%s" is missing or is corrupted. You have to either restart the install process, or restore this file.', + GLPI_CONFIG_DIR . '/config_db.php' + ) . PHP_EOL; + exit(); + } + + if (!$DB->connected) { + echo 'ERROR: The connection to the SQL server could not be established. Please check your configuration.' . PHP_EOL; + exit(); + } + + if (!Config::isLegacyConfigurationLoaded()) { + echo 'ERROR: Unable to load the GLPI configuration from the database.' . PHP_EOL; + exit(); + } + + if (!defined('SKIP_UPDATES') && !Update::isDbUpToDate()) { + echo 'The GLPI codebase has been updated. The update of the GLPI database is necessary.' . PHP_EOL; + exit(); + } +} // Ensure current directory when run from crontab chdir(__DIR__); diff --git a/install/install.php b/install/install.php index 4a853a04b80..b76ed902bf3 100644 --- a/install/install.php +++ b/install/install.php @@ -54,8 +54,6 @@ Session::loadLanguage(with_plugins: false); } -Session::checkCookieSecureConfig(); - //Print a correct Html header for application function header_html($etape) { @@ -518,11 +516,12 @@ function checkConfigFile() checkConfigFile(); - // Add a flag that will be used to validate that installation can be processed. - // This flag is put here just after checking that DB config file does not exist yet. - // It is mandatory to validate that `Etape_4` to `Etape_6` are not used outside installation process - // to change GLPI base URL without even being authenticated. + // Add a flag that will be used to validate that installation can be processed. + // This flag is put here just after checking that DB config file does not exist yet. + // It is mandatory to validate that installation endpoints are not used outside installation process + // to alter the GLPI database or configuration. $_SESSION['can_process_install'] = true; + $_SESSION['is_installing'] = true; header_html(__("Select your language")); choose_language(); @@ -532,8 +531,6 @@ function checkConfigFile() $_POST["db_pass"] = rawurldecode($_POST["db_pass"]); } - $_SESSION['is_installing'] = true; - switch ($_POST["install"]) { case "lang_select": // lang ok, go accept licence checkConfigFile(); diff --git a/install/update.php b/install/update.php index 24e6372d583..f64c97e07b4 100644 --- a/install/update.php +++ b/install/update.php @@ -37,8 +37,6 @@ use Glpi\Cache\CacheManager; use Glpi\Toolbox\VersionParser; -include_once(GLPI_CONFIG_DIR . "/config_db.php"); - /** * @var \DBmysql|null $DB * @var \Psr\SimpleCache\CacheInterface $GLPI_CACHE @@ -46,21 +44,14 @@ * @var bool $HEADER_LOADED */ global $DB, - $GLPI_CACHE, - $update, - $HEADER_LOADED; + $GLPI_CACHE, + $update, + $HEADER_LOADED; $GLPI_CACHE = (new CacheManager())->getInstallerCacheInstance(); -Session::checkCookieSecureConfig(); - -if (!($DB instanceof DBmysql)) { // $DB can have already been init in install.php script - $DB = new DB(); -} $DB->disableTableCaching(); //prevents issues on fieldExists upgrading from old versions -Config::loadLegacyConfiguration(); - $update = new Update($DB); if (isset($_POST['update_end'])) { @@ -146,10 +137,6 @@ function showSecurityKeyCheckForm() //Debut du script $HEADER_LOADED = true; -Session::start(); - -Session::loadLanguage('', false); - // Send UTF8 Headers header("Content-Type: text/html; charset=UTF-8"); @@ -229,6 +216,8 @@ function showSecurityKeyCheckForm() doUpdateDb(); echo ""; + Session::destroy(); // Remove session data set by web installation + $_SESSION['telemetry_from_install'] = true; TemplateRenderer::getInstance()->display('install/update.html.twig', [ diff --git a/phpunit/bootstrap.php b/phpunit/bootstrap.php index ae4c0ba0911..112a2f170cf 100644 --- a/phpunit/bootstrap.php +++ b/phpunit/bootstrap.php @@ -50,17 +50,21 @@ define('FIXTURE_DIR', __DIR__ . "/../tests/fixtures"); -global $CFG_GLPI, $GLPI_CACHE; +global $GLPI_CACHE; require_once dirname(__DIR__) . '/vendor/autoload.php'; $kernel = new Kernel('testing'); -$kernel->loadCommonGlobalConfig(); +$kernel->boot(); if (!file_exists(GLPI_CONFIG_DIR . '/config_db.php')) { echo("\nConfiguration file for tests not found\n\nrun: php bin/console database:install --env=testing ...\n\n"); exit(1); } +if (!defined('SKIP_UPDATES') && !Update::isDbUpToDate()) { + echo 'The GLPI codebase has been updated. The update of the GLPI database is necessary.' . PHP_EOL; + exit(1); +} //init cache if (file_exists(GLPI_CONFIG_DIR . DIRECTORY_SEPARATOR . CacheManager::CONFIG_FILENAME)) { diff --git a/src/DBConnection.php b/src/DBConnection.php index 04974fc6293..f0b1f24ba9b 100644 --- a/src/DBConnection.php +++ b/src/DBConnection.php @@ -646,29 +646,6 @@ public static function getHistoryMaxDate($DBconnection) } - /** - * Display a common mysql connection error - **/ - public static function displayMySQLError() - { - /** @var \DBmysql $DB */ - global $DB; - - $fr_msg = "Le serveur Mysql est inaccessible. Vérifiez votre configuration."; - $en_msg = "A link to the SQL server could not be established. Please check your configuration."; - - if (!isCommandLine()) { - Html::nullHeader("Mysql Error", ''); - echo "

$en_msg

$fr_msg

"; - Html::nullFooter(); - } else { - echo "$en_msg\n$fr_msg\n"; - } - - exit(1); - } - - /** * @param $name **/ diff --git a/src/Glpi/Config/LegacyConfigurators/CleanPHPSelfParam.php b/src/Glpi/Config/LegacyConfigurators/CleanPHPSelfParam.php index 21116a8ef75..ee637eaa77d 100644 --- a/src/Glpi/Config/LegacyConfigurators/CleanPHPSelfParam.php +++ b/src/Glpi/Config/LegacyConfigurators/CleanPHPSelfParam.php @@ -37,7 +37,6 @@ use Glpi\Config\ConfigProviderHasRequestTrait; use Glpi\Config\ConfigProviderWithRequestInterface; use Glpi\Config\LegacyConfigProviderInterface; -use Glpi\Debug\Profiler; final class CleanPHPSelfParam implements LegacyConfigProviderInterface, ConfigProviderWithRequestInterface { @@ -45,12 +44,10 @@ final class CleanPHPSelfParam implements LegacyConfigProviderInterface, ConfigPr public function execute(): void { - Profiler::getInstance()->start('CleanPHPSelfParam::execute', Profiler::CATEGORY_BOOT); // Security of PHP_SELF $self = \Html::cleanParametersURL($this->getRequest()->server->get('PHP_SELF')); $_SERVER['PHP_SELF'] = $self; $this->getRequest()->server->set('PHP_SELF', $self); - Profiler::getInstance()->stop('CleanPHPSelfParam::execute'); } } diff --git a/src/Glpi/Config/LegacyConfigurators/ConfigRest.php b/src/Glpi/Config/LegacyConfigurators/ConfigRest.php index 9ab7c469f93..98b38223f0a 100644 --- a/src/Glpi/Config/LegacyConfigurators/ConfigRest.php +++ b/src/Glpi/Config/LegacyConfigurators/ConfigRest.php @@ -35,7 +35,6 @@ namespace Glpi\Config\LegacyConfigurators; use Glpi\Config\LegacyConfigProviderInterface; -use Glpi\Debug\Profiler; use Session; final readonly class ConfigRest implements LegacyConfigProviderInterface @@ -45,21 +44,9 @@ public function execute(): void /** * @var array $CFG_GLPI * @var \Psr\SimpleCache\CacheInterface $GLPI_CACHE - * @var bool $FOOTER_LOADED - * @var bool $HEADER_LOADED - * @var string $CURRENTCSRFTOKEN */ - global $CFG_GLPI, - $GLPI_CACHE, - $FOOTER_LOADED, $HEADER_LOADED, - $CURRENTCSRFTOKEN - ; + global $CFG_GLPI, $GLPI_CACHE; - // Mark if Header is loaded or not : - $HEADER_LOADED = false; - $FOOTER_LOADED = false; - - Profiler::getInstance()->start('ConfigRest::execute', Profiler::CATEGORY_BOOT); // Security : check CSRF token if (!isAPI() && count($_POST) > 0) { if (preg_match(':' . $CFG_GLPI['root_doc'] . '(/(plugins|marketplace)/[^/]*|)/ajax/:', $_SERVER['REQUEST_URI']) === 1) { @@ -73,8 +60,6 @@ public function execute(): void Session::checkCSRF($_POST); } } - // SET new global Token - $CURRENTCSRFTOKEN = ''; // Manage profile change if (isset($_REQUEST["force_profile"]) && ($_SESSION['glpiactiveprofile']['id'] ?? -1) != $_REQUEST["force_profile"]) { @@ -105,6 +90,5 @@ public function execute(): void ) { Session::loadGroups(); } - Profiler::getInstance()->stop('ConfigRest::execute'); } } diff --git a/src/Glpi/Config/LegacyConfigurators/SessionConfig.php b/src/Glpi/Config/LegacyConfigurators/SessionConfig.php index 55f177374da..81386e103dc 100644 --- a/src/Glpi/Config/LegacyConfigurators/SessionConfig.php +++ b/src/Glpi/Config/LegacyConfigurators/SessionConfig.php @@ -37,8 +37,6 @@ use Glpi\Config\ConfigProviderHasRequestTrait; use Glpi\Config\ConfigProviderWithRequestInterface; use Glpi\Config\LegacyConfigProviderInterface; -use Glpi\Debug\Profile; -use Glpi\Debug\Profiler; use Glpi\Toolbox\URL; use Session; @@ -48,15 +46,6 @@ final class SessionConfig implements LegacyConfigProviderInterface, ConfigProvid public function execute(): void { - Profiler::getInstance()->start('SessionConfig::execute', Profiler::CATEGORY_BOOT); - if ( - isset($_SESSION['glpi_use_mode']) - && ($_SESSION['glpi_use_mode'] == Session::DEBUG_MODE) - ) { - // Start the debug profile - Profile::getCurrent(); - } - if (!isset($_SESSION["MESSAGE_AFTER_REDIRECT"])) { $_SESSION["MESSAGE_AFTER_REDIRECT"] = []; } @@ -79,6 +68,5 @@ public function execute(): void if (isset($_REQUEST['glpilist_limit'])) { $_SESSION['glpilist_limit'] = $_REQUEST['glpilist_limit']; } - Profiler::getInstance()->stop('SessionConfig::execute'); } } diff --git a/src/Glpi/Config/LegacyConfigurators/StandardIncludes.php b/src/Glpi/Config/LegacyConfigurators/StandardIncludes.php index ede5c76807c..6be0bc80f14 100644 --- a/src/Glpi/Config/LegacyConfigurators/StandardIncludes.php +++ b/src/Glpi/Config/LegacyConfigurators/StandardIncludes.php @@ -34,155 +34,36 @@ namespace Glpi\Config\LegacyConfigurators; -use Auth; -use DBConnection; -use DBmysql; use Config; use Glpi\Application\View\TemplateRenderer; -use Glpi\Cache\CacheManager; +use Glpi\Config\ConfigProviderHasRequestTrait; +use Glpi\Config\ConfigProviderWithRequestInterface; use Glpi\Config\LegacyConfigProviderInterface; +use Glpi\Http\RequestPoliciesTrait; use Glpi\System\Requirement\DatabaseTablesEngine; use Glpi\System\RequirementsManager; use Glpi\Toolbox\VersionParser; use Html; use Session; -use Symfony\Component\DependencyInjection\Attribute\Autowire; use Toolbox; use Update; -final readonly class StandardIncludes implements LegacyConfigProviderInterface +final class StandardIncludes implements LegacyConfigProviderInterface, ConfigProviderWithRequestInterface { - public function __construct( - #[Autowire('%kernel.project_dir%')] private string $projectDir, - ) { - } + use ConfigProviderHasRequestTrait; + use RequestPoliciesTrait; public function execute(): void { /** - * @var \DBmysql|null $DB * @var array $CFG_GLPI - * @var \Psr\SimpleCache\CacheInterface $GLPI_CACHE */ - global $DB, - $CFG_GLPI, - $GLPI_CACHE - ; - - if (isset($_SESSION['is_installing'])) { - $GLPI_CACHE = (new CacheManager())->getInstallerCacheInstance(); - - Session::loadLanguage(with_plugins: false); - return; - } - - $skip_db_checks = false; - $skip_maintenance_checks = false; - if (array_key_exists('REQUEST_URI', $_SERVER)) { - if (preg_match('#^' . $CFG_GLPI['root_doc'] . '/front/(css|locale).php#', $_SERVER['REQUEST_URI']) === 1) { - $skip_db_checks = true; - $skip_maintenance_checks = true; - } - - $no_db_checks_scripts = [ - '#^' . $CFG_GLPI['root_doc'] . '/$#', - '#^' . $CFG_GLPI['root_doc'] . '/index.php#', - '#^' . $CFG_GLPI['root_doc'] . '/install/install.php#', - '#^' . $CFG_GLPI['root_doc'] . '/install/update.php#', - ]; - foreach ($no_db_checks_scripts as $pattern) { - if (preg_match($pattern, $_SERVER['REQUEST_URI']) === 1) { - $skip_db_checks = true; - break; - } - } - } - - //init cache - $cache_manager = new CacheManager(); - $GLPI_CACHE = $cache_manager->getCoreCacheInstance(); - - // Check if the DB is configured properly - if (!$skip_db_checks) { - if ($DB instanceof DBmysql) { - //Database connection - if (!$DB->connected) { - DBConnection::displayMySQLError(); - exit(1); - } - - //Options from DB, do not touch this part. - if (!Config::isLegacyConfigurationLoaded()) { - echo "Error accessing config table"; - exit(1); - } - } else { - Session::loadLanguage('', false); - - if (!isCommandLine()) { - // Prevent inclusion of debug information in footer, as they are based on vars that are not initialized here. - $debug_mode = $_SESSION['glpi_use_mode']; - $_SESSION['glpi_use_mode'] = Session::NORMAL_MODE; - - Html::nullHeader('Missing configuration', $CFG_GLPI["root_doc"]); - $twig_params = [ - 'config_db' => GLPI_CONFIG_DIR . '/config_db.php', - 'install_exists' => file_exists($this->projectDir . '/install/install.php'), - ]; - // language=Twig - echo TemplateRenderer::getInstance()->renderFromStringTemplate(<< -
-
-

GLPI seems to not be configured properly.

-

- Database configuration file "{{ config_db }}" is missing or is corrupted. - You have to either restart the install process, either restore this file. -
-
- {% if install_exists %} - Go to install page - {% endif %} -

-
-
- - TWIG, $twig_params); - Html::nullFooter(); - $_SESSION['glpi_use_mode'] = $debug_mode; - } else { - echo "GLPI seems to not be configured properly.\n"; - echo sprintf('Database configuration file "%s" is missing or is corrupted.', GLPI_CONFIG_DIR . '/config_db.php') . "\n"; - echo "You have to either restart the install process, either restore this file.\n"; - } - exit(1); - } - } - - if ( - isCommandLine() - && !defined('TU_USER') // In test suite context, used --debug option is the atoum one - && isset($_SERVER['argv']) - ) { - $key = array_search('--debug', $_SERVER['argv']); - if ($key) { - $_SESSION['glpi_use_mode'] = Session::DEBUG_MODE; - unset($_SERVER['argv'][$key]); - $_SERVER['argv'] = array_values($_SERVER['argv']); - $_SERVER['argc']--; - } - } - - // Override cfg_features by session value - foreach ($CFG_GLPI['user_pref_field'] as $field) { - if (!isset($_SESSION["glpi$field"]) && isset($CFG_GLPI[$field])) { - $_SESSION["glpi$field"] = $CFG_GLPI[$field]; - } - } + global $CFG_GLPI; // Check maintenance mode if ( - !$skip_maintenance_checks + !$this->isFrontEndAssetEndpoint($this->getRequest()) + && !$this->isSymfonyProfilerEndpoint($this->getRequest()) && isset($CFG_GLPI["maintenance_mode"]) && $CFG_GLPI["maintenance_mode"] ) { @@ -191,36 +72,20 @@ public function execute(): void } if (!isset($_SESSION["glpiskipMaintenance"]) || !$_SESSION["glpiskipMaintenance"]) { - Session::loadLanguage('', false); - if (isCommandLine()) { - echo __('Service is down for maintenance. It will be back shortly.'); - echo "\n"; - } else { - TemplateRenderer::getInstance()->display('maintenance.html.twig', [ - 'title' => "MAINTENANCE MODE", - 'maintenance_text' => $CFG_GLPI["maintenance_text"] ?? "", - ]); - } + TemplateRenderer::getInstance()->display('maintenance.html.twig', [ + 'title' => "MAINTENANCE MODE", + 'maintenance_text' => $CFG_GLPI["maintenance_text"] ?? "", + ]); exit(); } } // Check version - if (!$skip_db_checks && !defined('SKIP_UPDATES') && !Update::isDbUpToDate()) { - Session::checkCookieSecureConfig(); - + if ($this->shouldCheckDbStatus($this->getRequest()) && !defined('SKIP_UPDATES') && !Update::isDbUpToDate()) { // Prevent debug bar to be displayed when an admin user was connected with debug mode when codebase was updated. $debug_mode = $_SESSION['glpi_use_mode']; Toolbox::setDebugMode(Session::NORMAL_MODE); - Session::loadLanguage('', false); - - if (isCommandLine()) { - echo __('The GLPI codebase has been updated. The update of the GLPI database is necessary.'); - echo "\n"; - exit(); - } - /** @var \DBmysql $DB */ global $DB; @@ -286,8 +151,5 @@ public function execute(): void $_SESSION['glpi_use_mode'] = $debug_mode; exit(); } - - // Load Language file - Session::loadLanguage(); } } diff --git a/src/Glpi/Console/Application.php b/src/Glpi/Console/Application.php index 790068c3a3c..f67719bff81 100644 --- a/src/Glpi/Console/Application.php +++ b/src/Glpi/Console/Application.php @@ -35,19 +35,15 @@ namespace Glpi\Console; -use Config; -use DB; use DBmysql; use GLPI; use Glpi\Application\ErrorHandler; -use Glpi\Cache\CacheManager; use Glpi\Console\Command\ConfigurationCommandInterface; -use Glpi\Console\Command\ForceNoPluginsOptionCommandInterface; use Glpi\Console\Command\GlpiCommandInterface; +use Glpi\Kernel\Kernel; use Glpi\System\Requirement\RequirementInterface; use Glpi\System\RequirementsManager; use Glpi\Toolbox\Filesystem; -use Plugin; use Session; use Symfony\Component\Console\Application as BaseApplication; use Symfony\Component\Console\Command\Command; @@ -92,44 +88,35 @@ class Application extends BaseApplication */ private $config; - /** - * @var ErrorHandler - */ - private $error_handler; - - /** - * @var DBmysql|null - */ - private $db; + private ?DBmysql $db = null; /** * @var OutputInterface */ private $output; - public function __construct() + public function __construct(private Kernel $kernel) { + /** + * @var \DBmysql $DB + * @var array $CFG_GLPI + */ + global $DB, $CFG_GLPI; parent::__construct('GLPI CLI', GLPI_VERSION); - $this->initApplication(); - $this->initCache(); - $this->initDb(); - $this->initSession(); - $this->initConfig(); + $this->kernel->boot(); + + $this->db = $DB; + $this->config = &$CFG_GLPI; + + // Force the current "username" + $_SESSION['glpiname'] = 'cli'; $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); + $loader = new CommandLoader(include_plugins: $this->db instanceof DBmysql && $this->db->connected); $this->setCommandLoader($loader); - - if ($this->usePlugins()) { - $plugin = new Plugin(); - $plugin->init(true); - $loader->setIncludePlugins(true); - } } protected function getDefaultInputDefinition(): InputDefinition @@ -199,12 +186,6 @@ protected function getDefaultInputDefinition(): InputDefinition InputOption::VALUE_OPTIONAL, __('Configuration directory to use. Deprecated option') ), - new InputOption( - '--no-plugins', - null, - InputOption::VALUE_NONE, - __('Disable GLPI plugins (unless commands forces plugins loading)') - ), new InputOption( '--lang', null, @@ -336,106 +317,6 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI return $result; } - /** - * Initalize GLPI. - * - * @return void - */ - private function initApplication() - { - } - - /** - * Initialize database connection. - * - * @global DBmysql $DB - * - * @return void - * - * @throws RuntimeException - */ - private function initDb() - { - - if (!class_exists('DB', false) || !class_exists('mysqli', false)) { - return; - } - - /** @var \DBmysql $DB */ - 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 \Symfony\Component\Console\Exception\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 (!Session::canWriteSessionFiles()) { - throw new \Symfony\Component\Console\Exception\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; - $_SESSION['glpiname'] = 'cli'; - } - - /** - * Initialize GLPI cache. - * - * @global \Psr\SimpleCache\CacheInterface $GLPI_CACHE - * - * @return void - */ - private function initCache() - { - - /** @var \Psr\SimpleCache\CacheInterface $GLPI_CACHE */ - global $GLPI_CACHE; - $cache_manager = new CacheManager(); - $GLPI_CACHE = $cache_manager->getCoreCacheInstance(); - } - - /** - * Initialize GLPI configuration. - * - * @global array $CFG_GLPI - * - * @return void - */ - private function initConfig() - { - - /** @var array $CFG_GLPI */ - global $CFG_GLPI; - $this->config = &$CFG_GLPI; - - Config::loadLegacyConfiguration(); - } - /** * Compute and load output language. * @@ -468,9 +349,11 @@ private function computeAndLoadOutputLang() $lang = 'en_GB'; } - $_SESSION['glpilanguage'] = $lang; + if ($lang !== $_SESSION['glpilanguage']) { + $_SESSION['glpilanguage'] = $lang; - Session::loadLanguage('', $this->usePlugins()); + Session::loadLanguage(); + } } /** @@ -487,32 +370,6 @@ private function isLanguageValid($language) && array_key_exists($language, $this->config['languages']); } - /** - * Whether or not plugins have to be used. - * - * @return boolean - */ - private function usePlugins() - { - if (!($this->db instanceof DBmysql) || !$this->db->connected) { - return false; - } - - $input = new ArgvInput(); - - try { - $command = $this->find($this->getCommandName($input) ?? ''); - if ($command instanceof ForceNoPluginsOptionCommandInterface) { - return !$command->getNoPluginsOptionValue(); - } - } catch (\Symfony\Component\Console\Exception\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. * @@ -523,11 +380,9 @@ private function usePlugins() private function checkCoreMandatoryRequirements( array $command_specific_requirements ): 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 + $this->db instanceof DBmysql && $this->db->connected ? $this->db : null ); // Some commands might specify some extra requirements @@ -614,13 +469,11 @@ public function extractNamespace(string $name, ?int $limit = null): string * Gets the Kernel associated with this Console. * * This method is required by most of the commands provided by the `symfony/framework-bundle`. + * * @see \Symfony\Bundle\FrameworkBundle\Console\Application::getKernel() */ - public function getKernel(): ?KernelInterface + public function getKernel(): KernelInterface { - /** @var KernelInterface|null $kernel */ - global $kernel; - - return $kernel; + return $this->kernel; } } diff --git a/src/Glpi/Console/Command/ForceNoPluginsOptionCommandInterface.php b/src/Glpi/Console/Command/ForceNoPluginsOptionCommandInterface.php deleted file mode 100644 index 7c8c0ff8cf5..00000000000 --- a/src/Glpi/Console/Command/ForceNoPluginsOptionCommandInterface.php +++ /dev/null @@ -1,46 +0,0 @@ -. - * - * --------------------------------------------------------------------- - */ - -namespace Glpi\Console\Command; - -interface ForceNoPluginsOptionCommandInterface -{ - /** - * Defines whether or not command prevents plugins to be loaded. - * - * @return boolean - */ - public function getNoPluginsOptionValue(); -} diff --git a/src/Glpi/Console/Database/AbstractConfigureCommand.php b/src/Glpi/Console/Database/AbstractConfigureCommand.php index 255ca02a8f1..4c7b0fff457 100644 --- a/src/Glpi/Console/Database/AbstractConfigureCommand.php +++ b/src/Glpi/Console/Database/AbstractConfigureCommand.php @@ -39,7 +39,6 @@ use DBConnection; use DBmysql; use Glpi\Console\AbstractCommand; -use Glpi\Console\Command\ForceNoPluginsOptionCommandInterface; use Glpi\System\Requirement\DbTimezones; use mysqli; use Symfony\Component\Console\Exception\InvalidArgumentException; @@ -49,7 +48,7 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\Question; -abstract class AbstractConfigureCommand extends AbstractCommand implements ForceNoPluginsOptionCommandInterface +abstract class AbstractConfigureCommand extends AbstractCommand { /** * Error code returned if DB configuration succeed. @@ -351,12 +350,6 @@ public function __construct( }; } - public function getNoPluginsOptionValue() - { - - return true; - } - /** * Check if DB is already configured. * diff --git a/src/Glpi/Console/Database/UpdateCommand.php b/src/Glpi/Console/Database/UpdateCommand.php index 835121f86a0..50ccfdeb138 100644 --- a/src/Glpi/Console/Database/UpdateCommand.php +++ b/src/Glpi/Console/Database/UpdateCommand.php @@ -39,7 +39,6 @@ use Glpi\Cache\CacheManager; use Glpi\Console\AbstractCommand; use Glpi\Console\Command\ConfigurationCommandInterface; -use Glpi\Console\Command\ForceNoPluginsOptionCommandInterface; use Glpi\Console\Traits\TelemetryActivationTrait; use Glpi\System\Diagnostic\DatabaseSchemaIntegrityChecker; use Glpi\System\Requirement\DatabaseTablesEngine; @@ -55,7 +54,7 @@ use Symfony\Component\Console\Output\OutputInterface; use Update; -class UpdateCommand extends AbstractCommand implements ConfigurationCommandInterface, ForceNoPluginsOptionCommandInterface +class UpdateCommand extends AbstractCommand implements ConfigurationCommandInterface { use TelemetryActivationTrait; @@ -266,12 +265,6 @@ protected function execute(InputInterface $input, OutputInterface $output) return 0; // Success } - public function getNoPluginsOptionValue() - { - - return true; - } - /** * Check schema integrity of installed database. * diff --git a/src/Glpi/Console/Plugin/AbstractPluginCommand.php b/src/Glpi/Console/Plugin/AbstractPluginCommand.php index 230a168679e..ab95c5df426 100644 --- a/src/Glpi/Console/Plugin/AbstractPluginCommand.php +++ b/src/Glpi/Console/Plugin/AbstractPluginCommand.php @@ -36,14 +36,13 @@ namespace Glpi\Console\Plugin; use Glpi\Console\AbstractCommand; -use Glpi\Console\Command\ForceNoPluginsOptionCommandInterface; 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 +abstract class AbstractPluginCommand extends AbstractCommand { /** * Wildcard value to target all directories. @@ -113,13 +112,6 @@ protected function interact(InputInterface $input, OutputInterface $output) } } - public function getNoPluginsOptionValue() - { - - // Force no loading on plugins in plugin install process - return true; - } - /** * Normalize input to symplify handling of specific arguments/options values. * diff --git a/src/Glpi/Console/Plugin/ListCommand.php b/src/Glpi/Console/Plugin/ListCommand.php index 6d44894c796..7b261d3cb0a 100644 --- a/src/Glpi/Console/Plugin/ListCommand.php +++ b/src/Glpi/Console/Plugin/ListCommand.php @@ -36,7 +36,6 @@ namespace Glpi\Console\Plugin; use Glpi\Console\AbstractCommand; -use Glpi\Console\Command\ForceNoPluginsOptionCommandInterface; use Glpi\Marketplace\Controller; use Plugin; use Symfony\Component\Console\Helper\Table; @@ -45,7 +44,7 @@ use Symfony\Component\Console\Output\OutputInterface; use Toolbox; -class ListCommand extends AbstractCommand implements ForceNoPluginsOptionCommandInterface +class ListCommand extends AbstractCommand { protected function configure() { @@ -107,9 +106,4 @@ protected function execute(InputInterface $input, OutputInterface $output) } return 0; } - - public function getNoPluginsOptionValue() - { - return true; - } } diff --git a/src/Glpi/Controller/ErrorController.php b/src/Glpi/Controller/ErrorController.php index 62ec3611937..4eff32fbc52 100644 --- a/src/Glpi/Controller/ErrorController.php +++ b/src/Glpi/Controller/ErrorController.php @@ -34,6 +34,8 @@ namespace Glpi\Controller; +use Config; +use DBConnection; use Glpi\Application\ErrorHandler; use Html; use Session; @@ -127,8 +129,10 @@ private function getErrorResponse(\Throwable $exception, Request $request): Resp { $status_code = $exception instanceof HttpExceptionInterface ? $exception->getStatusCode() : 500; - $title = _n('Error', 'Errors', 1); - $message = __('An unexpected error has occurred.'); + $title = _n('Error', 'Errors', 1); + $message = __('An unexpected error has occurred.'); + $link_text = null; + $link_url = null; if ($exception instanceof HttpExceptionInterface) { // Default messages. @@ -158,6 +162,10 @@ private function getErrorResponse(\Throwable $exception, Request $request): Resp ) { $message = $custom_message; } + if ($exception instanceof \Glpi\Exception\Http\HttpExceptionInterface) { + $link_text = $exception->getLinktext(); + $link_url = $exception->getLinkUrl(); + } } $trace = null; @@ -210,6 +218,11 @@ private function getErrorResponse(\Throwable $exception, Request $request): Resp $header_method = match (true) { // A minimal page is displayed as we do not have enough memory available to display the full page. $exception instanceof OutOfMemoryError => 'simpleHeader', + + // Only the error message should be shown if the DB is ot available or the config not loaded. + // Trying to display a full header is not possible. + !DBConnection::isDbAvailable() || !Config::isLegacyConfigurationLoaded() => 'nullHeader', + Session::getCurrentInterface() === 'central' => 'header', Session::getCurrentInterface() === 'helpdesk' => 'helpHeader', default => 'nullHeader', @@ -220,8 +233,8 @@ private function getErrorResponse(\Throwable $exception, Request $request): Resp [ 'header_method' => $header_method, 'page_title' => $title, - 'link_url' => Html::getBackUrl(), - 'link_text' => __('Return to previous page'), + 'link_url' => $link_url ?? Html::getBackUrl(), + 'link_text' => $link_text ?? __('Return to previous page'), ] + $error_block_params, new Response(status: $status_code) ); diff --git a/src/Glpi/Controller/IndexController.php b/src/Glpi/Controller/IndexController.php index 611d5988d01..8c3f3092986 100644 --- a/src/Glpi/Controller/IndexController.php +++ b/src/Glpi/Controller/IndexController.php @@ -85,45 +85,10 @@ public function __invoke(Request $request): Response private function call(): void { /** - * @var \DBmysql|null $DB * @var array $CFG_GLPI * @var array $PLUGIN_HOOKS */ - global $DB, $CFG_GLPI, $PLUGIN_HOOKS; - - // If config_db doesn't exist -> start installation - if (!file_exists(GLPI_CONFIG_DIR . "/config_db.php") || !class_exists('DB', false)) { - if (file_exists(GLPI_ROOT . '/install/install.php')) { - Html::redirect("install/install.php"); - } else { - Session::loadLanguage('', false); - // Prevent inclusion of debug information in footer, as they are based on vars that are not initialized here. - $_SESSION['glpi_use_mode'] = Session::NORMAL_MODE; - - // no translation - $title_text = 'GLPI seems to not be configured properly.'; - $missing_conf_text = sprintf('Database configuration file "%s" is missing.', GLPI_CONFIG_DIR . '/config_db.php'); - $hint_text = 'You have to either restart the install process, either restore this file.'; - - Html::nullHeader('Missing configuration'); - echo '
'; - echo '
'; - echo '
'; - echo '

' . $title_text . '

'; - echo '

'; - echo $missing_conf_text; - echo ' '; - echo $hint_text; - echo '

'; - echo '
'; - echo '
'; - echo '
'; - Html::nullFooter(); - return; - } - } - - Session::checkCookieSecureConfig(); + global $CFG_GLPI, $PLUGIN_HOOKS; $_SESSION["glpicookietest"] = 'testcookie'; diff --git a/src/Glpi/Controller/InstallController.php b/src/Glpi/Controller/InstallController.php index e5b2b02d926..0d99546e8a9 100644 --- a/src/Glpi/Controller/InstallController.php +++ b/src/Glpi/Controller/InstallController.php @@ -56,7 +56,7 @@ public function __construct( #[SecurityStrategy(Firewall::STRATEGY_NO_CHECK)] public function start_inserts(): Response { - if (!isset($_SESSION['is_installing'])) { + if (!isset($_SESSION['can_process_install'])) { throw new AccessDeniedHttpException(); } diff --git a/src/Glpi/Exception/Http/HttpExceptionInterface.php b/src/Glpi/Exception/Http/HttpExceptionInterface.php index 51b5236f1e2..92e167817ae 100644 --- a/src/Glpi/Exception/Http/HttpExceptionInterface.php +++ b/src/Glpi/Exception/Http/HttpExceptionInterface.php @@ -40,4 +40,14 @@ interface HttpExceptionInterface extends \Symfony\Component\HttpKernel\Exception * Get the message to display. */ public function getMessageToDisplay(): ?string; + + /** + * Get the specific link text. + */ + public function getLinkText(): ?string; + + /** + * Get the specific link URL. + */ + public function getLinkUrl(): ?string; } diff --git a/src/Glpi/Exception/Http/HttpExceptionTrait.php b/src/Glpi/Exception/Http/HttpExceptionTrait.php index b324b4d27af..6712921c548 100644 --- a/src/Glpi/Exception/Http/HttpExceptionTrait.php +++ b/src/Glpi/Exception/Http/HttpExceptionTrait.php @@ -38,6 +38,10 @@ trait HttpExceptionTrait { private ?string $message_to_display = null; + private ?string $link_text = null; + + private ?string $link_url = null; + /** * Get the message to display. */ @@ -53,4 +57,36 @@ public function setMessageToDisplay(?string $message): void { $this->message_to_display = $message; } + + /** + * Get the specific link text. + */ + public function getLinkText(): ?string + { + return $this->link_text; + } + + /** + * Define the specific link text. + */ + public function setLinkText(?string $text): void + { + $this->link_text = $text; + } + + /** + * Get the specific link URL. + */ + public function getLinkUrl(): ?string + { + return $this->link_url; + } + + /** + * Define the specific link URL. + */ + public function setLinkUrl(?string $url): void + { + $this->link_url = $url; + } } diff --git a/src/Glpi/Http/Listener/AccessErrorListener.php b/src/Glpi/Http/Listener/AccessErrorListener.php index 02624d94e5a..9340032cc86 100644 --- a/src/Glpi/Http/Listener/AccessErrorListener.php +++ b/src/Glpi/Http/Listener/AccessErrorListener.php @@ -45,12 +45,11 @@ use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Symfony\Component\HttpKernel\KernelEvents; -final class AccessErrorListener implements EventSubscriberInterface +final readonly class AccessErrorListener implements EventSubscriberInterface { public static function getSubscribedEvents(): array { return [ - // priority = 1 to be executed before the default Symfony listeners KernelEvents::EXCEPTION => ['onKernelException', 1], ]; } diff --git a/src/Glpi/Http/Listener/CheckDatabaseStatusListener.php b/src/Glpi/Http/Listener/CheckDatabaseStatusListener.php new file mode 100644 index 00000000000..541b566be10 --- /dev/null +++ b/src/Glpi/Http/Listener/CheckDatabaseStatusListener.php @@ -0,0 +1,109 @@ +. + * + * --------------------------------------------------------------------- + */ + +namespace Glpi\Http\Listener; + +use Config; +use DBmysql; +use Glpi\Http\RequestPoliciesTrait; +use Glpi\Kernel\ListenersPriority; +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +final class CheckDatabaseStatusListener implements EventSubscriberInterface +{ + use RequestPoliciesTrait; + + public function __construct( + #[Autowire('%kernel.project_dir%')] private readonly string $projectDir, + ) { + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::REQUEST => [ + ['onKernelRequest', ListenersPriority::REQUEST_LISTENERS_PRIORITIES[self::class]], + ], + ]; + } + + public function onKernelRequest(RequestEvent $event): void + { + /** @var ?DBmysql $DB */ + global $DB; + + if (!$event->isMainRequest()) { + return; + } + + if (!$this->shouldCheckDbStatus($event->getRequest())) { + return; + } + + if (!($DB instanceof DBmysql)) { + $exception = new \Glpi\Exception\Http\HttpException(500); + $exception->setMessageToDisplay( + __('The database configuration file is missing or is corrupted. You have to either restart the install process, or restore this file.') + ); + if (file_exists($this->projectDir . '/install/install.php')) { + $exception->setLinkText(__('Go to install page')); + $exception->setLinkUrl($event->getRequest()->getBasePath() . '/install/install.php'); + } + throw $exception; + } + + if (!$DB->connected) { + $exception = new \Glpi\Exception\Http\HttpException(500); + $exception->setMessageToDisplay( + __('The connection to the SQL server could not be established. Please check your configuration.') + ); + $exception->setLinkText(__('Try again')); + $exception->setLinkUrl($event->getRequest()->getRequestUri()); + throw $exception; + } + + if (!Config::isLegacyConfigurationLoaded()) { + $exception = new \Glpi\Exception\Http\HttpException(500); + $exception->setMessageToDisplay( + __('Unable to load the GLPI configuration from the database.') + ); + $exception->setLinkText(__('Try again')); + $exception->setLinkUrl($event->getRequest()->getRequestUri()); + throw $exception; + } + } +} diff --git a/src/Glpi/Http/Listener/LegacyAssetsListener.php b/src/Glpi/Http/Listener/LegacyAssetsListener.php index 54040b60229..6440bf20ac2 100644 --- a/src/Glpi/Http/Listener/LegacyAssetsListener.php +++ b/src/Glpi/Http/Listener/LegacyAssetsListener.php @@ -35,7 +35,7 @@ namespace Glpi\Http\Listener; use Glpi\Http\LegacyRouterTrait; -use Glpi\Http\ListenersPriority; +use Glpi\Kernel\ListenersPriority; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\BinaryFileResponse; @@ -59,7 +59,7 @@ public function __construct( public static function getSubscribedEvents(): array { return [ - KernelEvents::REQUEST => ['onKernelRequest', ListenersPriority::LEGACY_LISTENERS_PRIORITIES[self::class]], + KernelEvents::REQUEST => ['onKernelRequest', ListenersPriority::REQUEST_LISTENERS_PRIORITIES[self::class]], ]; } diff --git a/src/Glpi/Config/LegacyConfigProviderListener.php b/src/Glpi/Http/Listener/LegacyConfigProviderListener.php similarity index 91% rename from src/Glpi/Config/LegacyConfigProviderListener.php rename to src/Glpi/Http/Listener/LegacyConfigProviderListener.php index ba1452a3d35..f4900fc073e 100644 --- a/src/Glpi/Config/LegacyConfigProviderListener.php +++ b/src/Glpi/Http/Listener/LegacyConfigProviderListener.php @@ -32,9 +32,11 @@ * --------------------------------------------------------------------- */ -namespace Glpi\Config; +namespace Glpi\Http\Listener; -use Glpi\Http\ListenersPriority; +use Glpi\Config\ConfigProviderWithRequestInterface; +use Glpi\Config\LegacyConfigProviders; +use Glpi\Kernel\ListenersPriority; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\KernelEvents; @@ -50,7 +52,7 @@ public static function getSubscribedEvents(): array { return [ // Has to be executed before anything else! - KernelEvents::REQUEST => ['onKernelRequest', ListenersPriority::LEGACY_LISTENERS_PRIORITIES[self::class]], + KernelEvents::REQUEST => ['onKernelRequest', ListenersPriority::REQUEST_LISTENERS_PRIORITIES[self::class]], ]; } diff --git a/src/Glpi/Http/Listener/LegacyItemtypeRouteListener.php b/src/Glpi/Http/Listener/LegacyItemtypeRouteListener.php index 86b6d8416d6..d6c0284bfca 100644 --- a/src/Glpi/Http/Listener/LegacyItemtypeRouteListener.php +++ b/src/Glpi/Http/Listener/LegacyItemtypeRouteListener.php @@ -46,7 +46,7 @@ use Glpi\Controller\DropdownFormController; use Glpi\Dropdown\Dropdown; use Glpi\Dropdown\DropdownDefinition; -use Glpi\Http\ListenersPriority; +use Glpi\Kernel\ListenersPriority; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; @@ -62,7 +62,7 @@ public function __construct(private UrlMatcherInterface $url_matcher) public static function getSubscribedEvents(): array { return [ - KernelEvents::REQUEST => ['onKernelRequest', ListenersPriority::LEGACY_LISTENERS_PRIORITIES[self::class]], + KernelEvents::REQUEST => ['onKernelRequest', ListenersPriority::REQUEST_LISTENERS_PRIORITIES[self::class]], ]; } diff --git a/src/Glpi/Http/Listener/LegacyPostRequestActionsListener.php b/src/Glpi/Http/Listener/LegacyPostRequestActionsListener.php index cff43cdba17..228cba38a11 100644 --- a/src/Glpi/Http/Listener/LegacyPostRequestActionsListener.php +++ b/src/Glpi/Http/Listener/LegacyPostRequestActionsListener.php @@ -37,7 +37,7 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\KernelEvents; -class LegacyPostRequestActionsListener implements EventSubscriberInterface +final readonly class LegacyPostRequestActionsListener implements EventSubscriberInterface { public static function getSubscribedEvents(): array { diff --git a/src/Glpi/Http/Listener/LegacyRouterListener.php b/src/Glpi/Http/Listener/LegacyRouterListener.php index ffedd0e6f20..58deadd5ab9 100644 --- a/src/Glpi/Http/Listener/LegacyRouterListener.php +++ b/src/Glpi/Http/Listener/LegacyRouterListener.php @@ -36,7 +36,7 @@ use Glpi\Controller\LegacyFileLoadController; use Glpi\Http\LegacyRouterTrait; -use Glpi\Http\ListenersPriority; +use Glpi\Kernel\ListenersPriority; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\RequestEvent; @@ -57,7 +57,7 @@ public function __construct( public static function getSubscribedEvents(): array { return [ - KernelEvents::REQUEST => ['onKernelRequest', ListenersPriority::LEGACY_LISTENERS_PRIORITIES[self::class]], + KernelEvents::REQUEST => ['onKernelRequest', ListenersPriority::REQUEST_LISTENERS_PRIORITIES[self::class]], ]; } diff --git a/src/Glpi/Http/Listener/PluginsRouterListener.php b/src/Glpi/Http/Listener/PluginsRouterListener.php index 56e369d3a0c..d98d79a32ea 100644 --- a/src/Glpi/Http/Listener/PluginsRouterListener.php +++ b/src/Glpi/Http/Listener/PluginsRouterListener.php @@ -37,7 +37,7 @@ use Glpi\Controller\AbstractController; use Glpi\DependencyInjection\PluginContainer; use Glpi\DependencyInjection\PublicService; -use Glpi\Http\ListenersPriority; +use Glpi\Kernel\ListenersPriority; use Plugin; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\RequestEvent; @@ -46,19 +46,19 @@ use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Symfony\Component\Routing\Router; -class PluginsRouterListener implements EventSubscriberInterface +final readonly class PluginsRouterListener implements EventSubscriberInterface { public const ROUTE_NAME = 'glpi_plugin'; public function __construct( - private readonly PluginContainer $plugin_container, + private PluginContainer $plugin_container, ) { } public static function getSubscribedEvents(): array { return [ - KernelEvents::REQUEST => ['onKernelRequest', ListenersPriority::LEGACY_LISTENERS_PRIORITIES[self::class]], + KernelEvents::REQUEST => ['onKernelRequest', ListenersPriority::REQUEST_LISTENERS_PRIORITIES[self::class]], ]; } diff --git a/src/Glpi/Http/Listener/RedirectExceptionListener.php b/src/Glpi/Http/Listener/RedirectExceptionListener.php index 1cef7cc5af0..c9c358d4d6b 100644 --- a/src/Glpi/Http/Listener/RedirectExceptionListener.php +++ b/src/Glpi/Http/Listener/RedirectExceptionListener.php @@ -39,7 +39,7 @@ use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Symfony\Component\HttpKernel\KernelEvents; -final class RedirectExceptionListener implements EventSubscriberInterface +final readonly class RedirectExceptionListener implements EventSubscriberInterface { public static function getSubscribedEvents(): array { diff --git a/src/Glpi/Http/Listener/RedirectLegacyRouteListener.php b/src/Glpi/Http/Listener/RedirectLegacyRouteListener.php index 32620826600..08cd46764ea 100644 --- a/src/Glpi/Http/Listener/RedirectLegacyRouteListener.php +++ b/src/Glpi/Http/Listener/RedirectLegacyRouteListener.php @@ -34,7 +34,7 @@ namespace Glpi\Http\Listener; -use Glpi\Http\ListenersPriority; +use Glpi\Kernel\ListenersPriority; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\Event\RequestEvent; @@ -49,7 +49,7 @@ public static function getSubscribedEvents(): array { return [ - KernelEvents::REQUEST => ['onKernelRequest', ListenersPriority::LEGACY_LISTENERS_PRIORITIES[self::class]], + KernelEvents::REQUEST => ['onKernelRequest', ListenersPriority::REQUEST_LISTENERS_PRIORITIES[self::class]], ]; } diff --git a/src/Glpi/Http/Listener/SessionCheckCookieListener.php b/src/Glpi/Http/Listener/SessionCheckCookieListener.php new file mode 100644 index 00000000000..61ffcf6d726 --- /dev/null +++ b/src/Glpi/Http/Listener/SessionCheckCookieListener.php @@ -0,0 +1,74 @@ +. + * + * --------------------------------------------------------------------- + */ + +namespace Glpi\Http\Listener; + +use Glpi\Exception\Http\BadRequestHttpException; +use Glpi\Http\RequestPoliciesTrait; +use Glpi\Kernel\ListenersPriority; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +class SessionCheckCookieListener implements EventSubscriberInterface +{ + use RequestPoliciesTrait; + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::REQUEST => ['onKernelRequest', ListenersPriority::REQUEST_LISTENERS_PRIORITIES[self::class]], + ]; + } + + public function onKernelRequest(RequestEvent $event): void + { + if (!$event->isMainRequest()) { + return; + } + + if ($this->isFrontEndAssetEndpoint($event->getRequest())) { + return; + } + + // If session cookie is only available on a secure HTTPS context but request is made on an unsecured HTTP context, + // throw an exception + $cookie_secure = filter_var(ini_get('session.cookie_secure'), FILTER_VALIDATE_BOOLEAN); + if ($event->getRequest()->isSecure() === false && $cookie_secure === true) { + $exception = new BadRequestHttpException(); + $exception->setMessageToDisplay(__('The web server is configured to allow session cookies only on secured context (https). Therefore, you must access GLPI on a secured context to be able to use it.')); + throw $exception; + } + } +} diff --git a/src/Glpi/Http/ListenersPriority.php b/src/Glpi/Http/ListenersPriority.php deleted file mode 100644 index b2f99e4bf13..00000000000 --- a/src/Glpi/Http/ListenersPriority.php +++ /dev/null @@ -1,78 +0,0 @@ -. - * - * --------------------------------------------------------------------- - */ - -namespace Glpi\Http; - -use Glpi\Config\LegacyConfigProviderListener; -use Glpi\Http\Listener\LegacyAssetsListener; -use Glpi\Http\Listener\LegacyItemtypeRouteListener; -use Glpi\Http\Listener\LegacyRouterListener; -use Glpi\Http\Listener\PluginsRouterListener; -use Glpi\Http\Listener\RedirectLegacyRouteListener; - -final class ListenersPriority -{ - public const LEGACY_LISTENERS_PRIORITIES = [ - // Static assets must be served without executing anything else. - // Keep them on top priority. - LegacyAssetsListener::class => 500, - - LegacyRouterListener::class => 400, - - // Legacy URLs redirections does not require any complex logic. It can be done prior to - // GLPI config and plugins initialization. - RedirectLegacyRouteListener::class => 375, - - // Config providers may still expect some `$_SERVER` variables to be redefined. - // They must therefore be executed after the `LegacyRouterListener`. - LegacyConfigProviderListener::class => 350, - - // Plugins itemtypes requires plugins to be initialized, therefore config must be already set. - // Also, keep it after the `LegacyRouterListener` to not map to the generic controller if a - // legacy script exists for the requested URI. - LegacyItemtypeRouteListener::class => 300, - - // This listener allows matching plugins routes at runtime, - // that's why it's executed right after Symfony's Router, - // and also after GLPI's config is set. - // - // Symfony's Router priority is 32. - // @see \Symfony\Component\HttpKernel\EventListener\RouterListener::getSubscribedEvents() - PluginsRouterListener::class => 31, - ]; - - private function __construct() - { - } -} diff --git a/src/Glpi/Http/RequestPoliciesTrait.php b/src/Glpi/Http/RequestPoliciesTrait.php new file mode 100644 index 00000000000..c2c867876db --- /dev/null +++ b/src/Glpi/Http/RequestPoliciesTrait.php @@ -0,0 +1,86 @@ +. + * + * --------------------------------------------------------------------- + */ + +namespace Glpi\Http; + +use Symfony\Component\HttpFoundation\Request; + +trait RequestPoliciesTrait +{ + /** + * Indicates the requested resource is made on a front-end asset endpoint. + */ + protected function isFrontEndAssetEndpoint(Request $request): bool + { + $path = $request->getPathInfo(); + + return \str_starts_with($path, '/js/') + || \str_starts_with($path, '/front/css.php') + || \str_starts_with($path, '/front/locale.php'); + } + + /** + * Indicates the requested resource is made on the Symfony profiler resources. + */ + protected function isSymfonyProfilerEndpoint(Request $request): bool + { + $path = $request->getPathInfo(); + + return \str_starts_with($path, '/_profiler/') + || \str_starts_with($path, '/_wdt/'); + } + + /** + * Indicates whether the DB status should be checked for the given request. + */ + protected function shouldCheckDbStatus(Request $request): bool + { + $path = $request->getPathInfo(); + + if ($this->isFrontEndAssetEndpoint($request) || $this->isSymfonyProfilerEndpoint($request)) { + // These resources should always be available. + return false; + } + + if ( + \str_starts_with($path, '/install/') + || ($_SESSION['is_installing'] ?? false) + ) { + // DB status should never be checked when the requested endpoint is part of the install process. + return false; + } + + return true; + } +} diff --git a/src/Glpi/Kernel/Kernel.php b/src/Glpi/Kernel/Kernel.php index 99717ab5f3e..b8c8254d20b 100644 --- a/src/Glpi/Kernel/Kernel.php +++ b/src/Glpi/Kernel/Kernel.php @@ -36,9 +36,6 @@ use GLPI; use Glpi\Application\SystemConfigurator; -use Glpi\Config\ConfigProviderConsoleExclusiveInterface; -use Glpi\Config\ConfigProviderWithRequestInterface; -use Glpi\Config\LegacyConfigProviders; use Glpi\Http\Listener\PluginsRouterListener; use Symfony\Bundle\FrameworkBundle\FrameworkBundle; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; @@ -111,30 +108,14 @@ public function registerBundles(): iterable return $bundles; } - public function loadCommonGlobalConfig(): void + public function boot(): void { - $this->boot(); + $dispatch_postboot = !$this->booted; - /** @var LegacyConfigProviders $providers */ - $providers = $this->container->get(LegacyConfigProviders::class); - foreach ($providers->getProviders() as $provider) { - if ($provider instanceof ConfigProviderWithRequestInterface) { - continue; - } - $provider->execute(); - } - } + parent::boot(); - public function loadCliConsoleOnlyConfig(): void - { - $this->boot(); - - /** @var LegacyConfigProviders $providers */ - $providers = $this->container->get(LegacyConfigProviders::class); - foreach ($providers->getProviders() as $provider) { - if ($provider instanceof ConfigProviderConsoleExclusiveInterface) { - $provider->execute(); - } + if ($dispatch_postboot) { + $this->container->get('event_dispatcher')->dispatch(new PostBootEvent()); } } diff --git a/src/Glpi/Config/LegacyConfigurators/CustomObjectsAutoloader.php b/src/Glpi/Kernel/Listener/CustomObjectsAutoloaderRegistration.php similarity index 64% rename from src/Glpi/Config/LegacyConfigurators/CustomObjectsAutoloader.php rename to src/Glpi/Kernel/Listener/CustomObjectsAutoloaderRegistration.php index 6b2ac629e05..fd1afcf036d 100644 --- a/src/Glpi/Config/LegacyConfigurators/CustomObjectsAutoloader.php +++ b/src/Glpi/Kernel/Listener/CustomObjectsAutoloaderRegistration.php @@ -32,26 +32,38 @@ * --------------------------------------------------------------------- */ -namespace Glpi\Config\LegacyConfigurators; +namespace Glpi\Kernel\Listener; use DBConnection; use Glpi\Asset\AssetDefinitionManager; -use Glpi\Config\LegacyConfigProviderInterface; use Glpi\Debug\Profiler; use Glpi\Dropdown\DropdownDefinitionManager; +use Glpi\Kernel\ListenersPriority; +use Glpi\Kernel\PostBootEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Update; -final readonly class CustomObjectsAutoloader implements LegacyConfigProviderInterface +final readonly class CustomObjectsAutoloaderRegistration implements EventSubscriberInterface { - public function execute(): void + public static function getSubscribedEvents(): array { - if (isset($_SESSION['is_installing']) || !DBConnection::isDbAvailable()) { + return [ + PostBootEvent::class => ['onPostBoot', ListenersPriority::POST_BOOT_LISTENERS_PRIORITIES[self::class]], + ]; + } + + public function onPostBoot(): void + { + if (!DBConnection::isDbAvailable() || (!defined('SKIP_UPDATES') && !Update::isDbUpToDate())) { // Requires the database to be available. return; } - Profiler::getInstance()->start('CustomObjectsAutoloader::execute', Profiler::CATEGORY_BOOT); + Profiler::getInstance()->start('CustomObjectsAutoloaderRegistration::execute', Profiler::CATEGORY_BOOT); + AssetDefinitionManager::getInstance()->registerAutoload(); DropdownDefinitionManager::getInstance()->registerAutoload(); - Profiler::getInstance()->stop('CustomObjectsAutoloader::execute'); + + Profiler::getInstance()->stop('CustomObjectsAutoloaderRegistration::execute'); } } diff --git a/src/Glpi/Config/LegacyConfigurators/CustomObjectsBootstrap.php b/src/Glpi/Kernel/Listener/CustomObjectsBootstrap.php similarity index 73% rename from src/Glpi/Config/LegacyConfigurators/CustomObjectsBootstrap.php rename to src/Glpi/Kernel/Listener/CustomObjectsBootstrap.php index fda3f7144af..0c92e12f1a2 100644 --- a/src/Glpi/Config/LegacyConfigurators/CustomObjectsBootstrap.php +++ b/src/Glpi/Kernel/Listener/CustomObjectsBootstrap.php @@ -32,26 +32,38 @@ * --------------------------------------------------------------------- */ -namespace Glpi\Config\LegacyConfigurators; +namespace Glpi\Kernel\Listener; use DBConnection; use Glpi\Asset\AssetDefinitionManager; -use Glpi\Config\LegacyConfigProviderInterface; use Glpi\Debug\Profiler; use Glpi\Dropdown\DropdownDefinitionManager; +use Glpi\Kernel\ListenersPriority; +use Glpi\Kernel\PostBootEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Update; -final readonly class CustomObjectsBootstrap implements LegacyConfigProviderInterface +final readonly class CustomObjectsBootstrap implements EventSubscriberInterface { - public function execute(): void + public static function getSubscribedEvents(): array { - if (isset($_SESSION['is_installing']) || !DBConnection::isDbAvailable()) { + return [ + PostBootEvent::class => ['onPostBoot', ListenersPriority::POST_BOOT_LISTENERS_PRIORITIES[self::class]], + ]; + } + + public function onPostBoot(): void + { + if (!DBConnection::isDbAvailable() || (!defined('SKIP_UPDATES') && !Update::isDbUpToDate())) { // Requires the database to be available. return; } Profiler::getInstance()->start('CustomObjectsBootstrap::execute', Profiler::CATEGORY_BOOT); + AssetDefinitionManager::getInstance()->bootstrapDefinitions(); DropdownDefinitionManager::getInstance()->bootstrapDefinitions(); + Profiler::getInstance()->stop('CustomObjectsBootstrap::execute'); } } diff --git a/src/Glpi/Kernel/Listener/InitializeCache.php b/src/Glpi/Kernel/Listener/InitializeCache.php new file mode 100644 index 00000000000..d06934206ed --- /dev/null +++ b/src/Glpi/Kernel/Listener/InitializeCache.php @@ -0,0 +1,65 @@ +. + * + * --------------------------------------------------------------------- + */ + +namespace Glpi\Kernel\Listener; + +use Glpi\Cache\CacheManager; +use Glpi\Debug\Profiler; +use Glpi\Kernel\ListenersPriority; +use Glpi\Kernel\PostBootEvent; +use Psr\SimpleCache\CacheInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +final readonly class InitializeCache implements EventSubscriberInterface +{ + public static function getSubscribedEvents(): array + { + return [ + PostBootEvent::class => ['onPostBoot', ListenersPriority::POST_BOOT_LISTENERS_PRIORITIES[self::class]], + ]; + } + + public function onPostBoot(): void + { + /** @var CacheInterface|null $GLPI_CACHE */ + global $GLPI_CACHE; + + Profiler::getInstance()->start('InitializeCache::execute', Profiler::CATEGORY_BOOT); + + $cache_manager = new CacheManager(); + $GLPI_CACHE = $cache_manager->getCoreCacheInstance(); + + Profiler::getInstance()->stop('InitializeCache::execute'); + } +} diff --git a/src/Glpi/Kernel/Listener/InitializeDbConnection.php b/src/Glpi/Kernel/Listener/InitializeDbConnection.php new file mode 100644 index 00000000000..cb4aa58c8a0 --- /dev/null +++ b/src/Glpi/Kernel/Listener/InitializeDbConnection.php @@ -0,0 +1,66 @@ +. + * + * --------------------------------------------------------------------- + */ + +namespace Glpi\Kernel\Listener; + +use DBConnection; +use Glpi\Debug\Profiler; +use Glpi\Kernel\ListenersPriority; +use Glpi\Kernel\PostBootEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +final readonly class InitializeDbConnection implements EventSubscriberInterface +{ + public static function getSubscribedEvents(): array + { + return [ + PostBootEvent::class => ['onPostBoot', ListenersPriority::POST_BOOT_LISTENERS_PRIORITIES[self::class]], + ]; + } + + public function onPostBoot(): void + { + Profiler::getInstance()->start('InitializeDbConnection::execute', Profiler::CATEGORY_BOOT); + + if (file_exists(GLPI_CONFIG_DIR . '/config_db.php')) { + include_once(GLPI_CONFIG_DIR . '/config_db.php'); + + if (\class_exists('DB', false)) { + DBConnection::establishDBConnection(false, false); + } + } + + Profiler::getInstance()->stop('InitializeDbConnection::execute'); + } +} diff --git a/src/Glpi/Config/LegacyConfigurators/InitializePlugins.php b/src/Glpi/Kernel/Listener/InitializePlugins.php similarity index 71% rename from src/Glpi/Config/LegacyConfigurators/InitializePlugins.php rename to src/Glpi/Kernel/Listener/InitializePlugins.php index 74161aecd24..cea75168bd7 100644 --- a/src/Glpi/Config/LegacyConfigurators/InitializePlugins.php +++ b/src/Glpi/Kernel/Listener/InitializePlugins.php @@ -32,35 +32,44 @@ * --------------------------------------------------------------------- */ -namespace Glpi\Config\LegacyConfigurators; +namespace Glpi\Kernel\Listener; -use Glpi\Config\LegacyConfigProviderInterface; +use DBConnection; use Glpi\Debug\Profiler; use Glpi\DependencyInjection\PluginContainer; +use Glpi\Kernel\ListenersPriority; +use Glpi\Kernel\PostBootEvent; use Plugin; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Update; -final readonly class InitializePlugins implements LegacyConfigProviderInterface +final readonly class InitializePlugins implements EventSubscriberInterface { public function __construct(private PluginContainer $pluginContainer) { } - public function execute(): void + public static function getSubscribedEvents(): array { - /* - * On startup, register all plugins configured for use, - * except during the database install/update process. - */ - if (isset($_SESSION['is_installing']) || (!defined('SKIP_UPDATES') && !Update::isDbUpToDate())) { + return [ + PostBootEvent::class => ['onPostBoot', ListenersPriority::POST_BOOT_LISTENERS_PRIORITIES[self::class]], + ]; + } + + public function onPostBoot(): void + { + if (!DBConnection::isDbAvailable() || (!defined('SKIP_UPDATES') && !Update::isDbUpToDate())) { + // Requires the database to be available. return; } Profiler::getInstance()->start('InitializePlugins::execute', Profiler::CATEGORY_BOOT); + $plugin = new Plugin(); $plugin->init(true); $this->pluginContainer->initializeContainer(); + Profiler::getInstance()->stop('InitializePlugins::execute'); } } diff --git a/src/Glpi/Config/LegacyConfigurators/InitializeDbConnection.php b/src/Glpi/Kernel/Listener/LoadLanguage.php similarity index 61% rename from src/Glpi/Config/LegacyConfigurators/InitializeDbConnection.php rename to src/Glpi/Kernel/Listener/LoadLanguage.php index 09316e97d3b..553f717016a 100644 --- a/src/Glpi/Config/LegacyConfigurators/InitializeDbConnection.php +++ b/src/Glpi/Kernel/Listener/LoadLanguage.php @@ -32,29 +32,29 @@ * --------------------------------------------------------------------- */ -namespace Glpi\Config\LegacyConfigurators; +namespace Glpi\Kernel\Listener; -use DBConnection; -use Glpi\Config\LegacyConfigProviderInterface; +use Glpi\Debug\Profiler; +use Glpi\Kernel\ListenersPriority; +use Glpi\Kernel\PostBootEvent; +use Session; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; -final readonly class InitializeDbConnection implements LegacyConfigProviderInterface +final readonly class LoadLanguage implements EventSubscriberInterface { - public function execute(): void + public static function getSubscribedEvents(): array { - if (isset($_SESSION['is_installing'])) { - return; - } - - if (!file_exists(GLPI_CONFIG_DIR . '/config_db.php')) { - return; - } + return [ + PostBootEvent::class => ['onPostBoot', ListenersPriority::POST_BOOT_LISTENERS_PRIORITIES[self::class]], + ]; + } - include_once(GLPI_CONFIG_DIR . '/config_db.php'); + public function onPostBoot(): void + { + Profiler::getInstance()->start('LoadLanguage::execute', Profiler::CATEGORY_BOOT); - if (!\class_exists('DB', false)) { - return; - } + Session::loadLanguage(); - DBConnection::establishDBConnection(false, false); + Profiler::getInstance()->stop('LoadLanguage::execute'); } } diff --git a/src/Glpi/Config/LegacyConfigurators/LoadLegacyConfiguration.php b/src/Glpi/Kernel/Listener/LoadLegacyConfiguration.php similarity index 57% rename from src/Glpi/Config/LegacyConfigurators/LoadLegacyConfiguration.php rename to src/Glpi/Kernel/Listener/LoadLegacyConfiguration.php index d2a59f1caf3..55941d098fc 100644 --- a/src/Glpi/Config/LegacyConfigurators/LoadLegacyConfiguration.php +++ b/src/Glpi/Kernel/Listener/LoadLegacyConfiguration.php @@ -32,27 +32,41 @@ * --------------------------------------------------------------------- */ -namespace Glpi\Config\LegacyConfigurators; +namespace Glpi\Kernel\Listener; use Config; -use Glpi\Config\LegacyConfigProviderInterface; +use Glpi\Debug\Profiler; +use Glpi\Kernel\ListenersPriority; +use Glpi\Kernel\PostBootEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; -final readonly class LoadLegacyConfiguration implements LegacyConfigProviderInterface +final readonly class LoadLegacyConfiguration implements EventSubscriberInterface { - public function execute(): void + public static function getSubscribedEvents(): array + { + return [ + PostBootEvent::class => ['onPostBoot', ListenersPriority::POST_BOOT_LISTENERS_PRIORITIES[self::class]], + ]; + } + + public function onPostBoot(): void { /** * @var array $CFG_GLPI */ global $CFG_GLPI; - if (isset($_SESSION['is_installing'])) { - // Force `root_doc` value - $request = \Symfony\Component\HttpFoundation\Request::createFromGlobals(); - $CFG_GLPI['root_doc'] = $request->getBasePath(); - return; - } + Profiler::getInstance()->start('LoadLegacyConfiguration::execute', Profiler::CATEGORY_BOOT); Config::loadLegacyConfiguration(); + + // Copy the configuration defaults to the session + foreach ($CFG_GLPI['user_pref_field'] as $field) { + if (!isset($_SESSION["glpi$field"]) && isset($CFG_GLPI[$field])) { + $_SESSION["glpi$field"] = $CFG_GLPI[$field]; + } + } + + Profiler::getInstance()->stop('LoadLegacyConfiguration::execute'); } } diff --git a/src/Glpi/Config/LegacyConfigurators/ProfilerStart.php b/src/Glpi/Kernel/Listener/ProfilerStart.php similarity index 64% rename from src/Glpi/Config/LegacyConfigurators/ProfilerStart.php rename to src/Glpi/Kernel/Listener/ProfilerStart.php index 710589882e9..43da95394c7 100644 --- a/src/Glpi/Config/LegacyConfigurators/ProfilerStart.php +++ b/src/Glpi/Kernel/Listener/ProfilerStart.php @@ -32,19 +32,38 @@ * --------------------------------------------------------------------- */ -namespace Glpi\Config\LegacyConfigurators; +namespace Glpi\Kernel\Listener; -use Glpi\Config\LegacyConfigProviderInterface; +use Session; +use Glpi\Debug\Profile; use Glpi\Debug\Profiler; +use Glpi\Kernel\ListenersPriority; +use Glpi\Kernel\PostBootEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; -final readonly class ProfilerStart implements LegacyConfigProviderInterface +final readonly class ProfilerStart implements EventSubscriberInterface { - public function execute(): void + public static function getSubscribedEvents(): array + { + return [ + PostBootEvent::class => ['onPostBoot', ListenersPriority::POST_BOOT_LISTENERS_PRIORITIES[self::class]], + ]; + } + + public function onPostBoot(): void { if (isCommandLine()) { Profiler::getInstance()->disable(); } else { Profiler::getInstance()->start('php_request'); } + + if ( + isset($_SESSION['glpi_use_mode']) + && ($_SESSION['glpi_use_mode'] == Session::DEBUG_MODE) + ) { + // Start the debug profile + Profile::getCurrent(); + } } } diff --git a/src/Glpi/Config/LegacyConfigurators/SessionStart.php b/src/Glpi/Kernel/Listener/SessionStart.php similarity index 85% rename from src/Glpi/Config/LegacyConfigurators/SessionStart.php rename to src/Glpi/Kernel/Listener/SessionStart.php index 35d790114f2..a2f754b8db3 100644 --- a/src/Glpi/Config/LegacyConfigurators/SessionStart.php +++ b/src/Glpi/Kernel/Listener/SessionStart.php @@ -32,15 +32,24 @@ * --------------------------------------------------------------------- */ -namespace Glpi\Config\LegacyConfigurators; +namespace Glpi\Kernel\Listener; +use Glpi\Kernel\ListenersPriority; +use Glpi\Kernel\PostBootEvent; use Session; -use Glpi\Config\LegacyConfigProviderInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; -final class SessionStart implements LegacyConfigProviderInterface +final readonly class SessionStart implements EventSubscriberInterface { - public function execute(): void + public static function getSubscribedEvents(): array + { + return [ + PostBootEvent::class => ['onPostBoot', ListenersPriority::POST_BOOT_LISTENERS_PRIORITIES[self::class]], + ]; + } + + public function onPostBoot(): void { // The session must be started even in CLI context. // The GLPI code refers to the session in many places @@ -57,7 +66,7 @@ public function execute(): void if (\str_starts_with($path, '/api.php') || \str_starts_with($path, '/apirest.php')) { // API clients must not use cookies, as the session token is expected to be passed in headers. $use_cookies = false; - // The API endpoint is strating the session manually. + // The API endpoint is starting the session manually. $start_session = false; } elseif (\str_starts_with($path, '/caldav.php')) { // CalDAV clients must not use cookies, as the authentication is expected to be passed in headers. diff --git a/src/Glpi/Kernel/ListenersPriority.php b/src/Glpi/Kernel/ListenersPriority.php new file mode 100644 index 00000000000..2553a7c23b2 --- /dev/null +++ b/src/Glpi/Kernel/ListenersPriority.php @@ -0,0 +1,96 @@ +. + * + * --------------------------------------------------------------------- + */ + +namespace Glpi\Kernel; + +use Glpi\Http\Listener\LegacyConfigProviderListener; +use Glpi\Kernel\Listener as KernelListener; +use Glpi\Http\Listener as HttpListener; + +final class ListenersPriority +{ + public const POST_BOOT_LISTENERS_PRIORITIES = [ + KernelListener\SessionStart::class => 200, + KernelListener\ProfilerStart::class => 190, + KernelListener\InitializeDbConnection::class => 180, + KernelListener\InitializeCache::class => 170, + KernelListener\LoadLegacyConfiguration::class => 160, + KernelListener\CustomObjectsAutoloaderRegistration::class => 150, + KernelListener\InitializePlugins::class => 140, + KernelListener\CustomObjectsBootstrap::class => 130, + KernelListener\LoadLanguage::class => 120, + ]; + + public const REQUEST_LISTENERS_PRIORITIES = [ + // Static assets must be served without executing anything else. + // Keep them on top priority. + HttpListener\LegacyAssetsListener::class => 500, + + // This listener will ensure that the request is made on a secure context (HTTPS) when the + // cookies are available only on a secure context (`session.cookie_secure=on`). + // It must be executed before trying to serve any statefull endpoint. + HttpListener\SessionCheckCookieListener::class => 475, + + // This listener will ensure that the database connection is configured and available. + // It must be executed before executing any controller (except controllers related to front-end assets). + HttpListener\CheckDatabaseStatusListener::class => 450, + + // Legacy config providers. + // FIXME: Reorganize them and transform them into HTTP request listeners to register them here directly. + LegacyConfigProviderListener::class => 425, + + // Executes the legacy controller scripts (`/ajax/*.php` or `/front/*.php` scripts) whenever the + // requested URI matches an existing file. + HttpListener\LegacyRouterListener::class => 400, + + // Map legacy scripts URLS (e.g. `/front/computer.php`) to modern controllers. + // Must be executed after the `LegacyRouterListener` to ensure to use the legacy script if it exists. + HttpListener\LegacyItemtypeRouteListener::class => 375, + + // Legacy URLs redirections. + HttpListener\RedirectLegacyRouteListener::class => 350, + + // This listener allows matching plugins routes at runtime, + // that's why it's executed right after Symfony's Router, + // and also after GLPI's config is set. + // + // Symfony's Router priority is 32. + // @see \Symfony\Component\HttpKernel\EventListener\RouterListener::getSubscribedEvents() + HttpListener\PluginsRouterListener::class => 31, + ]; + + private function __construct() + { + } +} diff --git a/src/Glpi/Config/ConfigProviderConsoleExclusiveInterface.php b/src/Glpi/Kernel/PostBootEvent.php similarity index 93% rename from src/Glpi/Config/ConfigProviderConsoleExclusiveInterface.php rename to src/Glpi/Kernel/PostBootEvent.php index ffcf06822da..bc839e160ee 100644 --- a/src/Glpi/Config/ConfigProviderConsoleExclusiveInterface.php +++ b/src/Glpi/Kernel/PostBootEvent.php @@ -32,8 +32,8 @@ * --------------------------------------------------------------------- */ -namespace Glpi\Config; +namespace Glpi\Kernel; -interface ConfigProviderConsoleExclusiveInterface +class PostBootEvent { } diff --git a/src/Session.php b/src/Session.php index 7838f8ee49d..0db48d8ad4a 100644 --- a/src/Session.php +++ b/src/Session.php @@ -38,7 +38,6 @@ use Glpi\Controller\InventoryController; use Glpi\Event; use Glpi\Exception\Http\AccessDeniedHttpException; -use Glpi\Exception\Http\BadRequestHttpException; use Glpi\Exception\SessionExpiredException; use Glpi\Plugin\Hooks; use Glpi\Session\SessionInfo; @@ -268,6 +267,11 @@ public static function start() if (!isset($_SESSION['glpi_use_mode'])) { $_SESSION['glpi_use_mode'] = Session::NORMAL_MODE; } + + // Define default language + if (!isset($_SESSION['glpilanguage'])) { + $_SESSION['glpilanguage'] = Session::getPreferredLanguage(); + } } @@ -1004,23 +1008,6 @@ public static function redirectIfNotLoggedIn() } } - /** - * Check the `session.cookie_secure` configuration and throw an exception if the - * current request context is not allowed to use session cookies. - */ - public static function checkCookieSecureConfig(): void - { - // If session cookie is only available on a secure HTTPS context but request is made on an unsecured HTTP context, - // throw an exception - $cookie_secure = filter_var(ini_get('session.cookie_secure'), FILTER_VALIDATE_BOOLEAN); - $is_https_request = ($_SERVER['HTTPS'] ?? 'off') === 'on' || (int)($_SERVER['SERVER_PORT'] ?? null) == 443; - if ($is_https_request === false && $cookie_secure === true) { - $exception = new BadRequestHttpException(); - $exception->setMessageToDisplay(__('The web server is configured to allow session cookies only on secured context (https). Therefore, you must access GLPI on a secured context to be able to use it.')); - throw $exception; - } - } - /** * Global check of session to prevent PHP vulnerability * diff --git a/templates/error_block.html.twig b/templates/error_block.html.twig index 2d3a4eaa701..4a92cb39513 100644 --- a/templates/error_block.html.twig +++ b/templates/error_block.html.twig @@ -41,7 +41,7 @@ {{ message }} {% if link_url is not empty %}
- {{ link_text }} + {{ link_text }} {% endif %} diff --git a/tests/bin/test-updated-data b/tests/bin/test-updated-data index 12d70ea4e62..2b34913c018 100755 --- a/tests/bin/test-updated-data +++ b/tests/bin/test-updated-data @@ -37,7 +37,7 @@ require_once dirname(__DIR__, 2) . '/vendor/autoload.php'; $kernel = new \Glpi\Kernel\Kernel('testing'); -$kernel->loadCommonGlobalConfig(); +$kernel->boot(); $command = new \Glpi\Tests\Command\TestUpdatedDataCommand(); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 82b4d3a44bf..75e8ffd1620 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -48,17 +48,21 @@ define('TU_USER', '_test_user'); define('TU_PASS', 'PhpUnit_4'); -global $CFG_GLPI, $GLPI_CACHE; +global $GLPI_CACHE; require_once dirname(__DIR__) . '/vendor/autoload.php'; $kernel = new Kernel('testing'); -$kernel->loadCommonGlobalConfig(); +$kernel->boot(); if (!file_exists(GLPI_CONFIG_DIR . '/config_db.php')) { echo("\nConfiguration file for tests not found\n\nrun: php bin/console database:install --env=testing ...\n\n"); exit(1); } +if (!defined('SKIP_UPDATES') && !Update::isDbUpToDate()) { + echo 'The GLPI codebase has been updated. The update of the GLPI database is necessary.' . PHP_EOL; + exit(1); +} //init cache if (file_exists(GLPI_CONFIG_DIR . DIRECTORY_SEPARATOR . CacheManager::CONFIG_FILENAME)) { diff --git a/tests/cypress/e2e/form/question_types/dropdown.cy.js b/tests/cypress/e2e/form/question_types/dropdown.cy.js index 19730bf01b4..958a1656359 100644 --- a/tests/cypress/e2e/form/question_types/dropdown.cy.js +++ b/tests/cypress/e2e/form/question_types/dropdown.cy.js @@ -147,6 +147,10 @@ describe('Dropdown form question type', () => { // Click on the question cy.get("@question").click('top'); + // TODO: Investigate why this 'wait' is needed and fix it. + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(200); + // Check selected options and option labels checkSelectedOptions([2]); checkOptionLabels(["Option 1", "Option 2", "Option 3"]); diff --git a/tests/cypress/e2e/search/display_preferences.cy.js b/tests/cypress/e2e/search/display_preferences.cy.js index 1b6559579bc..b5c334a5488 100644 --- a/tests/cypress/e2e/search/display_preferences.cy.js +++ b/tests/cypress/e2e/search/display_preferences.cy.js @@ -30,7 +30,7 @@ * --------------------------------------------------------------------- */ -describe('Display preferences', {retries: {runMode: 5}}, () => { +describe('Display preferences', () => { before(() => { // Create at least one ticket as we will be displaying the ticket list // to validate that the right columns are displayed @@ -47,7 +47,7 @@ describe('Display preferences', {retries: {runMode: 5}}, () => { openDisplayPreferences(); // Add a new column to the global view - goToTab('Global View'); + goToTab('Global View', true); addDisplayPeference('Pending reason'); // Refresh page @@ -86,7 +86,7 @@ describe('Display preferences', {retries: {runMode: 5}}, () => { openDisplayPreferences(); // Add a new column to the global view - goToTab('Helpdesk View'); + goToTab('Helpdesk View', true); addDisplayPeference('Pending reason'); // Refresh page @@ -135,11 +135,23 @@ describe('Display preferences', {retries: {runMode: 5}}, () => { ; } - function goToTab(name) { + function goToTab(name, wait = false) { + if (wait) { + // When changing tabs for the first time, we must wait for the + // js hanlder to be loaded. + // Sadly, there is no easy way to determine this without waiting + // an arbitrary amount of time. + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(250); + } + + cy.intercept('GET', '/ajax/common.tabs.php?*').as('refresh_tab_request'); cy.get('@iframeBody').find('#tabspanel-select').select(name); + cy.wait('@refresh_tab_request'); } function addDisplayPeference(name) { + cy.intercept('POST', '/ajax/displaypreference.php').as('update_request'); cy.get('@iframeBody') .getDropdownByLabelText('Select an option to add') .click() @@ -152,15 +164,18 @@ describe('Display preferences', {retries: {runMode: 5}}, () => { .findByRole('button', {'name': 'Add'}) .click() ; + cy.wait('@update_request'); } function deletePreference(name) { + cy.intercept('POST', '/ajax/displaypreference.php').as('update_request'); cy.get('@iframeBody') .findByRole('list') .findByRole('option', {'name': name}) // Should be listitem instead of option, our DOM is wrong .findByRole('button', {'name': "Delete permanently"}) .click() ; + cy.wait('@update_request'); } function validateThatDisplayPreferenceExist(name) { diff --git a/tools/bin/check-twig-templates-syntax b/tools/bin/check-twig-templates-syntax index a127d0c6821..285ab0821f7 100755 --- a/tools/bin/check-twig-templates-syntax +++ b/tools/bin/check-twig-templates-syntax @@ -37,7 +37,7 @@ require_once dirname(__DIR__, 2) . '/vendor/autoload.php'; $kernel = new \Glpi\Kernel\Kernel('testing'); -$kernel->loadCliConsoleOnlyConfig(); +$kernel->boot(); $command = new \Glpi\Tools\Command\CheckTwigTemplatesSyntaxCommand(); diff --git a/tools/cachebench.php b/tools/cachebench.php index fa62847371d..69871efac86 100644 --- a/tools/cachebench.php +++ b/tools/cachebench.php @@ -41,7 +41,7 @@ require dirname(__DIR__) . '/vendor/autoload.php'; $kernel = new \Glpi\Kernel\Kernel(); -$kernel->loadCommonGlobalConfig(); +$kernel->boot(); define('PER_LEVEL', 8); define('COUNT', 1024); diff --git a/tools/fake_metrics.php b/tools/fake_metrics.php index 58ad9a60fb7..9e5f6ced6dd 100644 --- a/tools/fake_metrics.php +++ b/tools/fake_metrics.php @@ -41,7 +41,7 @@ require dirname(__DIR__) . '/vendor/autoload.php'; $kernel = new \Glpi\Kernel\Kernel(); -$kernel->loadCommonGlobalConfig(); +$kernel->boot(); $printers_id = false; $networkports_id = false; diff --git a/tools/fk_generate.php b/tools/fk_generate.php index 4f9c73cba9e..66bf52fbb05 100644 --- a/tools/fk_generate.php +++ b/tools/fk_generate.php @@ -41,7 +41,7 @@ require dirname(__DIR__) . '/vendor/autoload.php'; $kernel = new \Glpi\Kernel\Kernel(); -$kernel->loadCommonGlobalConfig(); +$kernel->boot(); $DB->query("SET FOREIGN_KEY_CHECKS = '0';"); $result = $DB->list_tables(); diff --git a/tools/getsearchoptions.php b/tools/getsearchoptions.php index 5e4bf273f1a..53ebcd4c218 100644 --- a/tools/getsearchoptions.php +++ b/tools/getsearchoptions.php @@ -43,7 +43,7 @@ require dirname(__DIR__) . '/vendor/autoload.php'; $kernel = new \Glpi\Kernel\Kernel(); -$kernel->loadCommonGlobalConfig(); +$kernel->boot(); if (isset($_SERVER['argv'])) { for ($i = 1; $i < $_SERVER['argc']; $i++) { diff --git a/tools/inventory.php b/tools/inventory.php index a81b48d43cf..52781c2c743 100644 --- a/tools/inventory.php +++ b/tools/inventory.php @@ -39,7 +39,7 @@ require dirname(__DIR__) . '/vendor/autoload.php'; $kernel = new Kernel('testing'); -$kernel->loadCommonGlobalConfig(); +$kernel->boot(); $conf = new Conf(); if ($conf->enabled_inventory != 1) { diff --git a/tools/testmail.php b/tools/testmail.php index 1e63cbc67ea..5a20f5e42ad 100644 --- a/tools/testmail.php +++ b/tools/testmail.php @@ -41,7 +41,7 @@ require dirname(__DIR__) . '/vendor/autoload.php'; $kernel = new \Glpi\Kernel\Kernel(); -$kernel->loadCommonGlobalConfig(); +$kernel->boot(); if (isset($_SERVER['argc'])) { for ($i = 1; $i < $_SERVER['argc']; $i++) { diff --git a/tools/update_registered_ids.php b/tools/update_registered_ids.php index b13d3d5ce95..6f5749d09cd 100644 --- a/tools/update_registered_ids.php +++ b/tools/update_registered_ids.php @@ -41,7 +41,7 @@ require dirname(__DIR__) . '/vendor/autoload.php'; $kernel = new \Glpi\Kernel\Kernel(); -$kernel->loadCommonGlobalConfig(); +$kernel->boot(); $registeredid = new RegisteredID(); $manufacturer = new Manufacturer();