2 /************************************************************************/
4 /************************************************************************/
5 /* Copyright (c) 2002-2010 */
6 /* Inclusive Design Institute */
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 /************************************************************************/
15 * Custom ErrorHandler for php. Ability to log and send errors over e-mail
17 * @author Jacek Materna
31 * Log warnings to file?
36 var $LOG_WARN_TO_FILE;
47 * Container to store errors until we decide to print them all
55 * Constructor for this class
59 function ErrorHandler() {
61 $this->setFlags(); // false by default
62 set_error_handler(array(&$this, 'ERROR_HOOK'));
63 $this->container = array();
66 * check first if the log directory is setup, if not then create a logs dir with a+w && a-r
68 if (!file_exists(AT_CONTENT_DIR . 'logs/') || !realpath(AT_CONTENT_DIR. 'logs/')) {
70 } else if (!is_dir(AT_CONTENT_DIR .'logs/')) {
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':
81 * eg call from script, trigger_error('VITAL#There was a problem with the database.','E_USER_ERROR');
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.
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;
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
100 * Only produce the server configuration once per file
102 if ($this->todayLogFileExists() === false) {
103 // lets get some info about the system used by all error codes
106 // grab usefull data from php_info
107 phpinfo(INFO_GENERAL ^ INFO_CONFIGURATION);
108 $val_phpinfo .= ob_get_contents();
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);
122 if (defined('MYSQL_NUM'))
127 $val_phpinfo .= 'MySQL installed? ' . $msql_str . '<br/><br/>';
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( ' ', ' ', $val_phpinfo);
132 $val_phpinfo = str_replace( '</body>', ' ', $val_phpinfo);
133 $val_phpinfo = str_replace( '</html>', ' ', $val_phpinfo);
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);
137 $val_phpinfo_foot .= '$_ENV:' . chr(10) . $this->debug($_ENV) . '<br/><br/>';
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);
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( ' ', ' ', $val_phpuser) . '<br/>';
151 switch($error_type) {
153 case E_ERROR: // caught below
156 if (substr_count($error_msg, "#") > 0) {
157 $_error = explode("#", $error_msg);
159 $_error = array('', $error_msg);
163 * eg call, trigger_error('VITAL;There was a problem with the database.',E_USER_ERROR);
165 * List of custom errors go here and the appropriate action is taken
170 * Custom errors are not guaranteed to be printed for example in footer.inc.php
171 * Hanlde on a case-by-case basis
173 case 'VITAL': // @see vital.inc.php
174 if ($this->LOG_ERR_TO_FILE) {
175 if ($val_phpinfo_printed === true) {
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 );
183 $val_phpinfo_printed = true;
187 $this->printError('<strong>ATutor has detected an Error<strong> - ' .
193 case 'BKP_MEMBER': // @see TableBackup.class.php
194 if ($this->LOG_ERR_TO_FILE) {
195 if ($val_phpinfo_printed === true) {
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 );
203 $val_phpinfo_printed = true;
207 $this->printError('<strong>ATutor has detected an Error<strong> - ' .
213 default: // standard user error without custom prefix
214 if ($this->LOG_ERR_TO_FILE) {
215 if ($val_phpinfo_printed === true) {
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);
224 $val_phpinfo_printed = true;
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);
236 if ($this->LOG_WARN_TO_FILE) {
237 if ($val_phpinfo_printed === true) {
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);
246 $val_phpinfo_printed = true;
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);
261 ÊÊ * Dump the current error into a file along with an updated profile for that error
263 * @param string the profile to log
264 * @param string the bug to log
267 function log_to_files($profile, $profile_foot, $buf) {
269 if ($profile == '' || $profile_foor = '' || $buf == '') return;
272 * Redundancy control for profile/error log creation
274 $profile_created = true;
275 $error_created = true;
277 $php_head = '<?php echo \'Only viewable as Admin user\'; exit; ?>' . chr(10);
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);
299 $profile_key = md5($temp);
303 // Uniqueness assumend to be coupled to epoch timestamp
304 $timestamp_ = $today['mon'] . '-' . $today['mday'] . '-' . $today['year'];
307 * Lets make sure we have a log directory made for today
309 if (!is_dir(AT_CONTENT_DIR . 'logs/' . $timestamp_)) { // create it
310 $result = @mkdir(AT_CONTENT_DIR . 'logs/' . $timestamp_, 0771); // r+w for owner
313 $this->printError('Fatal. Could not create /content/logs' . '/' . $timestamp_ . '. Please resolve');
315 } // else already there
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
321 $dir_ = AT_CONTENT_DIR . 'logs/' . $timestamp_;
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');
331 * Run through the todays logs directory and lets get all the profiles
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) {
338 /* if the name is not a directory */
339 if( ($file == '.') || ($file == '..') || is_dir($file) ) {
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'));
347 if ($check_key === $profile_key) { // found!
348 $use_profile = $file;
349 $profile_created = true;
354 closedir($dir); // clean it up
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
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;
369 $timestamp = $timestamp_ . '_' . $today[0];
371 // create a unique error filename including the epoch timestamp + and the profile mapping
372 $unique_error_log = $timestamp . '_pr' . $profile_key;
374 if (is_file(AT_CONTENT_DIR . 'logs/' . $timestamp_ . '/' . $unique_error_log)) {
375 $unique_error_log .= rand(); // should be enough
378 $unique_error_log .= '.log.php'; // append suffix
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; }
384 $error_created = false;
386 fclose($file_handle);
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);
398 ÊÊ * Restores the error handler to the default error handler
403 function restoreOrigHandler() {
404 restore_error_handler();
408 Ê * Returns the error handler to ERROR_HOOK()
413 function returnHandler() {
414 set_error_handler(array(&$this, 'ERROR_HOOK'));
418 ÊÊ * Changes the logging preferences
420 * @param Boolean $error_flag Log errors to file?
421 ÊÊ * @param Boolean $warning_flag Log warnings to file?
425 function setFlags($error_flag = true, $warning_flag = true) {
427 $this->LOG_ERR_TO_FILE = $error_flag;
428 $this->LOG_WARN_TO_FILE = $warning_flag;
432 * Construct a nicely formatted tree view of a variable
433 * @param var String is the varialbe to construct the output from
436 function debug($var) {
441 $str = ob_get_contents();
444 $str = str_replace('<', '<', $str);
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);
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
461 function stripbase($str) {
463 $to_root = $_SERVER["PATH_TRANSLATED"];
465 $pos_last = strrpos($to_root, "/");
466 $to_root = substr($to_root, $pos_last + 1);
471 * Print the error to the browser, dont use any templates or css sheets for flexibility
474 function printError($str) {
475 if (!AT_DEVEL) return;
478 echo '<table bgcolor="#FF0000" border="0" cellpadding="3" cellspacing="2" width="90%" summary="" align="center">';
479 echo '<tr bgcolor="#FEF1F1" align="top">';
481 echo '<h3><span style="font-family: arial verdana">Internal Error Detected</span></small></h3>';
483 echo '<li><small><span style="font-family: arial verdana">'. $str .'</span></small></li>';
492 * Create restricted access logs dir
494 function makeLogDir() {
496 $result = @mkdir(AT_CONTENT_DIR . 'logs', 0771); // r+w for owner
498 $this->printError('Fatal. Could not create /content/logs. Please resolve');
504 * Determine wheter a log file exists for today
507 function todayLogFileExists() {
510 $timestamp = $today['mon'] . '-' . $today['mday'] . '-' . $today['year'];
512 return (is_file(AT_CONTENT_DIR . 'logs/' . $timestamp . '.log'));
516 * Run through $container and print all the errors on this page.
517 * Used to prevent errors from breaking content on the page
520 function showErrors() {
522 foreach($this->container as $elem) {
523 $this->printError('<strong>ATutor has detected an Error<strong> - ' .