<?php
/**
* Utility static class
* @package Error_Raise
* @version 0.2.2
* @author Greg Beaver hide@address.com
*/
/**
* Error used if a class passed to initialize hasn't been defined yet
*
* {@link initialize()} allows
*/
define('ERROR_UTIL_ERROR_CLASS_DOESNT_EXIST', 1);
/**
* Error used if a method doesn't exist in a callback passed in
*/
define('ERROR_UTIL_ERROR_METHOD_DOESNT_EXIST', 2);
/**
* Error used if a function doesn't exist in a callback passed in
*/
define('ERROR_UTIL_ERROR_FUNCTION_DOESNT_EXIST', 3);
/**
* Error used when parameters to functions don't match expected types
*/
define('ERROR_UTIL_ERROR_INVALID_INPUT', 4);
/**
* Error used when an internal function is passed as a callback - this is never
* allowed.
*/
define('ERROR_UTIL_ERROR_INTERNAL_FUNCTION', 5);
/**
* Utility functions for Error display
*
* This class is used for advanced retrieval of context information, and for
* callback validation. It also has a few miscellaneous functions for processing
* display of variables.
* @package Error_Raise
* @version 0.2.2
* @author Greg Beaver hide@address.com
* @static
*/
class Error_Util {
/**
* Extracted from {@link http://mojavelinux.com/forum/viewforum.php?f=4}
* originally by Dan Allen
* @param string full path to source file that triggered a PHP error
* @param string line number of error
* @static
*/
function getErrorContext($file, $line, $contextLines = 5)
{
if (!file_exists($file) || !is_readable($file)) {
return array(
'start' => 0,
'end' => 0,
'source' => '',
'variables' => array(),
);
}
$sourceLines = file($file);
$offset = max($line - 1 - $contextLines, 0);
$numLines = 2 * $contextLines + 1;
$sourceLines = array_slice($sourceLines, $offset, $numLines);
$numLines = count($sourceLines);
// add line numbers
foreach ($sourceLines as $index => $line) {
$sourceLines[$index] = ($offset + $index + 1) . ': ' . $line;
}
$source = Error_Util::_addPhpTags(join('', $sourceLines));
preg_match_all(';\$([[:alnum:]]+);', $source, $matches);
$variables = array_values(array_unique($matches[1]));
return array(
'start' => $offset + 1,
'end' => $offset + $numLines,
'source' => $source,
'variables' => $variables,
);
}
/**
* Extracted from {@link http://mojavelinux.com/forum/viewforum.php?f=4}
* originally by Dan Allen
* @param array list of variables found in code context
* @param array list of variables from trigger_error context
* @param boolean if false, then all variables in $variables will be dumped
* @param array list of classes to exclude from var_export
* @param string line number of error
* @return string|false
* @static
*/
function exportVariables($variables, $contextVariables, $strict = true,
$excludeClasses = array())
{
$variableString = '';
foreach ($variables as $name => $contents) {
// if we are using strict context and this variable is
// not in the context, skip it
if ($strict && !in_array($name, $contextVariables)) {
continue;
}
// if this is an object and the class is in the exclude list, skip it
if (is_object($contents) && in_array(get_class($contents),
$excludeClasses)) {
continue;
}
$variableString .= '$' . $name . ' = ' .
Error_Util::var_export2($contents, true) . ';' . "\n";
}
if (empty($variableString)) {
return false;
} else {
return "\n" . $variableString;
}
}
/**
* Extracted from {@link http://mojavelinux.com/forum/viewforum.php?f=4}
* originally by Dan Allen
* @param string string that should be escaped for JS
* @return string
* @static
*/
function escapeJavascript($string)
{
return strtr($string, array(
"\t" => '\\t',
"\n" => '\\n',
"\r" => '\\r',
'\\' => '\',
"'" => '''));
}
/**
* Copied from {@link http://www.php.net/strrpos}, comment by
* DONT SPAM vardges at iqnest dot com
* @param string full string
* @param string search string
* @param integer offset from the start of the string to begin searching
* from
* @static
*/
function strrpos_str ($string, $searchFor, $startFrom = 0)
{
$addLen = strlen ($searchFor);
$endPos = $startFrom - $addLen;
while (true) {
if (($newPos = strpos ($string, $searchFor,
$endPos + $addLen)) === false) {
break;
}
$endPos = $newPos;
}
return ($endPos >= 0) ? $endPos : false;
}
/**
* Add PHP Tags if necessary to variable context PHP for highlighting
*
* Extracted from {@link http://mojavelinux.com/forum/viewforum.php?f=4}
* originally by Dan Allen
* @param string source code for context around an error
* @static
*/
function _addPhpTags($source)
{
$startTag = '<?php';
$endTag = '?>';
if (($pos = strpos($source, $startTag)) !== false) {
$firstStartPos = $pos;
} else {
$firstStartPos = -1;
}
if (($pos = strpos($source, $endTag)) !== false) {
$firstEndPos = $pos;
} else {
$firstEndPos = -1;
}
// no tags found then it must be solid php since
// html can't throw a php error
if ($firstStartPos < 0 && $firstEndPos < 0) {
return $startTag . "\n" . $source . "\n" . $endTag;
}
// found an end tag first, so we are missing a start tag
if ($firstEndPos >= 0 &&
($firstStartPos < 0 || $firstStartPos > $firstEndPos)) {
$source = $startTag . "\n" . $source;
}
$sourceLength = strlen($source);
if (($pos = Error_Util::strrpos_str($source, $startTag)) !== false) {
$lastStartPos = $pos;
} else {
$lastStartPos = $sourceLength + 1;
}
if (($pos = Error_Util::strrpos_str($source, $endTag)) !== false) {
$lastEndPos = $pos;
} else {
$lastEndPos = $sourceLength + 1;
}
if ($lastEndPos < $lastStartPos || ($lastEndPos > $lastStartPos
&& $lastEndPos > $sourceLength)) {
$source .= $endTag;
}
return $source;
}
/**
* More advanced var_export for HTML/Javascript, private recursive function
*
* Extracted from {@link http://mojavelinux.com/forum/viewforum.php?f=4}
* originally by Dan Allen
* @param mixed variable to var_export
* @param string indentation
* @param boolean is an array portion of the variable
* @param integer recursion level
* @access private
* @static
*/
function &_var_export2(&$variable, $arrayIndent = '', $inArray = false,
$level = 0)
{
static $maxLevels = 5, $followObjectReferences = false;
if ($inArray != false) {
$leadingSpace = '';
$trailingSpace = ',' . "\n";
} else {
$leadingSpace = $arrayIndent;
$trailingSpace = '';
}
$result = '';
switch (gettype($variable))
{
case 'object':
if ($inArray && !$followObjectReferences)
{
$result = '*' . get_class($variable) . ' REFERENCE*';
$trailingSpace = "\n";
break;
}
case 'array':
if ($maxLevels && $level >= $maxLevels) {
$result = '** truncated, too much recursion **';
} else {
$result = "\n" . $arrayIndent . 'array (' . "\n";
foreach ($variable as $key => $value) {
$result .= $arrayIndent . ' ' . (is_int($key)
? $key : ('\'' .
str_replace('\'', '\\\'', $key) . '\'')) . ' => ' .
Error_Util::_var_export2($value,
$arrayIndent . ' ', true, $level + 1);
}
$result .= $arrayIndent . ')';
}
break;
case 'string':
$result = '\'' . str_replace('\'', '\\\'', $variable) . '\'';
break;
case 'boolean':
$result = $variable ? 'true' : 'false';
break;
case 'NULL':
$result = 'NULL';
break;
case 'resource':
$result = get_resource_type($variable);
break;
default:
$result = $variable;
break;
}
return $leadingSpace . $result . $trailingSpace;
}
/**
* More advanced var_export for HTML/Javascript, private recursive function
*
* Extracted from {@link http://mojavelinux.com/forum/viewforum.php?f=4}
* originally by Dan Allen
* @param mixed variable to var_export
* @param boolean prints output by default, set to true to return a string
* @static
*/
function var_export2(&$variable, $return = false)
{
$result =& Error_Util::_var_export2($variable);
if ($return) {
return $result;
} else {
echo $result;
}
}
/**
* calls {@link file_exists()} for each value in include_path,
* then calls {@link is_readable()} when it finds the file.
*
* This doesn't really belong here, but is useful
* @param string
* @return boolean
*/
function isIncludeable($filename)
{
$ip = get_include_path();
if (substr(PHP_OS, 0, 3) == 'WIN') {
$ip = explode(';', $ip);
} else {
$ip = explode(':', $ip);
}
foreach($ip as $path) {
if ($a = realpath($path . DIRECTORY_SEPARATOR . $filename)) {
if (is_readable($a)) {
return true;
}
}
}
return false;
}
/**
* Default file/line number grabber function
*
* This function uses a backtrace generated from {@link debug_backtrace()}
* and so will not work at all in PHP < 4.3.0. The frame should
* reference the frame that contains the source of the error. See how
* raise() implements this in the source code for a very specific idea
* of how to do this in your own code, if you won't be using one of the
* standard error-throwing methods
* @return array|false either array('_file' => file, '_line' => line,
* '_function' => function name, '_class' => class name) or
* if this doesn't work, then false
* @param array Results of debug_backtrace()
* @param integer backtrace frame.
*/
function _getFileLine($backtrace = null, $frame = 0, $functionframe = 1)
{
if (isset($backtrace) && is_array($backtrace) &&
isset($backtrace[$frame])) {
if (!isset($backtrace[$frame]['file'])) {
$frame++;
}
$funcbacktrace = $backtrace[$functionframe];
$filebacktrace = $backtrace[$frame];
$ret = array('_file' => $filebacktrace['file'],
'_line' => $filebacktrace['line']);
// rearrange for eval'd code or create function errors
if (preg_match(';^(.*?)\((\d+)\) : (.*?)$;', $filebacktrace['file'],
$matches)) {
$ret['_file'] = $matches[1];
$ret['_line'] = $matches[2] + 0;
}
if (isset($funcbacktrace['function'])) {
$ret['_function'] = $funcbacktrace['function'];
}
if (isset($funcbacktrace['class'])) {
$ret['_class'] = $funcbacktrace['class'];
}
return $ret;
}
return false;
}
function formatStackTrace($trace)
{
}
/**
* Parse a backtrace to retrieve the calling frame
*
* WARNING: do not attempt to use this in any code outside of
* Error_Raise::raise(), it just won't work at all
* @access private
* @param array debug_backtrace() output from {@link Error_Raise::raise()}
* @param string warning/error/notice/exception
* @param Error_Raise_Error error object
*/
function _parseBacktrace($trace, $errorType, &$error)
{
if (!isset($trace[1])) {
return array(0, 0);
}
$functionframe = $frame = 1; // get calling function backtrace
if (isset($trace[1]['class'])) {
if (isset($trace[1]['function']) &&
$trace[1]['function'] != $errorType) {
// raise was called directly
$frame = 0;
}
$testclass = $trace[$functionframe]['class'];
if (str_replace($error->getPackage(), '', $testclass) ==
'_raise' && isset($trace[$functionframe])) {
$functionframe++;
}
}
while (isset($trace[$functionframe]['function']) &&
in_array($trace[$functionframe]['function'],
array('eval', '__lambda_func')) &&
isset($trace[$functionframe + 1])) {
$functionframe++;
}
return array($frame, $functionframe);
}
/**
* Verify that $callback is a valid function callback
*
* This is used to be absolutely sure a callback is valid before registering
* it, to avoid later errors on the throwing of an error
* @param string|array
* @return true|Error_Raise_Error
* @throws ERROR_UTIL_ERROR_FUNCTION_DOESNT_EXIST If the callback is a
* string and isn't the name of any function
* @throws ERROR_UTIL_ERROR_INTERNAL_FUNCTION If the callback is the name
* of an internal, pre-defined function like "function_exists"
* @throws ERROR_UTIL_ERROR_INVALID_INPUT If the callback is neither
* a string, an array(classname, method), or an array(object, method)
* @throws ERROR_UTIL_ERROR_METHOD_DOESNT_EXIST if the callback is an
* array, and the method is not a method of the class
* @access private
*/
function _validCallback($callback)
{
static $init = false;
if (!$init) {
$init = true;
Error_Raise::setErrorMsgGenerator('Error_Util',
array('Error_Util', 'genErrorMessage'));
}
if (is_string($callback)) {
if (!function_exists($callback)) {
return Error_Raise::exception('Error_Util',
ERROR_UTIL_ERROR_FUNCTION_DOESNT_EXIST,
array('function' => $callback));
}
$a = get_defined_functions();
if (in_array($callback, $a['internal'])) {
return Error_Raise::exception('Error_Util',
ERROR_UTIL_ERROR_INTERNAL_FUNCTION,
array('function' => $callback));
}
return true;
}
if (is_array($callback)) {
if (!isset($callback[0]) || !isset($callback[1])) {
return Error_Raise::exception('Error_Util',
ERROR_UTIL_ERROR_INVALID_INPUT,
array('expected' => array(0, 1),
'was' => array_keys($callback),
'var' => 'array_keys($callback)',
'paramnum' => 1));
}
if (is_string($callback[0])) {
if (!is_string($callback[1])) {
return Error_Raise::exception('Error_Util',
ERROR_UTIL_ERROR_INVALID_INPUT,
array('expected' => 'string',
'was' => gettype($callback[1]),
'var' => '$callback[1]',
'paramnum' => 1));
}
if (!class_exists($callback[0])) {
return Error_Raise::exception('Error_Util',
ERROR_UTIL_ERROR_CLASS_DOESNT_EXIST,
array('class' => $callback[0]));
}
if (!in_array(strtolower($callback[1]),
get_class_methods($callback[0]))) {
return Error_Raise::exception('Error_Util',
ERROR_UTIL_ERROR_METHOD_DOESNT_EXIST,
array('method' => $callback[1],
'class' => $callback[0]));
}
return true;
} elseif (is_object($callback[0])) {
if (!method_exists($callback[0], $callback[1])) {
return Error_Raise::exception('Error_Util',
ERROR_UTIL_ERROR_METHOD_DOESNT_EXIST,
array('method' => $callback[1],
'class' => get_class($callback[0])));
}
return true;
} else {
return Error_Raise::exception('Error_Util',
ERROR_UTIL_ERROR_INVALID_INPUT,
array('expected' => array('array', 'string'),
'was' => gettype($callback[0]),
'var' => '$callback[0]',
'paramnum' => 1));
}
// is a callback method
return true;
}
return Error_Raise::exception('error_util',
ERROR_UTIL_ERROR_INVALID_INPUT,
array('expected' => array('array', 'string'),
'was' => gettype($callback),
'var' => '$callback',
'paramnum' => 1));;
}
/**
* Get an error message for Error_Util errors
* @return string error message from error code
* @param integer
* @param array
* @static
*/
function genErrorMessage($code, $args = array(), $state = ERROR_RAISE_TEXT)
{
if (!is_array($args)) {
return 'Error: $args passed to Error_Util::genErrorMessage is '.
'not an array but a '.gettype($args);
}
$messages =
array(
ERROR_UTIL_ERROR_CLASS_DOESNT_EXIST =>
'class "%cl%" does not exist',
ERROR_UTIL_ERROR_INVALID_INPUT =>
'invalid input, parameter #%paramnum% '
. '"%var%" was expecting '
. '"%expected%", instead got "%was%"',
ERROR_UTIL_ERROR_METHOD_DOESNT_EXIST =>
'method "%method%" doesn\'t exist in class "%class%"',
ERROR_UTIL_ERROR_FUNCTION_DOESNT_EXIST =>
'function "%function%" doesn\'t exist',
ERROR_UTIL_ERROR_INTERNAL_FUNCTION =>
'function "%function%" is an internal function, and '
. 'cannot be used as a callback',
);
if (is_int($code) && isset($messages[$code])) {
$msg = $messages[$code];
return Error_Raise::sprintfErrorMessageWithState($msg,
$args, $state);
} else {
return 'Error: code ' . $code . ' not found';
}
}
}
?>