1f9bd7e2f3418ebd90a3f17136e06c2ed549f4f4
[atutor.git] / docs / include / classes / ErrorHandler / ErrorHandler.class.php
1 <?php 
2 /************************************************************************/
3 /* ATutor                                                                                                                               */
4 /************************************************************************/
5 /* Copyright (c) 2002-2010                                              */
6 /* Inclusive Design Institute                                           */
7 /* http://atutor.ca                                                     */
8 /* This program is free software. You can redistribute it and/or        */
9 /* modify it under the terms of the GNU General Public License          */
10 /* as published by the Free Software Foundation.                        */
11 /************************************************************************/
12                 
13 /**
14 * ErrorHandler
15 * Custom ErrorHandler for php. Ability to log and send errors over e-mail
16 * @access       public
17 * @author       Jacek Materna
18 */
19
20 class ErrorHandler { 
21
22         /** 
23         * Log errors to file?
24         * 
25         * @var Boolean 
26         * @access public 
27         */ 
28         var $LOG_ERR_TO_FILE; 
29         
30         /** 
31         * Log warnings to file? 
32         * 
33         * @var Boolean 
34         * @access public  
35         */ 
36         var $LOG_WARN_TO_FILE;   
37         
38         /**
39          * Message object
40          *
41          * @var object
42          * @access public
43          */
44         var $msg;
45         
46         /**
47          * Container to store errors until we decide to print them all
48          *
49          * @var array
50          * @access public
51          */
52         var $container;
53          
54         /** 
55         * Constructor for this class
56         * @return void 
57         * @access public 
58         */ 
59         function ErrorHandler() {  
60                 
61                 $this->setFlags(); // false by default
62                 set_error_handler(array(&$this, 'ERROR_HOOK')); 
63                 $this->container = array();
64                 
65                 /**
66                  * check first if the log directory is setup, if not then create a logs dir with a+w && a-r
67                  */
68                 if (!file_exists(AT_CONTENT_DIR . 'logs/') || !realpath(AT_CONTENT_DIR. 'logs/')) {
69                         $this->makeLogDir();
70                 } else if (!is_dir(AT_CONTENT_DIR .'logs/')) {
71                         $this->makeLogDir();
72                 } 
73         }
74         
75         /** 
76         * The error handling routine set by set_error_handler(). Mimicking and Exception implementation in OOA
77         * Must be as quick as possible. Note a few: '\n' -> chr(10) - avoids inline translation in php engine
78         *                                                                                        'single quotes', avoid translation again
79         * Ability define custom errors. See case 'VITAL':
80         * 
81         * eg call from script, trigger_error('VITAL#There was a problem with the database.','E_USER_ERROR');
82         *
83         * @param string $error_type The type of error being handled. 
84         * @param string $error_msg The error message being handled. 
85         * @param string $error_file The file in which the error occurred. 
86         * @param integer $error_ln The line in which the error occurred. 
87         * @param string $error_context The context in which the error occurred.
88         * @return Boolean 
89         * @access public 
90         */ 
91         function ERROR_HOOK($error_type, $error_msg, $error_file, $error_ln, $error_context) { 
92                 if ($error_type == E_NOTICE || $error_type == E_USER_NOTICE) return;
93                 
94                 $val_phpinfo = '';
95                 $val_phpinfo_foot = '';
96                 $val_phpinfo_printed  = false; // used to track for the scope of this method whether the server
97                                                                                 // has been attached to a log file or e-mail buffer previously
98                 
99                 /**
100                  * Only produce the server configuration once per file
101                  */
102                 if ($this->todayLogFileExists() === false) {
103                         // lets get some info about the system used by all error codes
104                         ob_start();
105                         
106                         // grab usefull data from php_info
107                         phpinfo(INFO_GENERAL ^ INFO_CONFIGURATION);
108                         $val_phpinfo .= ob_get_contents();
109                         ob_end_clean();
110                         
111                         /*
112                          * Parse $val_phpinfo
113                          */
114                         
115                         // get a substring of the php info to get rid of the html, head, title, etc.
116                         $val_phpinfo = substr($val_phpinfo, 760, -19);
117                         $val_phpinfo = substr($val_phpinfo, 552);
118                         $val_phpinfo = substr($val_phpinfo, strpos($val_phpinfo, 'System'));
119                         $val_phpinfo .= chr(10);
120                         
121                         $msql_str = '';
122                         if (defined('MYSQL_NUM'))
123                                 $msql_str = "Yes";
124                         else
125                                 $msql_str = "No";
126                         
127                         $val_phpinfo .= 'MySQL installed? ' . $msql_str . '<br/><br/>';
128                         
129                         // replace the </td>'s with tabs and the $nbsp;'s with spaces
130                         $val_phpinfo = str_replace( '</td>', '    ', $val_phpinfo);
131                         $val_phpinfo = str_replace( '&nbsp;', ' ', $val_phpinfo);
132                         $val_phpinfo = str_replace( '</body>', ' ', $val_phpinfo);
133                         $val_phpinfo = str_replace( '</html>', ' ', $val_phpinfo);
134                         
135                         $val_phpinfo = str_replace('This program makes use of the Zend Scripting Language Engine:<br />Zend Engine v1.3.0, Copyright (c) 1998-2003 Zend Technologies', '', $val_phpinfo);
136                 
137                         $val_phpinfo_foot .= '$_ENV:' . chr(10) . $this->debug($_ENV) . '<br/><br/>';
138                 } 
139                 
140                 // Everytime
141                 $val_phpuser = '$_SESSION:' . chr(10) . $this->debug($_SESSION);
142                 $val_phpuser .= '$_REQUEST:' . chr(10) . $this->debug($_REQUEST);
143                 $val_phpuser .= '$_COOKIE:' . chr(10) . $this->debug($_COOKIE);
144                 $val_phpuser .= '$_GET:' . chr(10) . $this->debug($_GET);
145                 $val_phpuser .= '$_POST:' . chr(10) . $this->debug($_POST);
146                 
147                 // replace the </td>'s with tabs and the $nbsp;'s with spaces
148                 $val_phpuser = str_replace( '</td>', '    ', $val_phpuser);
149                 $val_phpuser = str_replace( '&nbsp;', ' ', $val_phpuser) . '<br/>';
150                 
151                 switch($error_type) {
152                         
153                         case E_ERROR: // caught below
154                         case E_USER_ERROR: 
155
156                                 if (substr_count($error_msg, "#") > 0) {
157                                         $_error = explode("#", $error_msg);
158                                 } else {
159                                         $_error = array('', $error_msg);
160                                 }
161
162                                 /**
163                                  * eg call, trigger_error('VITAL;There was a problem with the database.',E_USER_ERROR);
164                                  *
165                                  * List of custom errors go here and the appropriate action is taken
166                                  *@
167                                  */
168                                 switch($_error[0]) {
169                                         /**
170                                          * Custom errors are not guaranteed to be printed for example in footer.inc.php
171                                          * Hanlde on a case-by-case basis
172                                          */
173                                         case 'VITAL': // @see vital.inc.php
174                                                 if ($this->LOG_ERR_TO_FILE) { 
175                                                                 if ($val_phpinfo_printed === true) {
176                                                                         $val_phpinfo = '';
177                                                                 }
178                                                                 $this->log_to_files($val_phpinfo, $val_phpinfo_foot, 'ATutor v' . VERSION . '<br/>'. 'PHP ERROR MESSAGE:' . '<br/><p>'
179                                                                                 . $error_msg . ' (error type ' . $error_type . ' in ' 
180                                                                                 . $error_file . ' on line ' . $error_ln . ') [context: ' 
181                                                                                 . $error_context . ']</p>' . chr(10) .chr(10) . $val_phpuser );
182                                                                                 
183                                                                 $val_phpinfo_printed = true;
184                                                                 
185                                                 }                                       
186                                                 
187                                                 $this->printError('<strong>ATutor has detected an Error<strong> - ' .
188                                                                                                                 $_error[1]);
189
190                                                 exit; // done here
191                                                 break;
192                                                 
193                                         case 'BKP_MEMBER': // @see TableBackup.class.php
194                                                 if ($this->LOG_ERR_TO_FILE) { 
195                                                                 if ($val_phpinfo_printed === true) {
196                                                                         $val_phpinfo = '';
197                                                                 }
198                                                                 $this->log_to_files($val_phpinfo, $val_phpinfo_foot, 'ATutor v' . VERSION . '<br/>'. 'PHP ERROR MESSAGE:' . '<br/><p>'
199                                                                                 . $error_msg . ' (error type ' . $error_type . ' in ' 
200                                                                                 . $error_file . ' on line ' . $error_ln . ') [context: ' 
201                                                                                 . $error_context . ']</p>' . chr(10) .chr(10) . $val_phpuser );
202                                                                                 
203                                                                 $val_phpinfo_printed = true;
204                                                                 
205                                                 }
206                                                 
207                                                 $this->printError('<strong>ATutor has detected an Error<strong> - ' .
208                                                                                                                 $_error[1]);
209                                         
210                                                 exit;
211                                                 break;
212                                         
213                                         default: // standard user error without custom prefix
214                                                 if ($this->LOG_ERR_TO_FILE) { 
215                                                                 if ($val_phpinfo_printed === true) {
216                                                                         $val_phpinfo = '';
217                                                                 }
218                                                                 
219                                                                 $this->log_to_files($val_phpinfo, $val_phpinfo_foot, 'ATutor v' . VERSION . '<br/>'. 'PHP ERROR MESSAGE:' . '<br/><p>'
220                                                                                 . $error_msg . ' (error type ' . $error_type . ' in ' 
221                                                                                 . $error_file . ' on line ' . $error_ln . ') [context: ' 
222                                                                                 . $error_context . ']</p>' . chr(10) .chr(10) . $val_phpuser);
223                                                                 
224                                                                 $val_phpinfo_printed = true;
225                                                 }                                       
226                                 }
227                                 
228                                 //$this->printError('<strong>ATutor has detected an Error<strong> - ' . 'Problem spot: ' . $error_msg . ' in ' 
229                                 //                              . $this->stripbase($error_file) . ' on line ' . $error_ln);
230                                 array_push($this->container, 'Problem spot: ' . $error_msg . ' in ' . $this->stripbase($error_file) . ' on line ' . $error_ln);
231                                                                         
232                                 break;
233                         
234                         case E_WARNING: 
235                         case E_USER_WARNING: 
236                                 if ($this->LOG_WARN_TO_FILE) { 
237                                         if ($val_phpinfo_printed === true) {
238                                                 $val_phpinfo = '';
239                                         }
240                                                                 
241                                         $this->log_to_files($val_phpinfo, $val_phpinfo_foot, 'ATutor v' . VERSION . '<br/>'. 'PHP ERROR MESSAGE:' . '<br/><p>'
242                                                         . $error_msg . ' (error type ' . $error_type . ' in ' 
243                                                         . $error_file . ' on line ' . $error_ln . ') [context: ' 
244                                                         . $error_context . ']</p>' . chr(10) . chr(10) . $val_phpuser);                 
245                                 
246                                         $val_phpinfo_printed = true;
247                                 }
248
249                                 //$this->printError('<strong>ATutor has detected an Error</strong> - ' . 'Problem spot: ' . $error_msg . ' in ' 
250                                 //                              . $this->stripbase($error_file) . ' on line ' . $error_ln);
251                                 array_push($this->container, 'Problem spot: ' . $error_msg . ' in ' . $this->stripbase($error_file) . ' on line ' . $error_ln);
252         
253                                 break;
254                          default:
255                 }
256
257                 return true; 
258         }
259         
260         /** 
261 ÊÊ      * Dump the current error into a file along with an updated profile for that error
262 ÊÊ      * 
263         * @param string the profile to log
264         * @param string the bug to log 
265 ÊÊ      * @access public
266 ÊÊ      */
267         function log_to_files($profile, $profile_foot, $buf) {
268                 
269                 if ($profile == '' || $profile_foor = '' || $buf == '') return;
270                 
271                 /**
272                  * Redundancy control for profile/error log creation
273                  */
274                  $profile_created = true;
275                  $error_created = true;
276                  
277                 $php_head = '<?php echo \'Only viewable as Admin user\'; exit; ?>' . chr(10);
278                 
279                 // Lets make a unqiue profile key, strip away circumventors of the md5 hashing algo. @see md5 algo src
280                 $temp = strip_tags($profile);
281                 $temp = stripslashes($temp);
282                 $temp = str_replace('/', ' ', $temp);
283                 $temp = str_replace('\$', ' ', $temp);
284                 $temp = str_replace('$', ' ', $temp);
285                 $temp = str_replace('\&' , ' ', $temp);
286                 $temp = str_replace('&' , ' ', $temp);
287                 $temp = str_replace('*' , ' ', $temp);
288                 $temp = str_replace('~' , ' ', $temp);
289                 $temp = str_replace('.' , ' ', $temp);
290                 $temp = str_replace(';' , ' ', $temp);
291                 $temp = str_replace(':' , ' ', $temp);
292                 $temp = str_replace('-' , ' ', $temp);
293                 $temp = str_replace('_' , ' ', $temp);
294                 $temp = str_replace('\'' , ' ', $temp);
295                 $temp = str_replace(',' , ' ', $temp);
296                 $temp = str_replace('@' , ' ', $temp);
297                 $temp = str_replace('#' , ' ', $temp);
298
299                 $profile_key = md5($temp);
300                 
301                 $today = getdate(); 
302                 
303                 // Uniqueness assumend to be coupled to epoch timestamp
304                 $timestamp_ = $today['mon'] . '-' . $today['mday'] . '-' . $today['year'];
305                 
306                 /**
307                  * Lets make sure we have a log directory made for today
308                  */ 
309                 if (!is_dir(AT_CONTENT_DIR . 'logs/' . $timestamp_)) { // create it
310                         $result = @mkdir(AT_CONTENT_DIR . 'logs/' . $timestamp_, 0771); // r+w for owner
311         
312                         if ($result == 0) {
313                                 $this->printError('Fatal. Could not create /content/logs' . '/' . $timestamp_ . '. Please resolve');
314                         }
315                 } // else already there
316                 
317                 /**
318                  * Go through all the profiles in our directory and lets try and map our md5 key to one of them,
319                  * if its not found then we must be dealing with a new profile, thus create it
320                  */
321                  $dir_ = AT_CONTENT_DIR . 'logs/' . $timestamp_;
322                 
323                 if (!($dir = opendir($dir_))) {
324                         $msg->printNoLookupFeedback('Could not access /content/logs/' . $timestamp_ . '. Check that the permission for the <strong>Server</string> user are r+w to it');
325                         require(AT_INCLUDE_PATH.'footer.inc.php'); 
326                         
327                         exit;
328                 }
329                 
330                 /**
331                  * Run through the todays logs directory and lets get all the profiles
332                  */ 
333                 $use_profile = null;
334                 
335                 // loop through folder todays log folder and try and match a profile to our error profile md5 key
336                 while (($file = readdir($dir)) !== false) {
337                 
338                         /* if the name is not a directory */
339                         if( ($file == '.') || ($file == '..') || is_dir($file) ) {
340                                 continue;
341                         }
342                 
343                         if (strpos($file, 'profile') >= 0) {
344                                 $check_key = substr($file, strpos($file, '_') + 1);
345                                 $check_key = substr($check_key, 0, strpos($check_key, '.log.php'));
346
347                                 if ($check_key === $profile_key) { // found!
348                                         $use_profile = $file;
349                                         $profile_created = true;
350                                         break;
351                                 }
352                         }
353                 }
354                 closedir($dir); // clean it up
355                 
356                 // if $use_profile == null here then we must create a new profile for this error
357                 if ($use_profile == null) {
358                         $use_profile = 'profile_' . $profile_key . '.log.php';
359                         if ($file_handle = fopen(AT_CONTENT_DIR . 'logs/' . $timestamp_ . '/' . $use_profile, "w")) {
360                                 if (!fwrite($file_handle, $php_head . chr(10) . $profile . $profile_foot)) { $profile_created = false; }
361                         } else { $profile_created = false; }
362                         fclose($file_handle);
363                 } // else just use $use_profile as the profile for this error
364                 
365                 // if the creation of the profile_created = false then creation failed and we didnt have an already
366                 // existant one in the dir, profile must exist
367                 if ($profile_created === false) return;
368                 
369                 $timestamp = $timestamp_ . '_' . $today[0];
370                                         
371                 // create a unique error filename including the epoch timestamp + and the profile mapping
372                 $unique_error_log = $timestamp . '_pr' . $profile_key;
373
374                 if (is_file(AT_CONTENT_DIR . 'logs/' . $timestamp_ . '/' . $unique_error_log)) {
375                         $unique_error_log .= rand(); // should be enough
376                 }
377                 
378                 $unique_error_log .= '.log.php'; // append suffix
379                 
380                 /* Create error log file */
381                 if ($file_handle = fopen(AT_CONTENT_DIR . 'logs/' . $timestamp_ . '/' . $unique_error_log, "w")) {
382                         if (!fwrite($file_handle, $php_head . chr(10) . $buf)) {  $error_created = false;  }
383                 } else {
384                         $error_created = false;
385                 }
386                 fclose($file_handle);
387                 
388                 // check that we created a profile and its error or used an existing profile and created its error
389                 if ($profile_created === true && $error_created === true) { // ok
390                         chmod(AT_CONTENT_DIR . 'logs/' . $timestamp_ . '/' . $unique_error_log, 0771);
391                         chmod(AT_CONTENT_DIR . 'logs/' . $timestamp_ . '/' . $use_profile, 0771);
392                 } else if ($profile_create === true && $error_created === false) { // remove profile
393                         unlink(AT_CONTENT_DIR . 'logs/' . $timestamp_ . '/' . $use_profile);
394                 } 
395         }
396
397         /** 
398 ÊÊ      * Restores the error handler to the default error handler 
399 ÊÊ      * 
400 ÊÊ      * @return void 
401 ÊÊ      * @access public
402 ÊÊ      */
403         function restoreOrigHandler() {
404                 restore_error_handler();
405         }
406
407         /** 
408 Ê       * Returns the error handler to ERROR_HOOK() 
409 Ê       * 
410 ÊÊ      * @return void 
411 ÊÊ      * @access public  
412 ÊÊ      */
413         function returnHandler() {
414                 set_error_handler(array(&$this, 'ERROR_HOOK'));
415         }
416         
417         /** 
418 ÊÊ      * Changes the logging preferences
419 ÊÊ      * 
420         * @param Boolean $error_flag Log errors to file? 
421 ÊÊ      * @param Boolean $warning_flag Log warnings to file? 
422 ÊÊ      * @return void 
423 ÊÊ      * @access public 
424 ÊÊ      */
425         function setFlags($error_flag = true, $warning_flag = true) {                            
426                 
427                 $this->LOG_ERR_TO_FILE = $error_flag;
428                 $this->LOG_WARN_TO_FILE = $warning_flag;
429         }
430         
431         /**
432          * Construct a nicely formatted tree view of a variable
433          * @param var String is the varialbe to construct the output from
434          * @access private
435          */
436         function debug($var) {          
437                 $str_ = '<pre>';
438                 
439                 ob_start();
440                 print_r($var);
441                 $str = ob_get_contents();
442                 ob_end_clean();
443         
444                 $str = str_replace('<', '&lt;', $str);
445         
446                 $str = str_replace('[', '<span style="color: red; font-weight: bold;">[', $str);
447                 $str = str_replace(']', ']</span>', $str);
448                 $str = str_replace('=>', '<span style="color: blue; font-weight: bold;">=></span>', $str);
449                 $str = str_replace('Array', '<span style="color: purple; font-weight: bold;">Array</span>', $str);
450                 $str .= '</pre>';
451                 
452                 $str = $str_ . $str;
453                 return $str;
454         }
455         
456         /**
457          * Function which strips the path base off a file URL since it is a security risk
458          * @param String str is the path string
459          * @return String only the script filename where the error occured
460          */
461         function stripbase($str) {
462                 
463                 $to_root = $_SERVER["PATH_TRANSLATED"];
464                 
465                 $pos_last = strrpos($to_root, "/");
466                 $to_root = substr($to_root, $pos_last + 1);
467                 return $to_root;
468         }
469         
470         /**
471          * Print the error to the browser, dont use any templates or css sheets for flexibility
472          * @access private
473          */
474         function printError($str) {
475                 if (!AT_DEVEL) return;
476                 
477                 echo '<br />';
478                 echo '<table bgcolor="#FF0000" border="0" cellpadding="3" cellspacing="2" width="90%" summary="" align="center">';
479                 echo '<tr bgcolor="#FEF1F1" align="top">';
480                 echo '<td>';
481                 echo '<h3><span style="font-family: arial verdana">Internal Error Detected</span></small></h3>';
482                 echo '<ul>';
483                 echo '<li><small><span style="font-family: arial verdana">'. $str .'</span></small></li>';
484                 echo'</ul>';
485                 echo '</td>';
486                 echo '</tr>';
487                 echo '</table>';
488                 echo '<br />';
489         }
490         
491         /**
492          * Create restricted access logs dir
493          */
494         function makeLogDir() {
495                 
496                 $result = @mkdir(AT_CONTENT_DIR . 'logs', 0771); // r+w for owner               
497                 if ($result == 0) {
498                         $this->printError('Fatal. Could not create /content/logs. Please resolve');
499                 }
500         
501         }
502         
503         /**
504          * Determine wheter a log file exists for today
505          * @access private
506          */
507         function todayLogFileExists() {
508                 $today = getdate(); 
509
510                 $timestamp = $today['mon'] . '-' . $today['mday'] . '-' . $today['year'];
511                 
512                 return (is_file(AT_CONTENT_DIR . 'logs/' . $timestamp . '.log'));
513         }
514         
515         /**
516          * Run through $container and print all the errors on this page.
517          * Used to prevent errors from breaking content on the page
518          * @access public
519          */
520         function showErrors() {
521
522                 foreach($this->container as $elem) {
523                         $this->printError('<strong>ATutor has detected an Error<strong> - ' .
524                                                                                                                         $elem);
525                         unset($elem);
526                 }
527         }
528
529 ?>