605 lines
20 KiB
PHP
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);
|
|
}
|
|
}
|