diff --git a/bootstrap.inc.php b/bootstrap.inc.php index ae2a8ae78..c88922bcb 100644 --- a/bootstrap.inc.php +++ b/bootstrap.inc.php @@ -50,18 +50,19 @@ if (function_exists('microtime')) { $fItopStarted = 1000 * time(); } -if (! isset($GLOBALS['bBypassAutoload']) || $GLOBALS['bBypassAutoload'] == false) -{ +if (!isset($GLOBALS['bBypassAutoload']) || $GLOBALS['bBypassAutoload'] == false) { require_once APPROOT.'/lib/autoload.php'; } +require_once APPROOT.'core/log.class.inc.php'; +DeprecatedCallsLog::Enable(); + // // Maintenance mode // // Use 'maintenance' parameter to bypass maintenance mode -if (!isset($bBypassMaintenance)) -{ +if (!isset($bBypassMaintenance)) { $bBypassMaintenance = isset($_REQUEST['maintenance']) ? boolval($_REQUEST['maintenance']) : false; } diff --git a/core/coreexception.class.inc.php b/core/coreexception.class.inc.php index 0eb78e1e5..2e7644a5e 100644 --- a/core/coreexception.class.inc.php +++ b/core/coreexception.class.inc.php @@ -3,4 +3,6 @@ * This file is only here for compatibility issues. Will be removed in iTop 3.1.0 (N°3664) * * @deprecated 3.0.0 N°3663 Exception classes were moved to `/application/exceptions`, use autoloader instead of require ! - */ \ No newline at end of file + */ +require_once '../approot.inc.php'; +DeprecatedCallsLog::NotifyDeprecatedFile('Classes were moved to /application/exceptions'); diff --git a/core/log.class.inc.php b/core/log.class.inc.php index f0577107e..c084d8b5c 100644 --- a/core/log.class.inc.php +++ b/core/log.class.inc.php @@ -535,30 +535,33 @@ class FileLog abstract class LogAPI { - const CHANNEL_DEFAULT = ''; + public const CHANNEL_DEFAULT = ''; - const LEVEL_ERROR = 'Error'; - const LEVEL_WARNING = 'Warning'; - const LEVEL_INFO = 'Info'; - const LEVEL_OK = 'Ok'; - const LEVEL_DEBUG = 'Debug'; - const LEVEL_TRACE = 'Trace'; + public const LEVEL_ERROR = 'Error'; + public const LEVEL_WARNING = 'Warning'; + public const LEVEL_INFO = 'Info'; + public const LEVEL_OK = 'Ok'; + public const LEVEL_DEBUG = 'Debug'; + public const LEVEL_TRACE = 'Trace'; /** - * @var string default log level, can be overrided - * @see GetMinLogLevel + * @var string default log level + * @used-by GetLevelDefault * @since 2.7.1 N°2977 */ - const LEVEL_DEFAULT = self::LEVEL_OK; + public const LEVEL_DEFAULT = self::LEVEL_OK; protected static $aLevelsPriority = array( - self::LEVEL_ERROR => 400, + self::LEVEL_ERROR => 400, self::LEVEL_WARNING => 300, - self::LEVEL_INFO => 200, - self::LEVEL_OK => 200, - self::LEVEL_DEBUG => 100, - self::LEVEL_TRACE => 50, + self::LEVEL_INFO => 200, + self::LEVEL_OK => 200, + self::LEVEL_DEBUG => 100, + self::LEVEL_TRACE => 50, ); + /** + * @var \Config attribute allowing to mock config in the tests + */ protected static $m_oMockMetaModelConfig = null; public static function Enable($sTargetFile) @@ -567,7 +570,7 @@ abstract class LogAPI static::$m_oFileLog = new FileLog($sTargetFile); } - public static function MockStaticObjects($oFileLog, $oMetaModelConfig=null) + public static function MockStaticObjects($oFileLog, $oMetaModelConfig = null) { static::$m_oFileLog = $oFileLog; static::$m_oMockMetaModelConfig = $oMetaModelConfig; @@ -603,85 +606,115 @@ abstract class LogAPI static::Log(self::LEVEL_TRACE, $sMessage, $sChannel, $aContext); } + /** + * @throws \ConfigException if log wrongly configured + */ public static function Log($sLevel, $sMessage, $sChannel = null, $aContext = array()) { - if (! static::$m_oFileLog) - { + if (!static::$m_oFileLog) { return; } - if (! isset(self::$aLevelsPriority[$sLevel])) - { + if (!isset(self::$aLevelsPriority[$sLevel])) { IssueLog::Error("invalid log level '{$sLevel}'"); + return; } - if (is_null($sChannel)) - { + if (is_null($sChannel)) { $sChannel = static::CHANNEL_DEFAULT; } - $sMinLogLevel = self::GetMinLogLevel($sChannel); - - if ($sMinLogLevel === false || $sMinLogLevel === 'false') - { + if (!static::IsLogLevelEnabled($sLevel, $sChannel)) { return; } - if (is_string($sMinLogLevel)) - { - if (! isset(self::$aLevelsPriority[$sMinLogLevel])) - { - throw new Exception("invalid configuration for log_level '{$sMinLogLevel}' is not within the list: ".implode(',', array_keys(self::$aLevelsPriority))); - } - elseif (self::$aLevelsPriority[$sLevel] < self::$aLevelsPriority[$sMinLogLevel]) - { - //priority too low regarding the conf, do not log this - return; - } - } static::$m_oFileLog->$sLevel($sMessage, $sChannel, $aContext); } /** - * @param $sChannel - * - * @return string one of the LEVEL_* const value - * @uses \LogAPI::LEVEL_DEFAULT + * @throws \ConfigException if log wrongly configured + * @uses GetMinLogLevel */ - private static function GetMinLogLevel($sChannel) + final public static function IsLogLevelEnabled(string $sLevel, string $sChannel): bool { - $oConfig = (static::$m_oMockMetaModelConfig !== null) ? static::$m_oMockMetaModelConfig : \MetaModel::GetConfig(); - if (!$oConfig instanceof Config) - { - return static::LEVEL_DEFAULT; + $sMinLogLevel = self::GetMinLogLevel($sChannel); + + if ($sMinLogLevel === false || $sMinLogLevel === 'false') { + return false; + } + if (!is_string($sMinLogLevel)) { + return false; + } + + if (!isset(self::$aLevelsPriority[$sMinLogLevel])) { + throw new ConfigException("invalid configuration for log_level '{$sMinLogLevel}' is not within the list: ".implode(',', array_keys(self::$aLevelsPriority))); + } elseif (self::$aLevelsPriority[$sLevel] < self::$aLevelsPriority[$sMinLogLevel]) { + return false; + } + + return true; + } + + /** + * @param string $sChannel + * + * @return string one of the LEVEL_* const value : the one configured it if exists, otherwise default log level for this channel + * Config can be done globally : `'log_level_min' => LogAPI::LEVEL_TRACE,` + * Or per channel : `'log_level_min' => ['InlineImage' => LogAPI::LEVEL_TRACE, 'UserRequest' => LogAPI::LEVEL_TRACE],` + * + * @uses \LogAPI::GetConfig() + * @uses \LogAPI::GetLevelDefault + */ + protected static function GetMinLogLevel($sChannel) + { + $oConfig = static::GetConfig(); + if (!$oConfig instanceof Config) { + return static::GetLevelDefault(); } $sLogLevelMin = $oConfig->Get('log_level_min'); - if (empty($sLogLevelMin)) - { - return static::LEVEL_DEFAULT; + if (empty($sLogLevelMin)) { + return static::GetLevelDefault(); } - if (!is_array($sLogLevelMin)) - { + if (!is_array($sLogLevelMin)) { return $sLogLevelMin; } - if (isset($sLogLevelMin[$sChannel])) - { + if (isset($sLogLevelMin[$sChannel])) { return $sLogLevelMin[$sChannel]; } - if (isset($sLogLevelMin[static::CHANNEL_DEFAULT])) - { + if (isset($sLogLevelMin[static::CHANNEL_DEFAULT])) { return $sLogLevelMin[$sChannel]; } - return static::LEVEL_DEFAULT; + return static::GetLevelDefault(); } + /** + * @uses m_oMockMetaModelConfig if defined + * @uses \MetaModel::GetConfig() + */ + protected static function GetConfig(): ?Config + { + return static::$m_oMockMetaModelConfig ?? \MetaModel::GetConfig(); + } + + /** + * A method to override if default log level needs to be computed. Otherwise simply override the {@see LEVEL_DEFAULT} constant + * + * @used-by GetMinLogLevel + * @uses \LogAPI::LEVEL_DEFAULT + * + * @since 3.0.0 N°3731 + */ + protected static function GetLevelDefault(): string + { + return static::LEVEL_DEFAULT; + } } class SetupLog extends LogAPI @@ -765,10 +798,113 @@ class DeadLockLog extends LogAPI } +/** + * @since 3.0.0 N°3731 + */ +class DeprecatedCallsLog extends LogAPI +{ + public const ENUM_CHANNEL_PHP_METHOD = 'deprecated-php-method'; + public const ENUM_CHANNEL_FILE = 'deprecated-file'; + public const CHANNEL_DEFAULT = self::ENUM_CHANNEL_PHP_METHOD; + + public const LEVEL_DEFAULT = self::LEVEL_ERROR; + + /** @var \FileLog we want our own instance ! */ + protected static $m_oFileLog = null; + + public static function Enable($sTargetFile = null): void + { + if (empty($sTargetFile)) { + $sTargetFile = APPROOT.'log/deprecated-calls.log'; + } + parent::Enable($sTargetFile); + } + + protected static function GetLevelDefault(): string + { + if (utils::IsDevelopmentEnvironment()) { + return static::LEVEL_DEBUG; + } + + return static::LEVEL_DEFAULT; + } + + /** + * @throws \ConfigException + * @link https://www.php.net/debug_backtrace + * @uses \debug_backtrace() + */ + public static function NotifyDeprecatedFile(?string $sAdditionalMessage = null): void + { + if (!static::IsLogLevelEnabled(self::LEVEL_WARNING, self::ENUM_CHANNEL_FILE)) { + return; + } + + $aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + $sDeprecatedFile = $aStack[0]['file']; + if (array_key_exists(1, $aStack)) { + $sCallerFile = $aStack[1]['file']; + $sCallerLine = $aStack[1]['line']; + } else { + $sCallerFile = 'N/A'; + $sCallerLine = 'N/A'; + } + + $sMessage = "{$sCallerFile} L{$sCallerLine} including/requiring {$sDeprecatedFile}"; + + if (!is_null($sAdditionalMessage)) { + $sMessage .= ' : '.$sAdditionalMessage; + } + + static::Warning($sMessage, static::ENUM_CHANNEL_FILE); + } + + /** + * @param string|null $sAdditionalMessage + * + * @throws \ConfigException + * @link https://www.php.net/debug_backtrace + * @uses \debug_backtrace() + */ + public static function NotifyDeprecatedPhpMethod(?string $sAdditionalMessage = null): void + { + if (!static::IsLogLevelEnabled(self::LEVEL_WARNING, self::ENUM_CHANNEL_PHP_METHOD)) { + return; + } + + $aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3); + $sCallerObject = $aStack[2]['class']; + $sCallerMethod = $aStack[2]['function']; + $sCallerLine = $aStack[2]['line']; + + $sDeprecatedObject = $aStack[1]['class']; + $sDeprecatedMethod = $aStack[1]['function']; + + $sMessage = "{$sCallerObject}::{$sCallerMethod} L{$sCallerLine} calling {$sDeprecatedObject}:{$sDeprecatedMethod}"; + + if (!is_null($sAdditionalMessage)) { + $sMessage .= ' : '.$sAdditionalMessage; + } + + static::Warning($sMessage, static::ENUM_CHANNEL_PHP_METHOD); + } + + public static function Log($sLevel, $sMessage, $sChannel = null, $aContext = array()): void + { + if (utils::IsDevelopmentEnvironment()) { + trigger_error($sMessage, E_USER_DEPRECATED); + } + + parent::Log($sLevel, $sMessage, $sChannel, $aContext); + } +} + + class LogFileRotationProcess implements iScheduledProcess { /** * Cannot get this list from anywhere as log file name is provided by the caller using LogAPI::Enable + * * @var string[] */ const LOGFILES_TO_ROTATE = array( diff --git a/sources/application/WebPage/WebPage.php b/sources/application/WebPage/WebPage.php index 7576ff1e4..1dc7e20d1 100644 --- a/sources/application/WebPage/WebPage.php +++ b/sources/application/WebPage/WebPage.php @@ -1427,6 +1427,7 @@ class WebPage implements Page */ protected function outputCollapsibleSectionInit() { + DeprecatedCallsLog::NotifyDeprecatedPhpMethod(); if (!$this->bHasCollapsibleSection) { return; }