2 /************************************************************************/
4 /************************************************************************/
5 /* Copyright (c) 2002-2008 by Greg Gay, Joel Kronenberg & Heidi Hazelton*/
6 /* Adaptive Technology Resource Centre / University of Toronto */
9 /* This program is free software. You can redistribute it and/or */
10 /* modify it under the terms of the GNU General Public License */
11 /* as published by the Free Software Foundation. */
12 /************************************************************************/
16 * Custom ErrorHandler for php. Ability to log and send errors over e-mail
18 * @author Jacek Materna
32 * Log warnings to file?
37 var $LOG_WARN_TO_FILE;
48 * Container to store errors until we decide to print them all
56 * Constructor for this class
60 function ErrorHandler() {
62 $this->setFlags(); // false by default
63 set_error_handler(array(&$this, 'ERROR_HOOK'));
64 $this->container = array();
67 * check first if the log directory is setup, if not then create a logs dir with a+w && a-r
69 if (!file_exists(AT_CONTENT_DIR . 'logs/') || !realpath(AT_CONTENT_DIR. 'logs/')) {
71 } else if (!is_dir(AT_CONTENT_DIR .'logs/')) {
77 * The error handling routine set by set_error_handler(). Mimicking and Exception implementation in OOA
78 * Must be as quick as possible. Note a few: '\n' -> chr(10) - avoids inline translation in php engine
79 * 'single quotes', avoid translation again
80 * Ability define custom errors. See case 'VITAL':
82 * eg call from script, trigger_error('VITAL#There was a problem with the database.','E_USER_ERROR');
84 * @param string $error_type The type of error being handled.
85 * @param string $error_msg The error message being handled.
86 * @param string $error_file The file in which the error occurred.
87 * @param integer $error_ln The line in which the error occurred.
88 * @param string $error_context The context in which the error occurred.
92 function ERROR_HOOK($error_type, $error_msg, $error_file, $error_ln, $error_context) {
93 if ($error_type == E_NOTICE || $error_type == E_USER_NOTICE) return;
96 $val_phpinfo_foot = '';
97 $val_phpinfo_printed = false; // used to track for the scope of this method whether the server
98 // has been attached to a log file or e-mail buffer previously
101 * Only produce the server configuration once per file
103 if ($this->todayLogFileExists() === false) {
104 // lets get some info about the system used by all error codes
107 // grab usefull data from php_info
108 phpinfo(INFO_GENERAL ^ INFO_CONFIGURATION);
109 $val_phpinfo .= ob_get_contents();
116 // get a substring of the php info to get rid of the html, head, title, etc.
117 $val_phpinfo = substr($val_phpinfo, 760, -19);
118 $val_phpinfo = substr($val_phpinfo, 552);
119 $val_phpinfo = substr($val_phpinfo, strpos($val_phpinfo, 'System'));
120 $val_phpinfo .= chr(10);
123 if (defined('MYSQL_NUM'))
128 $val_phpinfo .= 'MySQL installed? ' . $msql_str . '<br/><br/>';
130 // replace the </td>'s with tabs and the $nbsp;'s with spaces
131 $val_phpinfo = str_replace( '</td>', ' ', $val_phpinfo);
132 $val_phpinfo = str_replace( ' ', ' ', $val_phpinfo);
133 $val_phpinfo = str_replace( '</body>', ' ', $val_phpinfo);
134 $val_phpinfo = str_replace( '</html>', ' ', $val_phpinfo);
136 $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);
138 $val_phpinfo_foot .= '$_ENV:' . chr(10) . $this->debug($_ENV) . '<br/><br/>';
142 $val_phpuser = '$_SESSION:' . chr(10) . $this->debug($_SESSION);
143 $val_phpuser .= '$_REQUEST:' . chr(10) . $this->debug($_REQUEST);
144 $val_phpuser .= '$_COOKIE:' . chr(10) . $this->debug($_COOKIE);
145 $val_phpuser .= '$_GET:' . chr(10) . $this->debug($_GET);
146 $val_phpuser .= '$_POST:' . chr(10) . $this->debug($_POST);
148 // replace the </td>'s with tabs and the $nbsp;'s with spaces
149 $val_phpuser = str_replace( '</td>', ' ', $val_phpuser);
150 $val_phpuser = str_replace( ' ', ' ', $val_phpuser) . '<br/>';
152 switch($error_type) {
154 case E_ERROR: // caught below
157 if (substr_count($error_msg, "#") > 0) {
158 $_error = explode("#", $error_msg);
160 $_error = array('', $error_msg);
164 * eg call, trigger_error('VITAL;There was a problem with the database.',E_USER_ERROR);
166 * List of custom errors go here and the appropriate action is taken
171 * Custom errors are not guaranteed to be printed for example in footer.inc.php
172 * Hanlde on a case-by-case basis
174 case 'VITAL': // @see vital.inc.php
175 if ($this->LOG_ERR_TO_FILE) {
176 if ($val_phpinfo_printed === true) {
179 $this->log_to_files($val_phpinfo, $val_phpinfo_foot, 'ATutor v' . VERSION . '<br/>'. 'PHP ERROR MESSAGE:' . '<br/><p>'
180 . $error_msg . ' (error type ' . $error_type . ' in '
181 . $error_file . ' on line ' . $error_ln . ') [context: '
182 . $error_context . ']</p>' . chr(10) .chr(10) . $val_phpuser );
184 $val_phpinfo_printed = true;
188 $this->printError('<strong>ATutor has detected an Error<strong> - ' .
194 case 'BKP_MEMBER': // @see TableBackup.class.php
195 if ($this->LOG_ERR_TO_FILE) {
196 if ($val_phpinfo_printed === true) {
199 $this->log_to_files($val_phpinfo, $val_phpinfo_foot, 'ATutor v' . VERSION . '<br/>'. 'PHP ERROR MESSAGE:' . '<br/><p>'
200 . $error_msg . ' (error type ' . $error_type . ' in '
201 . $error_file . ' on line ' . $error_ln . ') [context: '
202 . $error_context . ']</p>' . chr(10) .chr(10) . $val_phpuser );
204 $val_phpinfo_printed = true;
208 $this->printError('<strong>ATutor has detected an Error<strong> - ' .
214 default: // standard user error without custom prefix
215 if ($this->LOG_ERR_TO_FILE) {
216 if ($val_phpinfo_printed === true) {
220 $this->log_to_files($val_phpinfo, $val_phpinfo_foot, 'ATutor v' . VERSION . '<br/>'. 'PHP ERROR MESSAGE:' . '<br/><p>'
221 . $error_msg . ' (error type ' . $error_type . ' in '
222 . $error_file . ' on line ' . $error_ln . ') [context: '
223 . $error_context . ']</p>' . chr(10) .chr(10) . $val_phpuser);
225 $val_phpinfo_printed = true;
229 //$this->printError('<strong>ATutor has detected an Error<strong> - ' . 'Problem spot: ' . $error_msg . ' in '
230 // . $this->stripbase($error_file) . ' on line ' . $error_ln);
231 array_push($this->container, 'Problem spot: ' . $error_msg . ' in ' . $this->stripbase($error_file) . ' on line ' . $error_ln);
237 if ($this->LOG_WARN_TO_FILE) {
238 if ($val_phpinfo_printed === true) {
242 $this->log_to_files($val_phpinfo, $val_phpinfo_foot, 'ATutor v' . VERSION . '<br/>'. 'PHP ERROR MESSAGE:' . '<br/><p>'
243 . $error_msg . ' (error type ' . $error_type . ' in '
244 . $error_file . ' on line ' . $error_ln . ') [context: '
245 . $error_context . ']</p>' . chr(10) . chr(10) . $val_phpuser);
247 $val_phpinfo_printed = true;
250 //$this->printError('<strong>ATutor has detected an Error</strong> - ' . 'Problem spot: ' . $error_msg . ' in '
251 // . $this->stripbase($error_file) . ' on line ' . $error_ln);
252 array_push($this->container, 'Problem spot: ' . $error_msg . ' in ' . $this->stripbase($error_file) . ' on line ' . $error_ln);
262 ÊÊ * Dump the current error into a file along with an updated profile for that error
264 * @param string the profile to log
265 * @param string the bug to log
268 function log_to_files($profile, $profile_foot, $buf) {
270 if ($profile == '' || $profile_foor = '' || $buf == '') return;
273 * Redundancy control for profile/error log creation
275 $profile_created = true;
276 $error_created = true;
278 $php_head = '<?php echo \'Only viewable as Admin user\'; exit; ?>' . chr(10);
280 // Lets make a unqiue profile key, strip away circumventors of the md5 hashing algo. @see md5 algo src
281 $temp = strip_tags($profile);
282 $temp = stripslashes($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 $temp = str_replace('#' , ' ', $temp);
300 $profile_key = md5($temp);
304 // Uniqueness assumend to be coupled to epoch timestamp
305 $timestamp_ = $today['mon'] . '-' . $today['mday'] . '-' . $today['year'];
308 * Lets make sure we have a log directory made for today
310 if (!is_dir(AT_CONTENT_DIR . 'logs/' . $timestamp_)) { // create it
311 $result = @mkdir(AT_CONTENT_DIR . 'logs/' . $timestamp_, 0771); // r+w for owner
314 $this->printError('Fatal. Could not create /content/logs' . '/' . $timestamp_ . '. Please resolve');
316 } // else already there
319 * Go through all the profiles in our directory and lets try and map our md5 key to one of them,
320 * if its not found then we must be dealing with a new profile, thus create it
322 $dir_ = AT_CONTENT_DIR . 'logs/' . $timestamp_;
324 if (!($dir = opendir($dir_))) {
325 $msg->printNoLookupFeedback('Could not access /content/logs/' . $timestamp_ . '. Check that the permission for the <strong>Server</string> user are r+w to it');
326 require(AT_INCLUDE_PATH.'footer.inc.php');
332 * Run through the todays logs directory and lets get all the profiles
336 // loop through folder todays log folder and try and match a profile to our error profile md5 key
337 while (($file = readdir($dir)) !== false) {
339 /* if the name is not a directory */
340 if( ($file == '.') || ($file == '..') || is_dir($file) ) {
344 if (strpos($file, 'profile') >= 0) {
345 $check_key = substr($file, strpos($file, '_') + 1);
346 $check_key = substr($check_key, 0, strpos($check_key, '.log.php'));
348 if ($check_key === $profile_key) { // found!
349 $use_profile = $file;
350 $profile_created = true;
355 closedir($dir); // clean it up
357 // if $use_profile == null here then we must create a new profile for this error
358 if ($use_profile == null) {
359 $use_profile = 'profile_' . $profile_key . '.log.php';
360 if ($file_handle = fopen(AT_CONTENT_DIR . 'logs/' . $timestamp_ . '/' . $use_profile, "w")) {
361 if (!fwrite($file_handle, $php_head . chr(10) . $profile . $profile_foot)) { $profile_created = false; }
362 } else { $profile_created = false; }
363 fclose($file_handle);
364 } // else just use $use_profile as the profile for this error
366 // if the creation of the profile_created = false then creation failed and we didnt have an already
367 // existant one in the dir, profile must exist
368 if ($profile_created === false) return;
370 $timestamp = $timestamp_ . '_' . $today[0];
372 // create a unique error filename including the epoch timestamp + and the profile mapping
373 $unique_error_log = $timestamp . '_pr' . $profile_key;
375 if (is_file(AT_CONTENT_DIR . 'logs/' . $timestamp_ . '/' . $unique_error_log)) {
376 $unique_error_log .= rand(); // should be enough
379 $unique_error_log .= '.log.php'; // append suffix
381 /* Create error log file */
382 if ($file_handle = fopen(AT_CONTENT_DIR . 'logs/' . $timestamp_ . '/' . $unique_error_log, "w")) {
383 if (!fwrite($file_handle, $php_head . chr(10) . $buf)) { $error_created = false; }
385 $error_created = false;
387 fclose($file_handle);
389 // check that we created a profile and its error or used an existing profile and created its error
390 if ($profile_created === true && $error_created === true) { // ok
391 chmod(AT_CONTENT_DIR . 'logs/' . $timestamp_ . '/' . $unique_error_log, 0771);
392 chmod(AT_CONTENT_DIR . 'logs/' . $timestamp_ . '/' . $use_profile, 0771);
393 } else if ($profile_create === true && $error_created === false) { // remove profile
394 unlink(AT_CONTENT_DIR . 'logs/' . $timestamp_ . '/' . $use_profile);
399 ÊÊ * Restores the error handler to the default error handler
404 function restoreOrigHandler() {
405 restore_error_handler();
409 Ê * Returns the error handler to ERROR_HOOK()
414 function returnHandler() {
415 set_error_handler(array(&$this, 'ERROR_HOOK'));
419 ÊÊ * Changes the logging preferences
421 * @param Boolean $error_flag Log errors to file?
422 ÊÊ * @param Boolean $warning_flag Log warnings to file?
426 function setFlags($error_flag = true, $warning_flag = true) {
428 $this->LOG_ERR_TO_FILE = $error_flag;
429 $this->LOG_WARN_TO_FILE = $warning_flag;
433 * Construct a nicely formatted tree view of a variable
434 * @param var String is the varialbe to construct the output from
437 function debug($var) {
442 $str = ob_get_contents();
445 $str = str_replace('<', '<', $str);
447 $str = str_replace('[', '<span style="color: red; font-weight: bold;">[', $str);
448 $str = str_replace(']', ']</span>', $str);
449 $str = str_replace('=>', '<span style="color: blue; font-weight: bold;">=></span>', $str);
450 $str = str_replace('Array', '<span style="color: purple; font-weight: bold;">Array</span>', $str);
458 * Function which strips the path base off a file URL since it is a security risk
459 * @param String str is the path string
460 * @return String only the script filename where the error occured
462 function stripbase($str) {
464 $to_root = $_SERVER["PATH_TRANSLATED"];
466 $pos_last = strrpos($to_root, "/");
467 $to_root = substr($to_root, $pos_last + 1);
472 * Print the error to the browser, dont use any templates or css sheets for flexibility
475 function printError($str) {
476 if (!AT_DEVEL) return;
479 echo '<table bgcolor="#FF0000" border="0" cellpadding="3" cellspacing="2" width="90%" summary="" align="center">';
480 echo '<tr bgcolor="#FEF1F1" align="top">';
482 echo '<h3><span style="font-family: arial verdana">Internal Error Detected</span></small></h3>';
484 echo '<li><small><span style="font-family: arial verdana">'. $str .'</span></small></li>';
493 * Create restricted access logs dir
495 function makeLogDir() {
497 $result = @mkdir(AT_CONTENT_DIR . 'logs', 0771); // r+w for owner
499 $this->printError('Fatal. Could not create /content/logs. Please resolve');
505 * Determine wheter a log file exists for today
508 function todayLogFileExists() {
511 $timestamp = $today['mon'] . '-' . $today['mday'] . '-' . $today['year'];
513 return (is_file(AT_CONTENT_DIR . 'logs/' . $timestamp . '.log'));
517 * Run through $container and print all the errors on this page.
518 * Used to prevent errors from breaking content on the page
521 function showErrors() {
523 foreach($this->container as $elem) {
524 $this->printError('<strong>ATutor has detected an Error<strong> - ' .