Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
48.72% |
57 / 117 |
|
13.33% |
2 / 15 |
CRAP | |
0.00% |
0 / 1 |
| iaErrorReporter | |
48.28% |
56 / 116 |
|
13.33% |
2 / 15 |
540.71 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| process | |
89.19% |
33 / 37 |
|
0.00% |
0 / 1 |
16.32 | |||
| displayErrors | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
| errorException | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
| errorManual | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
3.14 | |||
| dbtrace | |
66.67% |
4 / 6 |
|
0.00% |
0 / 1 |
3.33 | |||
| errorSql | |
25.00% |
2 / 8 |
|
0.00% |
0 / 1 |
15.55 | |||
| errorPhp | |
50.00% |
3 / 6 |
|
0.00% |
0 / 1 |
2.50 | |||
| errorJson | |
42.86% |
3 / 7 |
|
0.00% |
0 / 1 |
6.99 | |||
| errorPreg | |
33.33% |
2 / 6 |
|
0.00% |
0 / 1 |
5.67 | |||
| errorXml | |
42.86% |
3 / 7 |
|
0.00% |
0 / 1 |
4.68 | |||
| logErrorAdd | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
| sqlNormalized | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
| constant2name | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
56 | |||
| strim | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
12 | |||
| 1 | <?php |
| 2 | require_once(__DIR__ . '/iaErrorReporterDisplay.php'); |
| 3 | /** |
| 4 | * iaErrorReporter, |
| 5 | * |
| 6 | * |
| 7 | * @example |
| 8 | * $errRep = new iaErrorReporter($db, $mailErrorsTo); |
| 9 | * $errRep->process(true); // at bottom of script, stores and emails new errors. true also displays, false no display |
| 10 | * |
| 11 | * @version 1.1.2 |
| 12 | * |
| 13 | */ |
| 14 | class iaErrorReporter { |
| 15 | |
| 16 | |
| 17 | /** @var object db class */ |
| 18 | protected $db; |
| 19 | /** @var array error logged */ |
| 20 | protected $logError = []; |
| 21 | public $ReportErrorsHtml = ""; |
| 22 | public $ReportCount = 0; |
| 23 | public $ReportTraceSql = array(); |
| 24 | |
| 25 | /** |
| 26 | * __construct() |
| 27 | * |
| 28 | * @param object $db |
| 29 | * @return void |
| 30 | */ |
| 31 | public function __construct($db) { |
| 32 | $this->logError = []; |
| 33 | $this->db = $db; |
| 34 | } |
| 35 | |
| 36 | /** |
| 37 | * Registers last errors, if any, emails new errors & optionally displays them |
| 38 | * |
| 39 | * @param bool $display on true echos error |
| 40 | * @return void |
| 41 | * |
| 42 | * @see pageDisplayErrors pageDisplayErrors |
| 43 | * @see emailSend emailSend |
| 44 | */ |
| 45 | public function process($display=false) { |
| 46 | $this->errorPhp(); |
| 47 | $this->errorJson(); |
| 48 | $this->errorPreg(); |
| 49 | $this->errorXml(); |
| 50 | $this->errorSql(); |
| 51 | $count = array('newbug'=>0,'Fixed'=>0,'Bug'=>0,'Wont Fix'=>0,'?'=>0); |
| 52 | $html = ''; |
| 53 | $username = isset($_SESSION['usuario']) ? $_SESSION['usuario'] : '¿?'; |
| 54 | $webRoot = str_replace("\\", '/', $_SERVER['DOCUMENT_ROOT']); |
| 55 | foreach($this->logError as $error) { |
| 56 | $error['file'] = str_replace($webRoot, '', str_replace("\\", '/', $error['file']) ); |
| 57 | $icac_error_id = $error['error_type'] === 'Sql' || $error['error_type'] === 'json' || $error['error_type'] === 'pcre' ? |
| 58 | md5($error['error_type'].$error['message']) : |
| 59 | md5($error['file'].$error['line'].$error['error_type'].$error['coded']); |
| 60 | try { |
| 61 | if($this->db != null) { |
| 62 | $sql = "SELECT /*".__METHOD__."*/ `status` FROM icac_error WHERE icac_error_id=".strit($icac_error_id); |
| 63 | $error_status = $this->db->single_read($sql,'newbug'); |
| 64 | } else { |
| 65 | $error_status='newbug'; |
| 66 | } |
| 67 | if(empty($error_status)) { |
| 68 | $error_status='newbug'; |
| 69 | } |
| 70 | if(array_key_exists($error_status,$count)) { |
| 71 | $count[$error_status]++; |
| 72 | } |
| 73 | $html .= "<li style='padding-bottom:0.4em;'>".ia_htmlentities($error['message']). |
| 74 | (!empty($error['Sql']) ? " <br />".ia_htmlentities($error['Sql']) : '' ).(!empty($error['retries']) ? " ".ia_htmlentities($error['retries']) : '' ). |
| 75 | "<br />".ia_htmlentities($error['file'])." Line: ".$error['line']." Type: ".ia_htmlentities($error['error_type']); |
| 76 | // log error in db |
| 77 | if($this->db != null) { |
| 78 | $sql = "INSERT /*".__METHOD__."*/ INTO icac_error(icac_error_id,usuario,file,line,error_type,coded,error_message,first_seen,last_seen,seen_times) VALUES(" |
| 79 | .stritc($icac_error_id).stritc($username).stritc($error['file']).stritc($error['line']).stritc($error['error_type']).stritc($error['coded']). |
| 80 | stritc($error['message'].(empty($error['Sql']) ? '' : "\r\n".$error['Sql'] )) |
| 81 | ."NOW(),NOW(),1 )" |
| 82 | ." ON DUPLICATE KEY UPDATE file=VALUES(file), usuario=VALUES(usuario), last_seen=NOW(),seen_times=seen_times+1," |
| 83 | ."error_message=VALUES(error_message), `status`=IF(`status`='Fixed','Bug',`status`)"; |
| 84 | if(!$this->db->query($sql)) { |
| 85 | ; // echo "<pre style='top:40em;color:red'>register errors sql error: $sql</pre>"; |
| 86 | } |
| 87 | } |
| 88 | } catch(\Exception $err) { |
| 89 | // DO NOTHING |
| 90 | } |
| 91 | } |
| 92 | $this->ReportErrorsHtml = $html; |
| 93 | $this->ReportCount = $count; |
| 94 | $this->ReportTraceSql = $this->dbtrace(); |
| 95 | $output = new iaErrorReporterDisplay(); |
| 96 | global $gConfig; |
| 97 | $output->reportErrors($display, isset($gConfig['mailErrorsTo']) ? $gConfig['mailErrorsTo'] : null, $html, $count, $this->ReportTraceSql); |
| 98 | $this->logError = []; // por si lo llaman varias veces |
| 99 | } |
| 100 | |
| 101 | public function displayErrors() { |
| 102 | if(!isset($this->ReportCount)) |
| 103 | return false; |
| 104 | $output = new iaErrorReporterDisplay(); |
| 105 | $output->reportErrors(true, null, $this->ReportErrorsHtml, $this->ReportCount, $this->ReportTraceSql); |
| 106 | return strlen($this->ReportErrorsHtml) > 0; |
| 107 | } |
| 108 | ////////////////////// |
| 109 | // Manually log an error |
| 110 | ////////////////////// |
| 111 | |
| 112 | /** |
| 113 | * Register an exception in the error log |
| 114 | * |
| 115 | * @param object $exception an exception |
| 116 | * @param string $error_type |
| 117 | * @return void |
| 118 | */ |
| 119 | public function errorException($exception,$error_type='exception') { |
| 120 | try { |
| 121 | $this->logErrorAdd(array( |
| 122 | 'file' => $exception->getFile(), |
| 123 | 'line' => $exception->getLine(), |
| 124 | 'error_type' => $error_type, |
| 125 | 'coded'=>md5($exception->getFile().$exception->getLine().$error_type.$exception->getMessage()), |
| 126 | 'message' => $exception->getMessage(), |
| 127 | ) |
| 128 | ); |
| 129 | } catch(\Exception $e) { |
| 130 | // just in case, defensive programming |
| 131 | $this->logErrorAdd(array( |
| 132 | 'file' => $_SERVER['SCRIPT_NAME'], |
| 133 | 'line' => 0, |
| 134 | 'error_type' => 'exception', |
| 135 | 'coded'=>'', |
| 136 | 'message' => "Registering exception trhough IacErrorReporter::errorException", |
| 137 | ) |
| 138 | ); |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | |
| 143 | /** |
| 144 | * iaErrorReporter::errorManual() |
| 145 | * |
| 146 | * @param string $message |
| 147 | * @param string $file |
| 148 | * @param string $error_type |
| 149 | * @param integer $line |
| 150 | * @param string $md5 |
| 151 | * @return void |
| 152 | */ |
| 153 | public function errorManual($message, $file, $error_type='Manual', $line=0, $md5='') { |
| 154 | if(empty($file)) { |
| 155 | $file = $_SERVER['SCRIPT_NAME']; |
| 156 | } |
| 157 | $this->logErrorAdd( array( |
| 158 | 'file' => $file, |
| 159 | 'line' => $line, |
| 160 | 'error_type' => $error_type, |
| 161 | 'coded' => empty($md5) ? md5($message.$file.$line.$error_type) : $md5, |
| 162 | 'message' => $message, |
| 163 | ) |
| 164 | ); |
| 165 | } |
| 166 | |
| 167 | ////////////////////// |
| 168 | // Manually log an error |
| 169 | ////////////////////// |
| 170 | |
| 171 | /** |
| 172 | * report Sql trace |
| 173 | * |
| 174 | * @return |
| 175 | */ |
| 176 | protected function dbtrace() { |
| 177 | if($this->db === null) { |
| 178 | return ''; |
| 179 | } |
| 180 | $log = $this->db->trace_get(); |
| 181 | if(empty($log)) { |
| 182 | return ""; |
| 183 | } |
| 184 | return "<ol><li>" . implode("<li>",$log)."</ol>"; |
| 185 | } |
| 186 | |
| 187 | /** |
| 188 | * Register Sql error log |
| 189 | * |
| 190 | * @return void |
| 191 | */ |
| 192 | protected function errorSql() { |
| 193 | if($this->db === null) { |
| 194 | return; |
| 195 | } |
| 196 | foreach($this->db->errorLog_get() as $error) { |
| 197 | $sqlNormalized = self::sqlNormalized($error['sql']); |
| 198 | $this->logErrorAdd( array( |
| 199 | 'file' => empty($_SERVER['SCRIPT_NAME']) ? 'Sql' : $_SERVER['SCRIPT_NAME'], |
| 200 | 'line' => 1, |
| 201 | 'error_type' => 'Sql', |
| 202 | 'coded' => md5($sqlNormalized), |
| 203 | 'Sql' => $sqlNormalized, |
| 204 | 'sqlNormalized' => $sqlNormalized, |
| 205 | 'message' => (empty($error['errno']) ? '' : $error['errno'].': ').$error['error'], |
| 206 | ) |
| 207 | ); |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | /** |
| 212 | * Register last php error |
| 213 | * |
| 214 | * @return void |
| 215 | */ |
| 216 | protected function errorPhp() { |
| 217 | $error = error_get_last(); |
| 218 | if(empty($error)) { |
| 219 | return; |
| 220 | } |
| 221 | $error['error_type'] = 'php'; |
| 222 | $error['coded'] = md5( $error['file'].$error['line'].$error['message'].'php'); |
| 223 | $this->logErrorAdd($error); |
| 224 | } |
| 225 | |
| 226 | |
| 227 | /** |
| 228 | * Register last json error |
| 229 | * |
| 230 | * @return void |
| 231 | */ |
| 232 | protected function errorJson() { |
| 233 | if(!function_exists('json_last_error')) { |
| 234 | return; |
| 235 | } |
| 236 | $error = json_last_error(); |
| 237 | if($error === JSON_ERROR_NONE) { |
| 238 | return; |
| 239 | } |
| 240 | $message = function_exists('json_last_error_msg') ? json_last_error_msg() : self::constant2name('json',$error); |
| 241 | $this->logErrorAdd( array( |
| 242 | 'file' => $_SERVER['SCRIPT_NAME'], |
| 243 | 'line' => 1, |
| 244 | 'error_type' => 'json', |
| 245 | 'coded' => md5($_SERVER['SCRIPT_NAME'].'json'.$message), |
| 246 | 'message' => $message |
| 247 | ) |
| 248 | ); |
| 249 | } |
| 250 | |
| 251 | /** |
| 252 | * Register last regexp error |
| 253 | * |
| 254 | * @return void |
| 255 | */ |
| 256 | protected function errorPreg() { |
| 257 | if(!function_exists('preg_last_error') || ($error=preg_last_error()) === PREG_NO_ERROR ) { |
| 258 | return null; |
| 259 | } |
| 260 | $this->logErrorAdd( array( |
| 261 | 'file' => $_SERVER['SCRIPT_NAME'], |
| 262 | 'line' => 1, |
| 263 | 'error_type' => 'pcre', |
| 264 | 'coded' => md5($_SERVER['SCRIPT_NAME'].$error.'pcre'), |
| 265 | 'message' => self::constant2name('pcre',$error) |
| 266 | ) |
| 267 | ); |
| 268 | } |
| 269 | |
| 270 | /** |
| 271 | * Register last xml error |
| 272 | * |
| 273 | * @return void |
| 274 | */ |
| 275 | protected function errorXml() { |
| 276 | if(!function_exists('libxml_get_last_error')) { |
| 277 | return; |
| 278 | } |
| 279 | $error = libxml_get_last_error(); |
| 280 | if($error == false) { |
| 281 | return; |
| 282 | } |
| 283 | $this->logErrorAdd( array( |
| 284 | 'file' => $_SERVER['SCRIPT_NAME'], |
| 285 | 'line' => 1, |
| 286 | 'error_type' => 'libxml', |
| 287 | 'coded' => md5($error->file."xml".$error->code), |
| 288 | 'message' => "Error code: ".$error->code.", Level: ".$error->level.' File: '.$error->file.", Line: ".$error->line.", Column: ".$error->column |
| 289 | ) |
| 290 | ); |
| 291 | } |
| 292 | |
| 293 | ////////////////////// |
| 294 | // Auxiliares |
| 295 | ////////////////////// |
| 296 | /** |
| 297 | * Store errors by hash key |
| 298 | * |
| 299 | * @param array $error |
| 300 | * @return void |
| 301 | */ |
| 302 | protected function logErrorAdd($error, $key = null) { |
| 303 | $this->logError[ $key === null ? $error['file'].$error['line'].$error['error_type'].$error['coded'] : $key] = $error; |
| 304 | } |
| 305 | |
| 306 | /** |
| 307 | * Generalize an Sql statement changing "parameters" to ? |
| 308 | * |
| 309 | * @param string $sql |
| 310 | * @return string |
| 311 | */ |
| 312 | protected static function sqlNormalized($sql) { |
| 313 | static $sqlKeyWords =["select "," from "," join "," left "," right "," outer "," inner "," straight "," exists "," with "," on "," where "," and "," or "," not "," null "," in "," is "," having "," limit "," order by "," group by ","update ","insert ","delete "," as "]; |
| 314 | static $sqlKeyWordsUpperCase = []; |
| 315 | if(empty($sqlKeyWordsUpperCase)) { |
| 316 | foreach($sqlKeyWords as $keyword) { |
| 317 | $sqlKeyWordsUpperCase[] = strtoupper($keyword); |
| 318 | } |
| 319 | } |
| 320 | $sql = str_replace(['=','>','>=','<','<=','<>',"\r","\n","\t"],[' = ',' > ',' >= ',' < ',' <= ',' <> ',' ',' ',' '], $sql); |
| 321 | $sql = str_ireplace($sqlKeyWords,$sqlKeyWordsUpperCase,$sql); |
| 322 | global $gDBName; |
| 323 | $sql = preg_replace('/$gDBName\.aa_[a-z0-9_]+/mi', 'tempTable', $sql); |
| 324 | return preg_replace("/('([^']|'')*'|\b[-+]?[0-9]*\.?[0-9]+\b)/uS", '?', str_replace(["\\'". '´', '"'], '', self::strim($sql)) ); |
| 325 | } |
| 326 | |
| 327 | /** |
| 328 | * Returns the name of a php constant |
| 329 | * |
| 330 | * @param string $group |
| 331 | * @param mixed $value string or number |
| 332 | * @param string $tag |
| 333 | * @return string |
| 334 | */ |
| 335 | protected static function constant2name($group, $value, $tag='ERR') { |
| 336 | static $constats; |
| 337 | if(!isset($constants)) { |
| 338 | $constants = get_defined_constants(true); |
| 339 | } |
| 340 | if(!isset($constants[$group])) { |
| 341 | return "Unknown ($value)"; |
| 342 | } |
| 343 | foreach($constants[$group] as $c=>$n) { |
| 344 | if( (empty($tag) || stripos($c,$tag)!==FALSE) && $n===$value) { |
| 345 | return "$c ($value)"; |
| 346 | } |
| 347 | } |
| 348 | return "Unknown ($value)"; |
| 349 | } |
| 350 | |
| 351 | /** |
| 352 | * superTrim trim (including \s utf spaces), and change multiple spaces to one space |
| 353 | * |
| 354 | * @param string $str |
| 355 | * @return string |
| 356 | */ |
| 357 | protected static function strim($str) { |
| 358 | $s1 = preg_replace('/[\pZ\pC]/muS',' ',$str); |
| 359 | // @codeCoverageIgnoreStart |
| 360 | if(preg_last_error()) { |
| 361 | $s1 = preg_replace('/[\pZ\pC]/muS',' ', iconv("UTF-8","UTF-8//IGNORE",$str)); |
| 362 | if(preg_last_error()) |
| 363 | return trim(preg_replace('/ {2,}/mS',' ',$str)); |
| 364 | } |
| 365 | // @codeCoverageIgnoreEnd |
| 366 | return trim(preg_replace('/ {2,}/muS',' ',$s1)); |
| 367 | } |
| 368 | } |