2017-08-30 11:37:48 +02:00

605 lines
20 KiB
PHP

<?php
/**
* TNT OFFICIAL MODULE FOR PRESTASHOP.
*
* @author GFI Informatique <www.gfi.fr>
* @copyright 2016-2017 GFI Informatique, 2016-2017 TNT
* @license https://opensource.org/licenses/MIT MIT License
*/
require_once _PS_MODULE_DIR_.'tntofficiel/tntofficiel.php';
class TNTOfficiel_Debug
{
/**
* @var bool Is debugging enabled ?
*/
private static $boolEnabled = false;
/**
* @var array List of allowed client IP. No client IP means all allowed.
*/
private static $arrRemoteIPAddressAllowed = array();
/**
* @var bool Backtrace auto added.
*/
private static $boolBackTraceAuto = true;
/**
* @var int Backtrace maximum items.
*/
private static $intBackTraceMaxDeep = 1; //64;
/**
* @var bool Backtrace arguments detail at maximum.
*/
private static $boolBackTraceArgsDetail = false;
/**
* @var array
*/
private static $arrPHPErrorExclude = array(
E_WARNING => array(
'/^filemtime\(\): stat failed for/ui'
),
//E_NOTICE => array()
);
/**
* @var array PHP Errors constant name list.
*/
private static $arrPHPErrorNames = array(
'E_ERROR',
'E_RECOVERABLE_ERROR',
'E_WARNING',
'E_PARSE',
'E_NOTICE',
'E_STRICT',
'E_DEPRECATED', // PHP 5.3+
'E_CORE_ERROR',
'E_CORE_WARNING',
'E_COMPILE_ERROR',
'E_COMPILE_WARNING',
'E_USER_ERROR',
'E_USER_WARNING',
'E_USER_NOTICE',
'E_USER_DEPRECATED' // PHP 5.3+
);
/**
* @var array PHP Errors map
*/
private static $arrPHPErrorMap = null;
/**
* @var array JSON Errors constant name list. PHP 5.3.0+.
*/
private static $arrJSONErrorNames = array(
'JSON_ERROR_NONE',
'JSON_ERROR_DEPTH',
'JSON_ERROR_STATE_MISMATCH',
'JSON_ERROR_CTRL_CHAR',
'JSON_ERROR_SYNTAX',
'JSON_ERROR_UTF8', // PHP 5.3.3+
'JSON_ERROR_RECURSION', // PHP 5.5.0+
'JSON_ERROR_INF_OR_NAN', // PHP 5.5.0+
'JSON_ERROR_UNSUPPORTED_TYPE', // PHP 5.5.0+
);
/**
* @var array JSON Errors map. PHP 5.3.0+.
*/
private static $arrJSONErrorMap = null;
/**
* @var bool
*/
private static $boolHandlerRegistered = false;
/**
* @var string
*/
private static $strRoot = null;
/**
* @var string
*/
private static $strFileName = null;
/**
* Prevent Construct.
*/
final private function __construct()
{
trigger_error(sprintf('%s() %s is static.', __FUNCTION__, get_class($this)), E_USER_ERROR);
}
/**
* Check if client IP address allowed to use debug.
*
* @return bool
*/
public static function isClientIPAddressAllowed()
{
$strRemoteIPAddress = array_key_exists('REMOTE_ADDR', $_SERVER) ? $_SERVER['REMOTE_ADDR'] : null;
$boolIPAllowed = count(TNTOfficiel_Debug::$arrRemoteIPAddressAllowed) === 0
|| in_array($strRemoteIPAddress, TNTOfficiel_Debug::$arrRemoteIPAddressAllowed, true) === true;
return $boolIPAllowed;
}
/**
* Encode to JSON.
* @param $arrDebugInfo
* @return string
*/
public static function encJSON($arrDebugInfo)
{
$flagJSONEncode = 0;
$flagJSONEncode |= defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0;
$flagJSONEncode |= defined('JSON_UNESCAPED_UNICODE') ? JSON_UNESCAPED_UNICODE : 0;
$flagJSONEncode |= defined('JSON_UNESCAPED_SLASHES') ? JSON_UNESCAPED_SLASHES : 0;
// PHP < 5.3 return null if second parameter is used.
return $flagJSONEncode === 0 ? json_encode($arrDebugInfo) : json_encode($arrDebugInfo, $flagJSONEncode);
}
/**
* @param int $intArgType
* @return string
*/
public static function getPHPErrorType($intArgType)
{
// Generate constant name mapping.
if (TNTOfficiel_Debug::$arrPHPErrorMap === null) {
TNTOfficiel_Debug::$arrPHPErrorMap = array();
foreach (TNTOfficiel_Debug::$arrPHPErrorNames as $strPHPErrorTypeName) {
if (defined($strPHPErrorTypeName)) {
$intPHPErrorType = constant($strPHPErrorTypeName);
TNTOfficiel_Debug::$arrPHPErrorMap[ $intPHPErrorType ] = $strPHPErrorTypeName;
}
}
}
$strPHPErrorType = array_key_exists($intArgType, TNTOfficiel_Debug::$arrPHPErrorMap) ? TNTOfficiel_Debug::$arrPHPErrorMap[ $intArgType ] : (string)$intArgType;
return $strPHPErrorType;
}
/**
* @param int $intArgType
* @return string
*/
public static function getJSONErrorType($intArgType)
{
// Generate constant name mapping.
if (TNTOfficiel_Debug::$arrJSONErrorMap === null) {
TNTOfficiel_Debug::$arrJSONErrorMap = array();
foreach (TNTOfficiel_Debug::$arrJSONErrorNames as $strJSONErrorTypeName) {
if (defined($strJSONErrorTypeName)) {
$intJSONErrorType = constant($strJSONErrorTypeName);
TNTOfficiel_Debug::$arrJSONErrorMap[ $intJSONErrorType ] = $strJSONErrorTypeName;
}
}
}
$strJSONErrorType = array_key_exists($intArgType, TNTOfficiel_Debug::$arrJSONErrorMap) ? TNTOfficiel_Debug::$arrJSONErrorMap[ $intArgType ] : (string)$intArgType;
return $strJSONErrorType;
}
/**
* Capture an Error.
*
* @param int $intArgType
* @param string $strArgMessage
* @param string $strArgFile
* @param int $intArgLine
* @param bool $boolArgIsLast
* @return boolean
*/
public static function captureError($intArgType, $strArgMessage, $strArgFile = null, $intArgLine = 0, $arrArgContext = array(), $boolArgIsLast = false)
{
$arrLogError = array(
'type' => $boolArgIsLast ? 'LastError' : 'Error'
);
if ($strArgFile !== null) {
$arrLogError['file'] = $strArgFile;
$arrLogError['line'] = $intArgLine;
}
if ($boolArgIsLast) {
$arrLogError['trace'] = array();
}
$strType = TNTOfficiel_Debug::getPHPErrorType($intArgType);
$arrLogError['msg'] = 'Type '.$strType.': '.$strArgMessage;
if (array_key_exists($intArgType, TNTOfficiel_Debug::$arrPHPErrorExclude) && is_array(TNTOfficiel_Debug::$arrPHPErrorExclude[$intArgType])) {
if (count(TNTOfficiel_Debug::$arrPHPErrorExclude[$intArgType]) === 0) {
return false;
}
foreach (TNTOfficiel_Debug::$arrPHPErrorExclude[$intArgType] as $k => $r) {
if (preg_match($r, $strArgMessage) === 1) {
return false;
}
}
}
TNTOfficiel_Debug::log($arrLogError);
// Internal error handler continues (displays/log error, …)
return false;
}
/**
* Capture last Error.
* Useful at script end for E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE, …
*/
public static function captureLastError()
{
$arrPHPLastError = error_get_last();
if (is_array($arrPHPLastError)) {
TNTOfficiel_Debug::captureError(
$arrPHPLastError['type'],
$arrPHPLastError['message'],
$arrPHPLastError['file'],
$arrPHPLastError['line'],
array(),
true
);
}
// PHP 5.3.0+
if (function_exists('json_last_error')) {
$intTypeJSONLastError = json_last_error();
$strJSONLastErrorType = TNTOfficiel_Debug::getJSONErrorType($intTypeJSONLastError);
// PHP 5.5.0+
$strJSONLastErrorMessage = function_exists('json_last_error_msg') ? json_last_error_msg() : 'N/A';
TNTOfficiel_Debug::captureError($strJSONLastErrorType, $strJSONLastErrorMessage, null, 0, array(), true);
}
}
/**
* Capture an Exception.
*
* @param \Exception $objArgException
*/
public static function captureException($objArgException)
{
$arrLogException = array(
'type' => 'Exception'
);
if ($objArgException->getFile() !== null) {
$arrLogException['file'] = $objArgException->getFile();
$arrLogException['line'] = $objArgException->getLine();
}
$arrLogException['msg'] = 'Code '.$objArgException->getCode().': '.$objArgException->getMessage();
$arrLogException['trace'] = $objArgException->getTrace();
TNTOfficiel_Debug::log($arrLogException);
}
/**
* Capture connection status.
*/
public static function captureConnectionStatus()
{
// is non normal connection status.
$intStatus = connection_status();
// connection_aborted()
if ($intStatus & 1) {
TNTOfficiel_Debug::log(array(
'type' => 'Shutdown',
'msg' => sprintf('Connection was aborted by user.')
));
}
if ($intStatus & 2) {
TNTOfficiel_Debug::log(array(
'type' => 'Shutdown',
'msg' => sprintf('Script exceeded maximum execution time.')
));
}
}
/**
* Capture output buffer status.
*/
public static function captureOutPutBufferStatus()
{
$msg = 'Output was not sent yet.';
// is output buffer was sent
$strOutputBufferFile = null;
$intOutputBufferLine = null;
$boolOutputBufferSent = headers_sent($strOutputBufferFile, $intOutputBufferLine);
if ($boolOutputBufferSent) {
$msg = sprintf('Output was sent in \'%s\' on line %s.', $strOutputBufferFile, $intOutputBufferLine);
}
TNTOfficiel_Debug::log(array(
'type' => 'Shutdown',
'msg' => $msg,
'headers' => headers_list(),
'level' => ob_get_level(),
'trace' => array()
));
}
/**
* Capture at shutdown.
*/
public static function captureAtShutdown()
{
TNTOfficiel_Debug::captureLastError();
TNTOfficiel_Debug::captureConnectionStatus();
TNTOfficiel_Debug::captureOutPutBufferStatus();
TNTOfficiel_Debug::addLogContent(']');
}
/**
* Register capture handlers once.
*/
public static function registerHandlers()
{
if (TNTOfficiel_Debug::$boolHandlerRegistered !== true) {
TNTOfficiel_Debug::$boolHandlerRegistered = true;
set_error_handler(array('TNTOfficiel_Debug', 'captureError'));
set_exception_handler(array('TNTOfficiel_Debug', 'captureException'));
register_shutdown_function(array('TNTOfficiel_Debug', 'captureAtShutdown'));
}
}
/**
* Get the script start time.
*
* @return float
*/
public static function getStartTime()
{
return (float)(array_key_exists('REQUEST_TIME_FLOAT', $_SERVER) ? $_SERVER['REQUEST_TIME_FLOAT'] :
$_SERVER['REQUEST_TIME']);
}
/**
* Set log root directory.
*
* @param $strArgRoot
*/
public static function setRootDirectory($strArgRoot)
{
TNTOfficiel_Debug::$strRoot = null;
$strRealpath = realpath($strArgRoot);
// If not a writable directory.
if ($strRealpath === false || !is_dir($strRealpath) || !is_writable($strRealpath)) {
return false;
}
// Add final separator.
if (mb_substr($strRealpath, -1) !== DIRECTORY_SEPARATOR) {
$strRealpath .= DIRECTORY_SEPARATOR;
}
// Save.
TNTOfficiel_Debug::$strRoot = $strRealpath;
return true;
}
/**
* Get log filename.
*
* @return string
*/
public static function getFilename()
{
// If root directory is defined, but not the filename.
if (TNTOfficiel_Debug::$strRoot !== null && TNTOfficiel_Debug::$strFileName === null) {
$floatScriptTime = TNTOfficiel_Debug::getStartTime();
$strTimeStamp = var_export($floatScriptTime, true);
$strTimeStamp = preg_replace('/^([0-9]+(?:\.[0-9]{1,6}))[0-9]*$/ui', '$1', $strTimeStamp);
$strTimeStamp = preg_replace('/\./ui', '', $strTimeStamp);
$strTimeStamp = sprintf('%-016s',$strTimeStamp);
$strFileNameSuffix = $strTimeStamp.'_'.preg_replace('/[^a-z0-9_]+/ui', '-', parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
TNTOfficiel_Debug::$strFileName = TNTOfficiel_Debug::$strRoot.'debug_'.$strFileNameSuffix.'.json';
}
return TNTOfficiel_Debug::$strFileName;
}
/**
* Create log file if do not exist.
* Log global info at creation.
*
* @param string $strArgDebugInfo
* @return bool
*/
public static function addLogContent($strArgDebugInfo = '')
{
$strFileName = TNTOfficiel_Debug::getFilename();
if ($strFileName === null) {
return false;
}
// If file don't already exist.
if (!file_exists($strFileName)) {
$arrDebugInfo = array(
'type' => 'StartInfo',
'uri' => array_key_exists('REQUEST_URI', $_SERVER) ? $_SERVER['REQUEST_URI'] : null,
'referer' => array_key_exists('HTTP_REFERER', $_SERVER) ? $_SERVER['HTTP_REFERER'] : null,
'client' => array_key_exists('REMOTE_ADDR', $_SERVER) ? $_SERVER['REMOTE_ADDR'] : null,
'post' => $_POST,
'get' => $_GET,
'cookie' => $_COOKIE
);
$strDebugInfo = '['.TNTOfficiel_Debug::encJSON($arrDebugInfo).',';
$strArgDebugInfo = $strDebugInfo.$strArgDebugInfo;
}
file_put_contents($strFileName, $strArgDebugInfo, FILE_APPEND);
}
/**
* Append log info.
*
* @param null $arrArg
*/
public static function log($arrArg = null)
{
// If not enabled or client IP not allowed.
if (TNTOfficiel_Debug::$boolEnabled !== true || TNTOfficiel_Debug::isClientIPAddressAllowed() !== true) {
return;
}
// Set default path.
//TNTOfficiel_Debug::setRootDirectory(_PS_ROOT_DIR_.'/log/');
TNTOfficiel_Debug::setRootDirectory(_PS_MODULE_DIR_.TNTOfficiel::MODULE_NAME.'/'.TNTOfficiel::LOG_DIR);
// Register handlers if not.
TNTOfficiel_Debug::registerHandlers();
if (!is_array($arrArg)) {
$arrArg = array('raw' => $arrArg);
}
// If message, file and line exist, then concat.
if (array_key_exists('msg', $arrArg) && array_key_exists('file', $arrArg) && array_key_exists('line', $arrArg)) {
$arrArg['msg'] .= ' \''.$arrArg['file'].'\' on line '.$arrArg['line'];
unset($arrArg['file']);
unset($arrArg['line']);
}
// If no backtrace and auto backtrace set.
if (TNTOfficiel_Debug::$boolBackTraceAuto === true
&& (!array_key_exists('trace', $arrArg) || !is_array($arrArg['trace']))
) {
// Get current one.
$arrArg['trace'] = debug_backtrace();
// Remove trace from this current method.
array_shift($arrArg['trace']);
}
// Process backtrace.
if (array_key_exists('trace', $arrArg) && is_array($arrArg['trace'])) {
// Final backtrace.
$arrTraceStack = array();
// Get each trace, deeper first.
while ($arrTrace = array_shift($arrArg['trace'])) {
$intDeepIndex = count($arrTraceStack);
// Get stack with maximum items.
if ($intDeepIndex >= TNTOfficiel_Debug::$intBackTraceMaxDeep) {
break;
}
// function
if (array_key_exists('class', $arrTrace) && is_string($arrTrace['class'])) {
// Exclude this class.
if (in_array($arrTrace['class'], array(__CLASS__), true)) {
continue;
}
$arrTrace['function'] = $arrTrace['class'].$arrTrace['type'].$arrTrace['function'];
}
// file
if (array_key_exists('file', $arrTrace) && is_string($arrTrace['file'])) {
$arrTrace['file'] = '\''.$arrTrace['file'].'\' on line '.$arrTrace['line'];
} else {
$arrTrace['file'] = '[Internal]';
}
// arguments
$arrCallerArgs = array();
if (array_key_exists('args', $arrTrace) && is_array($arrTrace['args'])) {
foreach ($arrTrace['args'] as $k => $mxdTraceArg) {
if (is_scalar($mxdTraceArg) || $mxdTraceArg === null) {
$arrCallerArgs[ $k ] = $mxdTraceArg;
} else {
$arrCallerArgs[ $k ] = '('.gettype($mxdTraceArg).')';
if (is_object($mxdTraceArg)) {
$arrCallerArgs[ $k ] .= get_class($mxdTraceArg);
}
}
}
$arrTrace['function'] .= '('.count($arrCallerArgs).')';
}
unset($arrTrace['line']);
unset($arrTrace['class']);
unset($arrTrace['type']);
unset($arrTrace['object']);
if (TNTOfficiel_Debug::$boolBackTraceArgsDetail === true) {
// Detecting circular reference, etc ...
try {
serialize($arrTrace['args']);
} catch (Exception $e) {
$arrTrace['args'] = $arrCallerArgs;
}
$strArgsJSON = TNTOfficiel_Debug::encJSON($arrTrace['args']);
// If unable to encode.
if (mb_strlen($strArgsJSON) == 0) {
$arrTrace['args'] = $arrCallerArgs;
}
} else {
$arrTrace['args'] = $arrCallerArgs;
}
// If no arguments.
if (count($arrCallerArgs) === 0) {
// Remove key (no line output).
unset($arrTrace['args']);
}
// Add trace.
$arrTraceStack[ $intDeepIndex ] = $arrTrace;
}
// Save processed backtrace.
$arrArg['trace'] = $arrTraceStack;
// Remove backtrace key if empty.
if (count($arrArg['trace']) === 0) {
unset($arrArg['trace']);
}
}
// Append time and memory consumption.
$arrArg += array(
'time' => microtime(true) - TNTOfficiel_Debug::getStartTime(),
'mem' => memory_get_peak_usage() / 1024 / 1024,
);
// List of sorted selected key.
$arrKeyExistSort = array_intersect_key(array_flip(
array('time', 'mem', 'type', 'msg', 'file', 'line', 'trace', 'dump')
), $arrArg);
// List of unsorted key left.
$arrKeyUnExistUnSort = array_diff_key($arrArg, $arrKeyExistSort);
// Append unsorted list to sorted.
$arrArg = array_merge($arrKeyExistSort, $arrArg) + $arrKeyUnExistUnSort;
$strDebugInfo = TNTOfficiel_Debug::encJSON($arrArg).',';
$strDebugInfo = preg_replace('/}\s*,\s*{/ui', '},{', $strDebugInfo);
TNTOfficiel_Debug::addLogContent($strDebugInfo);
}
}