<?php
/*
**	$Id$
**
**	Copyright (C) 1995-2007 by the ITools Authors.
**	This file is part of ITools - the Internet Tools Library
**
**	ITools is free software; you can redistribute it and/or modify
**	it under the terms of the GNU General Public License as published by
**	the Free Software Foundation; either version 3 of the License, or
**	(at your option) any later version.
**
**	ITools is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**	GNU General Public License for more details.
**
**	You should have received a copy of the GNU General Public License
**	along with this program.  If not, see <http://www.gnu.org/licenses/>.
**
**	debug.class - Debug Functionality for ITools
*/

/**
 * Debug functions
 */
class it_debug
{
	var $level;

/**
 * Constructor
 * @param $level Debug level. All debug() calls with a level <= $level are shown.
 */
function it_debug($level=0)
{
	$this->level = isset($GLOBALS['debug_level']) ? $GLOBALS['debug_level'] : $level;
}


/**
 * Output a message if the global debug level is higher or the same as $level
 * @param $text Message to display
 * @param $level Debug level
 */
function debug($text, $level = 0)
{
	if ($this->level >= $level)
	{
		echo (isset($_SERVER['REMOTE_ADDR']) ? nl2br("$level $text\n") : "$level $text")."\n";
		flush();
	}
}

/**
 * Get source line of grandparent calling this function
 * @param $stacksoffs go up an extra $stacksoffs levels
 */

function srcline($stackoffs = 0)
{
	$stack = debug_backtrace();
	$line = $stack[1 + $stackoffs]['line'];
	$file = $stack[1 + $stackoffs]['file'];

	if (!isset($GLOBALS['it_debug::dump source'][$file]))
		$GLOBALS['it_debug::dump source'][$file] = file($file);

	if (preg_match('/^#!/', $GLOBALS['it_debug::dump source'][$file][0]))
		$line++;

	return $GLOBALS['it_debug::dump source'][$file][$line-1];
}


/**
 * Backend for functions D(), ED() and EDX() in functions.php
 * @param Origargs Array containing original arguments do ED() etc
 * @return String representation of dump
 */
function dump($args)
{
	if (preg_match('/csv|txt|gif|jpg/', $_SERVER['PHP_SELF']))
		$plain = 1;
	else if ($_SERVER['REMOTE_ADDR'])
	{
		$blue = "<span style='color:#00c'>";
		$noblue = "</span>";
	}
	else if (posix_isatty(STDOUT))
		list($blue, $noblue, $red, $nored) = getenv('IT_ED_BRIGHT') ? array("\033[34m", "\033[m", "\033[33m", "\033[m") : array("\033[34m", "\033[m", "\033[31m", "\033[m");

	@fseek(STDOUT, 0, SEEK_END);	# Work around PHP bug 49819: posix_isatty seeks to start
	$src = it_debug::srcline(1);
	list($function, $paramlist) = it::match('\b(D|ED|EDC|EDX)\s*\((.*)', $src);
	$paramtokens = token_get_all("<?php $paramlist");
	array_shift($paramtokens);
	$param = "";
	$argnames = array();
	foreach ($paramtokens as $token)
	{
		if ($token == "(")
			$paramnesting++;
		else if ($token == ")")
		{
			if (!$paramnesting--)	# Found closing parens
			{
				$argnames[] = trim($param);
				break;
			}
		}

		if (($token == ",") && !$paramnesting)
		{
			$argnames[] = trim($param);
			$param = "";
		}
		else
			$param .= is_array($token) ? $token[1] : $token;
	}

	if (it::match('^EDC$', $function))	# First argument was removed by EDC
		array_shift($argnames);

	foreach ($args as $arg)
	{
		$var = array_shift($argnames);
		$item = gettype($arg) == 'resource' || is_array($arg) && isset($arg['GLOBALS']) || is_a($arg, "SimpleXMLElement") ? trim(print_r($arg, true)) : trim(var_export($arg, true));

		# Replace PHP 5 var_export object representation by old style
		while (preg_match("#(.*\b)(\w+)::__set_state\(array\(([^()]+)\)\)(.*)#s", $item, $regs))
		{
			list (, $head, $classname, $values, $tail) = $regs;
			$classname = strtolower($classname);
			$values = preg_replace("#'(\w+)' =>\s*([^\n]+),#", 'var \$$1 = $2;', $values);
			$item = $head . "class $classname { $values }$tail";
		}

		$item = preg_replace('/\b(1[234]\d\d\d\d\d\d\d\d)\b(.*)/e', "'$1$2 # ' . date('Y-m-d H:i:s', '$1')", $item);
		$item = preg_replace("#(=>?)\s*\n\s*(array|class)#", '$1 $2', $item); # array( and class on same line as key
		$item = preg_replace('#array \(\s+([^({,;]+),\s+\)#', 'array( $1 )', $item); # 1-element arrays on 1 line
		$item = preg_replace('#class (\S+) \{\s+([^({,;]+;)?\s+\}#', 'class $1 { $2 }', $item); # 1-element objects on 1 line
		#$item = preg_replace('#\{\s*var \$attr#', '{ var $attr', $item); # move $attr on same line
		$item = preg_replace("#\\(\s*\n\s*\\)#", "()", $item);	   # empty arrays on 1 line
		#$item = preg_replace('#\s+var \$_(.|\n)*?;\s*\n#', "", $item);
		$item = "$red$item$nored";

		if (isset($_SERVER['REMOTE_ADDR']) && !$plain)
			$item = htmlspecialchars($item);

		if (preg_match('/^[\'"]/', $var))
			$r .= "$item ";
		else {
			$var = "$blue$var=$noblue";
			$r .= $previtem && preg_match("/\n/", $item . $previtem) ? "\n$var$item " : "$var$item ";
			$previtem = $item;
		}
	}

	if ($GLOBALS['debug_indent'])
		$r = str_repeat("  ", count(debug_backtrace())-3) . $r;

	# not it_html::Q(); we dont wanna load it_html in ultraclassloader debugging
	$title = htmlspecialchars(it_debug::backtrace(array('skiplevels' => 1)));

	if (isset($_SERVER['REMOTE_ADDR']) && !$plain)
		 return "<pre title='$title' style='color:#c00; text-align:left; background-color:white; margin:0; padding:0.25em; border-bottom:1px solid #ddd'>$r</pre>\n";
	else
		 return "$r\n";
}

/**
 * Return short stackdump
 * @param $p['levels'] number of stack levels to return (default: 0 = all)
 * @param $p['skiplevels'] number of stack levels to omit
 * @param $p['skipfiles'] regular expression of filenames to omit
 */
function backtrace($p = array())
{
	if (!is_array($p))
		$p = array('skiplevels' => $p);

	$p += array('levels' => 0, 'skiplevels'=> 0, 'skipfiles' => "###");

	if (!function_exists('memory_get_usage') || (memory_get_usage() < 50000000))
	{
		foreach (array_slice(debug_backtrace(), $p['skiplevels'] + 1) as $call)
		{
			if (($fn = $call['file']) && !it::match($p['skipfiles'], $call['file']))
			{
				$fn = (it::match('auto_prepend', $fn) ? basename(dirname(dirname($fn))) . "/" : "") . basename($fn);
				$result[] = $fn . ":" . $call['line'];

				if (--$p['levels'] == 0)
					break;
			}
		}
	}

	return join(" ", (array)$result);
}

}
?>