%s:%s: %s: Assertion failed
", $file, $line, $code), $file, $line);
}
/**
* A class which allows custom handling of PHP errors.
*
* This is a singleton class. There should only be one instance
* of it --- you can access the one instance via $GLOBALS['ErrorManager'].
*
* FIXME: more docs.
*/
class ErrorManager
{
/**
* Constructor.
*
* As this is a singleton class, you should never call this.
* @access private
*/
function ErrorManager() {
$this->_handlers = array();
$this->_fatal_handler = false;
$this->_postpone_mask = 0;
$this->_postponed_errors = array();
set_error_handler('ErrorManager_errorHandler');
}
/**
* Get mask indicating which errors are currently being postponed.
* @access public
* @return int The current postponed error mask.
*/
function getPostponedErrorMask() {
return $this->_postpone_mask;
}
/**
* Set mask indicating which errors to postpone.
*
* The default value of the postpone mask is zero (no errors postponed.)
*
* When you set this mask, any queue errors which do not match the new
* mask are reported.
*
* @access public
* @param $newmask int The new value for the mask.
*/
function setPostponedErrorMask($newmask) {
$this->_postpone_mask = $newmask;
if (function_exists('PrintXML'))
PrintXML($this->_flush_errors($newmask));
else
echo($this->_flush_errors($newmask));
}
/**
* Report any queued error messages.
* @access public
*/
function flushPostponedErrors() {
if (function_exists('PrintXML'))
PrintXML($this->_flush_errors());
else
echo $this->_flush_errors();
}
/**
* Get postponed errors, formatted as HTML.
*
* This also flushes the postponed error queue.
*
* @return object HTML describing any queued errors (or false, if none).
*/
function getPostponedErrorsAsHTML() {
$flushed = $this->_flush_errors();
if (!$flushed)
return false;
if ($flushed->isEmpty())
return false;
// format it with the worst class (error, warning, notice)
//$class = 'notice';
$cur_err = new PhpError(0,"","","");
foreach ($flushed->_content as $err) {
if ($err and isa($err, 'PhpError') and $err->errno > $cur_err->errno) {
$cur_err = $err;
}
}
if ($cur_err->isNotice())
return $flushed;
$class = $cur_err->getHtmlClass();
$html = HTML::div(array('class' => $class),
HTML::h4(array('class' => 'errors'),
"PHP " . $cur_err->getDescription()));
$html->pushContent($flushed);
return $html;
}
/**
* Push a custom error handler on the handler stack.
*
* Sometimes one is performing an operation where one expects
* certain errors or warnings. In this case, one might not want
* these errors reported in the normal manner. Installing a custom
* error handler via this method allows one to intercept such
* errors.
*
* An error handler installed via this method should be either a
* function or an object method taking one argument: a PhpError
* object.
*
* The error handler should return either:
*
* - False
- If it has not handled the error. In this case,
* error processing will proceed as if the handler
* had never been called: the error will be passed
* to the next handler in the stack, or the
* default handler, if there are no more handlers
* in the stack.
*
*
- True
- If the handler has handled the error. If the
* error was a non-fatal one, no further processing
* will be done. If it was a fatal error, the
* ErrorManager will still terminate the PHP
* process (see setFatalHandler.)
*
*
- A PhpError object
- The error is not considered
* handled, and will be passed on to
* the next handler(s) in the stack
* (or the default handler). The
* returned PhpError need not be the
* same as the one passed to the
* handler. This allows the handler to
* "adjust" the error message.
*
* @access public
* @param $handler WikiCallback Handler to call.
*/
function pushErrorHandler($handler) {
array_unshift($this->_handlers, $handler);
}
/**
* Pop an error handler off the handler stack.
* @access public
*/
function popErrorHandler() {
return array_shift($this->_handlers);
}
/**
* Set a termination handler.
*
* This handler will be called upon fatal errors. The handler
* gets passed one argument: a PhpError object describing the
* fatal error.
*
* @access public
* @param $handler WikiCallback Callback to call on fatal errors.
*/
function setFatalHandler($handler) {
$this->_fatal_handler = $handler;
}
/**
* Handle an error.
*
* The error is passed through any registered error handlers, and
* then either reported or postponed.
*
* @access public
* @param $error object A PhpError object.
*/
function handleError($error) {
static $in_handler;
if (!empty($in_handler)) {
$msg = $error->_getDetail();
$msg->unshiftContent(HTML::h2(fmt("%s: error while handling error:",
"ErrorManager")));
$msg->printXML();
return;
}
$in_handler = true;
foreach ($this->_handlers as $handler) {
if (!$handler) continue;
$result = $handler->call($error);
if (!$result) {
continue; // Handler did not handle error.
}
elseif (is_object($result)) {
// handler filtered the result. Still should pass to
// the rest of the chain.
if ($error->isFatal()) {
// Don't let handlers make fatal errors non-fatal.
$result->errno = $error->errno;
}
$error = $result;
}
else {
// Handler handled error.
if (!$error->isFatal()) {
$in_handler = false;
return;
}
break;
}
}
$this->_noCacheHeaders();
// Error was either fatal, or was not handled by a handler.
// Handle it ourself.
if ($error->isFatal()) {
echo "Fatal Error:
\n";
if (defined('DEBUG') and (DEBUG & _DEBUG_TRACE)) {
echo "error_reporting=",error_reporting(),"\n
";
if (function_exists("debug_backtrace")) // >= 4.3.0
$error->printSimpleTrace(debug_backtrace());
}
$this->_die($error);
}
else if (($error->errno & error_reporting()) != 0) {
if (($error->errno & $this->_postpone_mask) != 0) {
if ((function_exists('is_a') and is_a($error,'PhpErrorOnce'))
or (!function_exists('is_a') and
(
// stdlib independent isa()
(strtolower(get_class($error)) == 'phperroronce')
or (is_subclass_of($error, 'PhpErrorOnce'))))) {
$error->removeDoublettes($this->_postponed_errors);
if ( $error->_count < 2 )
$this->_postponed_errors[] = $error;
} else {
$this->_postponed_errors[] = $error;
}
}
else {
//echo "postponed errors: ";
if (defined('DEBUG') and (DEBUG & _DEBUG_TRACE)) {
echo "error_reporting=",error_reporting(),"\n";
if (function_exists("debug_backtrace")) // >= 4.3.0
$error->printSimpleTrace(debug_backtrace());
}
$error->printXML();
}
}
$in_handler = false;
}
function warning($msg, $errno = E_USER_NOTICE) {
$this->handleError(new PhpWikiError($errno, $msg));
}
/**
* @access private
*/
function _die($error) {
//echo "\n\n";
$error->printXML();
PrintXML($this->_flush_errors());
if ($this->_fatal_handler)
$this->_fatal_handler->call($error);
exit -1;
}
/**
* @access private
*/
function _flush_errors($keep_mask = 0) {
$errors = &$this->_postponed_errors;
if (empty($errors)) return '';
$flushed = HTML();
for ($i=0; $ierrno & $keep_mask) != 0)
continue;
unset($errors[$i]);
$flushed->pushContent($error);
}
return $flushed;
}
function _noCacheHeaders() {
global $request;
static $already = false;
if (isset($request) and isset($request->_validators)) {
$request->_validators->_tag = false;
$request->_validators->_mtime = false;
}
if ($already) return;
// FIXME: Howto announce that to Request->cacheControl()?
if (!headers_sent()) {
header( "Cache-control: no-cache" );
header( "Pragma: nocache" );
}
$already = true;
}
}
/**
* Global error handler for class ErrorManager.
*
* This is necessary since PHP's set_error_handler() does not allow
* one to set an object method as a handler.
*
* @access private
*/
function ErrorManager_errorHandler($errno, $errstr, $errfile, $errline)
{
if (!isset($GLOBALS['ErrorManager'])) {
$GLOBALS['ErrorManager'] = new ErrorManager;
}
$error = new PhpErrorOnce($errno, $errstr, $errfile, $errline);
$GLOBALS['ErrorManager']->handleError($error);
}
/**
* A class representing a PHP error report.
*
* @see The PHP documentation for set_error_handler at
* http://php.net/manual/en/function.set-error-handler.php .
*/
class PhpError {
/**
* The PHP errno
*/
//var $errno;
/**
* The PHP error message.
*/
//var $errstr;
/**
* The source file where the error occurred.
*/
//var $errfile;
/**
* The line number (in $this->errfile) where the error occured.
*/
//var $errline;
/**
* Construct a new PhpError.
* @param $errno int
* @param $errstr string
* @param $errfile string
* @param $errline int
*/
function PhpError($errno, $errstr, $errfile, $errline) {
$this->errno = $errno;
$this->errstr = $errstr;
$this->errfile = $errfile;
$this->errline = $errline;
}
/**
* Determine whether this is a fatal error.
* @return boolean True if this is a fatal error.
*/
function isFatal() {
return ($this->errno & (2048|EM_WARNING_ERRORS|EM_NOTICE_ERRORS)) == 0;
}
/**
* Determine whether this is a warning level error.
* @return boolean
*/
function isWarning() {
return ($this->errno & EM_WARNING_ERRORS) != 0;
}
/**
* Determine whether this is a notice level error.
* @return boolean
*/
function isNotice() {
return ($this->errno & EM_NOTICE_ERRORS) != 0;
}
function getHtmlClass() {
if ($this->isNotice()) {
return 'hint';
} elseif ($this->isWarning()) {
return 'errors';
} else {
return 'errors';
}
}
function getDescription() {
if ($this->isNotice()) {
return 'Notice';
} elseif ($this->isWarning()) {
return 'Warning';
} else {
return 'Error';
}
}
/**
* Get a printable, HTML, message detailing this error.
* @return object The detailed error message.
*/
function _getDetail() {
$dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__),0,-4);
if (substr(PHP_OS,0,3) == 'WIN') {
$dir = str_replace('/','\\',$dir);
$this->errfile = str_replace('/','\\',$this->errfile);
$dir .= "\\";
} else
$dir .= '/';
$errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
$lines = explode("\n", $this->errstr);
if (DEBUG & _DEBUG_VERBOSE) {
$msg = sprintf("%s:%d: %s[%d]: %s",
$errfile, $this->errline,
$this->getDescription(), $this->errno,
array_shift($lines));
} else {
$msg = sprintf("%s:%d: %s: \"%s\"",
$errfile, $this->errline,
$this->getDescription(),
array_shift($lines));
}
//$html = HTML::div(array('class' => $this->getHtmlClass()), HTML::p($msg));
// The class is now used for the div container.
$html = HTML::div(HTML::p($msg));
if ($lines) {
$list = HTML::ul();
foreach ($lines as $line)
$list->pushContent(HTML::li($line));
$html->pushContent($list);
}
return $html;
}
/**
* Print an HTMLified version of this error.
* @see asXML()
*/
function printXML() {
PrintXML($this->_getDetail());
}
/**
* Return an HTMLified version of this error.
*/
function asXML() {
return AsXML($this->_getDetail());
}
/**
* Return a plain-text version of this error.
*/
function asString() {
return AsString($this->_getDetail());
}
function printSimpleTrace($bt) {
global $HTTP_SERVER_VARS;
$nl = isset($HTTP_SERVER_VARS['REQUEST_METHOD']) ? "
" : "\n";
echo $nl."Traceback:".$nl;
foreach ($bt as $i => $elem) {
if (!array_key_exists('file', $elem)) {
continue;
}
print " " . $elem['file'] . ':' . $elem['line'] . $nl;
}
flush();
}
}
/**
* A class representing a PhpWiki warning.
*
* This is essentially the same as a PhpError, except that the
* error message is quieter: no source line, etc...
*/
class PhpWikiError extends PhpError {
/**
* Construct a new PhpError.
* @param $errno int
* @param $errstr string
*/
function PhpWikiError($errno, $errstr) {
$this->PhpError($errno, $errstr, '?', '?');
}
function _getDetail() {
return HTML::div(//array('class' => $this->getHtmlClass()),
HTML::p($this->getDescription() . ": $this->errstr"));
}
}
/**
* A class representing a Php warning, printed only the first time.
*
* Similar to PhpError, except only the first same error message is printed,
* with number of occurences.
*/
class PhpErrorOnce extends PhpError {
function PhpErrorOnce($errno, $errstr, $errfile, $errline) {
$this->_count = 1;
$this->PhpError($errno, $errstr, $errfile, $errline);
}
function _sameError($error) {
if (!$error) return false;
return ($this->errno == $error->errno and
$this->errfile == $error->errfile and
$this->errline == $error->errline);
}
// count similar handlers, increase _count and remove the rest
function removeDoublettes(&$errors) {
for ($i=0; $i < count($errors); $i++) {
if (!isset($errors[$i])) continue;
if ($this->_sameError($errors[$i])) {
$errors[$i]->_count++;
$this->_count++;
if ($i) unset($errors[$i]);
}
}
return $this->_count;
}
function _getDetail($count=0) {
if (!$count) $count = $this->_count;
$dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : substr(dirname(__FILE__),0,-4);
if (substr(PHP_OS,0,3) == 'WIN') {
$dir = str_replace('/','\\',$dir);
$this->errfile = str_replace('/','\\',$this->errfile);
$dir .= "\\";
} else
$dir .= '/';
$errfile = preg_replace('|^' . preg_quote($dir) . '|', '', $this->errfile);
$lines = explode("\n", $this->errstr);
$errtype = (DEBUG & _DEBUG_VERBOSE) ? sprintf("%s[%d]", $this->getDescription(), $this->errno)
: sprintf("%s", $this->getDescription());
$msg = sprintf("%s:%d: %s: %s %s",
$errfile, $this->errline,
$errtype,
array_shift($lines),
$count > 1 ? sprintf(" (...repeated %d times)",$count) : ""
);
$html = HTML::div(//array('class' => $this->getHtmlClass()),
HTML::p($msg));
if ($lines) {
$list = HTML::ul();
foreach ($lines as $line)
$list->pushContent(HTML::li($line));
$html->pushContent($list);
}
return $html;
}
}
require_once(dirname(__FILE__).'/HtmlElement.php');
if (!isset($GLOBALS['ErrorManager'])) {
$GLOBALS['ErrorManager'] = new ErrorManager;
}
// $Log: not supported by cvs2svn $
// Revision 1.40 2004/12/13 14:39:46 rurban
// aesthetics
//
// Revision 1.39 2004/11/05 18:04:20 rurban
// print errno only if _DEBUG_VERBOSE
//
// Revision 1.38 2004/10/19 17:34:55 rurban
// <4.3 fix
//
// Revision 1.37 2004/10/14 19:23:58 rurban
// remove debugging prints
//
// Revision 1.36 2004/10/12 15:35:43 rurban
// avoid Php Notice header
//
// Revision 1.35 2004/10/12 13:13:19 rurban
// php5 compatibility (5.0.1 ok)
//
// Revision 1.34 2004/09/24 18:52:19 rurban
// in deferred html error messages use the worst header and class
// (notice => warning => errors)
//
// Revision 1.33 2004/09/14 10:28:21 rurban
// use assert, maybe we should only turn it off for releases
//
// Revision 1.32 2004/07/08 13:50:32 rurban
// various unit test fixes: print error backtrace on _DEBUG_TRACE; allusers fix; new PHPWIKI_NOMAIN constant for omitting the mainloop
//
// Revision 1.31 2004/07/02 09:55:58 rurban
// more stability fixes: new DISABLE_GETIMAGESIZE if your php crashes when loading LinkIcons: failing getimagesize in old phps; blockparser stabilized
//
// Revision 1.30 2004/06/25 14:29:12 rurban
// WikiGroup refactoring:
// global group attached to user, code for not_current user.
// improved helpers for special groups (avoid double invocations)
// new experimental config option ENABLE_XHTML_XML (fails with IE, and document.write())
// fixed a XHTML validation error on userprefs.tmpl
//
// Revision 1.29 2004/06/20 15:30:04 rurban
// get_class case-sensitivity issues
//
// Revision 1.28 2004/06/16 11:51:04 rurban
// fixed typo: undefined object #235
//
// Revision 1.27 2004/06/13 09:38:20 rurban
// isa() workaround, if stdlib.php is not loaded
//
// Revision 1.26 2004/06/02 18:01:45 rurban
// init global FileFinder to add proper include paths at startup
// adds PHPWIKI_DIR if started from another dir, lib/pear also
// fix slashify for Windows
// fix USER_AUTH_POLICY=old, use only USER_AUTH_ORDER methods (besides HttpAuth)
//
// Revision 1.25 2004/06/02 10:18:36 rurban
// assert only if DEBUG is non-false
//
// Revision 1.24 2004/05/27 17:49:05 rurban
// renamed DB_Session to DbSession (in CVS also)
// added WikiDB->getParam and WikiDB->getAuthParam method to get rid of globals
// remove leading slash in error message
// added force_unlock parameter to File_Passwd (no return on stale locks)
// fixed adodb session AffectedRows
// added FileFinder helpers to unify local filenames and DATA_PATH names
// editpage.php: new edit toolbar javascript on ENABLE_EDIT_TOOLBAR
//
//
// (c-file-style: "gnu")
// Local Variables:
// mode: php
// tab-width: 8
// c-basic-offset: 4
// c-hanging-comment-ender-p: nil
// indent-tabs-mode: nil
// End:
?>