Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
48.72% covered (danger)
48.72%
57 / 117
13.33% covered (danger)
13.33%
2 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
iaErrorReporter
48.28% covered (danger)
48.28%
56 / 116
13.33% covered (danger)
13.33%
2 / 15
540.71
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 process
89.19% covered (warning)
89.19%
33 / 37
0.00% covered (danger)
0.00%
0 / 1
16.32
 displayErrors
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 errorException
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 errorManual
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 dbtrace
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
3.33
 errorSql
25.00% covered (danger)
25.00%
2 / 8
0.00% covered (danger)
0.00%
0 / 1
15.55
 errorPhp
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
2.50
 errorJson
42.86% covered (danger)
42.86%
3 / 7
0.00% covered (danger)
0.00%
0 / 1
6.99
 errorPreg
33.33% covered (danger)
33.33%
2 / 6
0.00% covered (danger)
0.00%
0 / 1
5.67
 errorXml
42.86% covered (danger)
42.86%
3 / 7
0.00% covered (danger)
0.00%
0 / 1
4.68
 logErrorAdd
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 sqlNormalized
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 constant2name
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
56
 strim
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2require_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 */
14class 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}