<?php

	if( !class_exists('Debugger') ) {
		class Debugger {

			/**
			 * Set to true if fatal error occured
			 *
			 * @var bool
			 */
			var $IsFatalError = false;

			/**
			 * Debugger data for building report
			 *
			 * @var Array
			 */
			var $Data = Array();

			var $ProfilerData = Array();
			var $ProfilerTotals = Array();
			var $ProfilerTotalCount = Array();

			var $ProfilePoints = Array();

			/**
			 * Prevent recursion when processing debug_backtrace() function results
			 *
			 * @var Array
			 */
			var $RecursionStack = Array();

			var $scrollbarWidth = 0;

			/**
			 * Long errors are saved here, because trigger_error doesn't support error messages over 1KB in size
			 *
			 * @var Array
			 */
			var $longErrors = Array();

			var $IncludesData = Array();
			var $IncludeLevel = 0;

			var $reportDone = false;

			/**
			 * Transparent spacer image used in case of none spacer image defined via SPACER_URL contant.
			 * Used while drawing progress bars (memory usage, time usage, etc.)
			 *
			 * @var string
			 */
			var $dummyImage = '';

			/**
			 * Temporary files created by debugger will be stored here
			 *
			 * @var string
			 */
			var $tempFolder = '';

			/**
			 * Debug rows will be separated using this string before writing to debug file
			 *
			 * @var string
			 */
			var $rowSeparator = '@@';

			/**
			 * Base URL for debugger includes
			 *
			 * @var string
			 */
			var $baseURL = '';

			function Debugger()
			{
				global $start, $dbg_options;
				// check if user haven't defined DEBUG_MODE contant directly
				if (defined('DEBUG_MODE') && DEBUG_MODE) {
					die('error: contant DEBUG_MODE defined directly, please use <strong>$dbg_options</strong> array instead');
				}

				// check IP before enabling debug mode
				$ip_match =  $this->ipMatch(isset($dbg_options['DBG_IP']) ? $dbg_options['DBG_IP'] : '');

				if (!$ip_match) {
					define('DEBUG_MODE', 0);
					return ;
				}

				// debug is allowed for user, continue initialization
				$this->InitDebugger();
				$this->profileStart('kernel4_startup', 'Startup and Initialization of kernel4', $start);
				$this->profileStart('script_runtime', 'Script runtime', $start);

				error_reporting(E_ALL);
				ini_set('display_errors', $this->constOn('DBG_ZEND_PRESENT') ? 0 : 1);	// show errors on screen in case if not in Zend Studio debugging

				$this->scrollbarWidth = $this->isGecko() ? 22 : 25;					// vertical scrollbar width differs in Firefox and other browsers
				$this->appendRequest();
			}

			/**
			 * Checks, that user IP address is within allowed range
			 *
			 * @param string $ip_list semi-column (by default) separated ip address list
			 * @param string $separator ip address separator (default ";")
			 *
			 * @return bool
			 */
			function ipMatch($ip_list, $separator = ';')
			{
				$ip_match = false;
				$ip_addresses = $ip_list ? explode($separator, $ip_list) : Array ();
				foreach ($ip_addresses as $ip_address) {
					if ($this->netMatch($ip_address, $_SERVER['REMOTE_ADDR'])) {
						$ip_match = true;
						break;
					}
				}

				return $ip_match;
			}

			/**
			 * Set's default values to constants debugger uses
			 *
			 */
			function InitDebugger()
			{
				global $dbg_options;

				unset($_REQUEST['debug_host'], $_REQUEST['debug_fastfile'], $dbg_options['DBG_IP']);	// this var messed up whole detection stuff :(

				// Detect fact, that this session beeing debugged by Zend Studio
				foreach ($_REQUEST as $rq_name => $rq_value) {
					if (substr($rq_name, 0, 6) == 'debug_') {
						$this->safeDefine('DBG_ZEND_PRESENT', 1);
						break;
					}
				}

				$this->safeDefine('DBG_ZEND_PRESENT', 0); // set this constant value to 0 (zero) to debug debugger using Zend Studio

				// set default values for debugger constants
				$dbg_constMap = Array(
										'DBG_USE_HIGHLIGHT'			=>	1,							// highlight output same as php code using "highlight_string" function
										'DBG_WINDOW_WIDTH'			=>	700,						// set width of debugger window (in pixels) for better viewing large amount of debug data
										'DBG_USE_SHUTDOWN_FUNC'		=>	DBG_ZEND_PRESENT ? 0 : 1,	// use shutdown function to include debugger code into output
										'DBG_HANDLE_ERRORS'			=>	DBG_ZEND_PRESENT ? 0 : 1,	// handle all allowed by php (see php manual) errors instead of default handler
										'DBG_IGNORE_STRICT_ERRORS'	=>	1,							// ignore PHP5 errors about private/public view modified missing in class declarations
										'DBG_DOMVIEWER'				=>	'/temp/domviewer.html',		// path to DOMViewer on website
										'DOC_ROOT'					=>	str_replace('\\', '/', realpath($_SERVER['DOCUMENT_ROOT']) ), // windows hack
										'DBG_LOCAL_BASE_PATH'		=>	'w:',						// replace DOC_ROOT in filenames (in errors) using this path
								);

				// only for IE, in case if no windows php script editor defined
				if (!defined('DBG_EDITOR')) {
//					$dbg_constMap['DBG_EDITOR'] = 'c:\Program Files\UltraEdit\uedit32.exe %F/%L';
					$dbg_constMap['DBG_EDITOR'] = 'c:\Program Files\Zend\ZendStudio-5.2.0\bin\ZDE.exe %F';
				}

				if (isset($_REQUEST['ajax']) && $_REQUEST['ajax'] && constOn('DBG_SKIP_AJAX')) {
					$this->safeDefine('DBG_SKIP_REPORTING', 1);
				}

				// user defined options override debugger defaults
				$dbg_constMap = $this->array_merge_recursive2($dbg_constMap, $dbg_options);

				foreach ($dbg_constMap as $dbg_constName => $dbg_constValue) {
					$this->safeDefine($dbg_constName, $dbg_constValue);
				}
			}

			function constOn($const_name)
			{
				return defined($const_name) && constant($const_name);
			}

			function safeDefine($const_name, $const_value) {
				if (!defined($const_name)) {
					define($const_name, $const_value);
				}
			}

			function array_merge_recursive2($paArray1, $paArray2)
			{
				if (!is_array($paArray1) or !is_array($paArray2)) {
					return $paArray2;
				}

				foreach ($paArray2 AS $sKey2 => $sValue2) {
					$paArray1[$sKey2] = isset($paArray1[$sKey2]) ? array_merge_recursive2($paArray1[$sKey2], $sValue2) : $sValue2;
				}

				return $paArray1;
			}

			function netMatch($network, $ip) {

				$network = trim($network);
				$ip = trim($ip);

				if ($network == $ip) {
					// comparing 2 ip addresses directly
					return true;
				}

				$d = strpos($network, '-');
				if ($d === false) {
					// sigle subnet specified
					$ip_arr = explode('/', $network);

					if (!preg_match("@\d*\.\d*\.\d*\.\d*@", $ip_arr[0], $matches)) {
						$ip_arr[0] .= '.0';    // Alternate form 194.1.4/24
					}

					$network_long = ip2long($ip_arr[0]);
					$x = ip2long($ip_arr[1]);

					$mask = long2ip($x) == $ip_arr[1] ? $x : (0xffffffff << (32 - $ip_arr[1]));
					$ip_long = ip2long($ip);

					return ($ip_long & $mask) == ($network_long & $mask);
				}
				else {
					// ip address range specified
					$from = ip2long(trim(substr($network, 0, $d)));
					$to = ip2long(trim(substr($network, $d + 1)));

					$ip = ip2long($ip);
					return ($ip >= $from && $ip <= $to);
				}
			}

			function InitReport()
			{
				if (!class_exists('kApplication')) return false;

				$application =& kApplication::Instance();

				// string used to separate debugger records while in file (used in debugger dump filename too)
				$this->rowSeparator = '@'.(is_object($application->Factory) ? $application->GetSID() : 0).'@';

				// include debugger files from this url
				$reg_exp = '/^'.preg_quote(FULL_PATH, '/').'/';
				$kernel_path = preg_replace($reg_exp, '', KERNEL_PATH, 1);
				$this->baseURL = PROTOCOL.SERVER_NAME.rtrim(BASE_PATH, '/').$kernel_path.'/utility/debugger';

				// save debug output in this folder
				$this->tempFolder = defined('WRITEABLE') ? WRITEABLE.'/cache' : FULL_PATH.'/kernel/cache';
			}

			function mapLongError($msg)
			{
				$key = $this->generateID();
				$this->longErrors[$key] = $msg;
				return $key;
			}

			/**
			 * Appends all passed variable values (wihout variable names) to debug output
			 *
			 */
			function dumpVars()
			{
				$dump_mode = 'var_dump';
				$dumpVars = func_get_args();

				if ($dumpVars[count($dumpVars) - 1] === 'STRICT') {
					$dump_mode = 'strict_var_dump';
					array_pop($dumpVars);
				}

				foreach ($dumpVars as $varValue) {
					$this->Data[] = Array('value' => $varValue, 'debug_type' => $dump_mode);
				}

			}

			function prepareHTML($dataIndex)
			{
				$Data =& $this->Data[$dataIndex];
				if ($Data['debug_type'] == 'html') {
					return $Data['html'];
				}

				switch ($Data['debug_type']) {
					case 'error':
						$fileLink = $this->getFileLink($Data['file'], $Data['line']);
						$ret = '<b class="debug_error">'.$this->getErrorNameByCode($Data['no']).'</b>: '.$Data['str'];
						$ret .= ' in <b>'.$fileLink.'</b> on line <b>'.$Data['line'].'</b>';
						return $ret;
						break;

					case 'var_dump':
						return $this->highlightString( $this->print_r($Data['value'], true) );
						break;

					case 'strict_var_dump':
						return $this->highlightString( var_export($Data['value'], true) );
						break;

					case 'trace':
						ini_set('memory_limit', '500M');
						$trace =& $Data['trace'];

						$i = 0; $traceCount = count($trace);
						$ret = '';
						while ($i < $traceCount) {
							$traceRec =& $trace[$i];
							$argsID = 'trace_args_'.$dataIndex.'_'.$i;

							$has_args = isset($traceRec['args']);

							if (isset($traceRec['file'])) {
								$func_name = isset($traceRec['class']) ? $traceRec['class'].$traceRec['type'].$traceRec['function'] : $traceRec['function'];
								$args_link = $has_args ? '<a href="javascript:$Debugger.ToggleTraceArgs(\''.$argsID.'\');" title="Show/Hide Function Arguments"><b>Function</b></a>' : '<b>Function</b>';

								$ret .= $args_link.': '.$this->getFileLink($traceRec['file'], $traceRec['line'], $func_name);
								$ret .= ' in <b>'.basename($traceRec['file']).'</b> on line <b>'.$traceRec['line'].'</b><br>';
							}
							else {
								$ret .= 'no file information available';
							}

							if ($has_args) {
								// if parameter value is longer then 200 symbols, then leave only first 50
								$args = $this->highlightString($this->print_r($traceRec['args'], true));
								$ret .= '<div id="'.$argsID.'" style="display: none;">'.$args.'</div>';
							}
							$i++;
						}
						return $ret;
						break;

					case 'profiler':
						$profileKey = $Data['profile_key'];
						$Data =& $this->ProfilerData[$profileKey];
						$runtime = ($Data['ends'] - $Data['begins']); // in seconds

						$totals_key = getArrayValue($Data, 'totalsKey');
						if ($totals_key) {
							$total_before = $Data['totalsBefore'];
							$total = $this->ProfilerTotals[$totals_key];

							$div_width = Array();
							$total_width = ($this->getWindowWidth() - 10);
							$div_width['before'] = round(($total_before / $total) * $total_width);
							$div_width['current'] = round(($runtime / $total) * $total_width);
							$div_width['left'] = round((($total - $total_before - $runtime) / $total) * $total_width);

							$ret = '<b>Name</b>: '.$Data['description'].'<br />';

							$additional = isset($Data['additional']) ? $Data['additional'] : Array ();
							if (isset($Data['file'])) {
								array_unshift($additional, Array('name' => 'File', 'value' => $this->getFileLink($Data['file'], $Data['line'], basename($Data['file']).':'.$Data['line'])));
							}
							array_unshift($additional, Array('name' => 'Runtime', 'value' => $runtime.'s'));

							foreach ($additional as $mixed_param) {
								$ret .= '[<strong>'.$mixed_param['name'].'</strong>: '.$mixed_param['value'].'] ';
							}

							/*if (isset($Data['file'])) {
								$ret .= '[<b>Runtime</b>: '.$runtime.'s] [<b>File</b>: '.$this->getFileLink($Data['file'], $Data['line'], basename($Data['file']).':'.$Data['line']).']<br />';
							}
							else {
								$ret .= '<b>Runtime</b>: '.$runtime.'s<br />';
							}*/

							$ret .= '<div class="dbg_profiler" style="width: '.$div_width['before'].'px; border-right: 0px; background-color: #298DDF;"><img src="'.$this->dummyImage.'" width="1" height="1"/></div>';
							$ret .= '<div class="dbg_profiler" style="width: '.$div_width['current'].'px; border-left: 0px; border-right: 0px; background-color: #EF4A4A;"><img src="'.$this->dummyImage.'" width="1" height="1"/></div>';
							$ret .= '<div class="dbg_profiler" style="width: '.$div_width['left'].'px; border-left: 0px; background-color: #DFDFDF;"><img src="'.$this->dummyImage.'" width="1" height="1"/></div>';

							return $ret;
						}
						else {
							return '<b>Name</b>: '.$Data['description'].'<br><b>Runtime</b>: '.$runtime.'s';
						}
						break;

					default:
						return 'incorrect debug data';
						break;
				}
			}

			function getWindowWidth()
			{
				return DBG_WINDOW_WIDTH - $this->scrollbarWidth - 8;
			}

			/**
			 * Tells debugger to skip objects that are heavy in plan of memory usage while printing debug_backtrace results
			 *
			 * @param Object $object
			 * @return bool
			 */
			function IsBigObject(&$object)
			{
				$skip_classes = Array(
										defined('APPLICATION_CLASS') ? APPLICATION_CLASS : 'kApplication',
										'kFactory',
										'kUnitConfigReader',
										'TemplateParser',
								);

				foreach ($skip_classes as $class_name) {
					if (strtolower(get_class($object)) == strtolower($class_name)) {
						return true;
					}
				}

				return false;
			}

			/**
			 * Advanced version of print_r (for debugger only). Don't print objects recursively
			 *
			 * @param Array $array
			 * @param bool $return_output return output or print it out
			 * @param int $tab_count offset in tabs
			 * @return string
			 */
			function print_r(&$array, $return_output = false, $tab_count = -1)
			{
				static $first_line = true;

				// not an array at all
				if (!is_array($array)) {
					switch (gettype($array)) {
						case 'NULL':
							return 'NULL'."\n";
							break;

						case 'object':
							return $this->processObject($array, $tab_count);
							break;

						default:
							// number or string
							if (strlen($array) > 200) {
								$array = substr($array, 0, 50).' ...';
							}
							return $array."\n";
							break;
					}
				}

				$output = '';

				$tab_count++;
				$output .= "Array\n".str_repeat('    ', $tab_count)."(\n";

				$tab_count++;
				$tabsign = $tab_count ? str_repeat('    ', $tab_count) : '';

				$array_keys = array_keys($array);

				foreach ($array_keys as $key) {
					switch (gettype($array[$key])) {
						case 'array':
							$output .= $tabsign.'['.$key.'] = '.$this->print_r($array[$key], true, $tab_count);
							break;

						case 'boolean':
							$output .= $tabsign.'['.$key.'] = '.($array[$key] ? 'true' : 'false')."\n";
							break;

						case 'integer':
						case 'double':
						case 'string':
							if (strlen($array[$key]) > 200) {
								$array[$key] = substr($array[$key], 0, 50).' ...';
							}
							$output .= $tabsign.'['.$key.'] = '.$array[$key]."\n";
							break;

						case 'NULL':
							$output .= $tabsign.'['.$key."] = NULL\n";
							break;

						case 'object':
							$output .= $tabsign.'['.$key."] = ";
							$output .= "Object (".get_class($array[$key]).") = \n".str_repeat('    ', $tab_count + 1)."(\n";
							$output .= $this->processObject($array[$key], $tab_count + 2);
							$output .= str_repeat('    ', $tab_count + 1).")\n";
							break;

						default:
							$output .= $tabsign.'['.$key.'] unknown = '.gettype($array[$key])."\n";
							break;
					}
				}

				$tab_count--;
				$output .= str_repeat('    ', $tab_count).")\n";

				if ($first_line) {
					$first_line = false;
					$output .= "\n";
				}

				$tab_count--;

				if ($return_output) {
				return $output;
				}
				else {
					echo $output;
				}

				return true;
			}

			function processObject(&$object, $tab_count)
			{
				$object_class = get_class($object);
				if (!in_array($object_class, $this->RecursionStack)) {
					if ($this->IsBigObject($object)) {
						return 'SKIPPED (class: '.$object_class.")\n";
					}

					$attribute_names = get_class_vars($object_class);
					if (!$attribute_names) {
						return "NO_ATTRIBUTES\n";
					}
					else {
						$output = '';
						array_push($this->RecursionStack, $object_class);

						$tabsign = $tab_count ? str_repeat('    ', $tab_count) : '';
						foreach ($attribute_names as $attribute_name => $attribute_value) {
							if (is_object($object->$attribute_name)) {
								// it is object
								$output .= $tabsign.'['.$attribute_name.'] = '.$this->processObject($object->$attribute_name, $tab_count + 1);
							}
							else {
								$output .= $tabsign.'['.$attribute_name.'] = '.$this->print_r($object->$attribute_name, true, $tab_count);
							}
						}
						array_pop($this->RecursionStack);
						return $output;
					}
				}
				else {
					// object [in recursion stack]
					return '*** RECURSION *** (class: '.$object_class.")\n";
				}
			}

			/**
			 * Format SQL Query using predefined formatting
			 * and highlighting techniques
			 *
			 * @param string $sql
			 * @return string
			 */
			function formatSQL($sql)
			{
				$sql = preg_replace('/(\n|\t| )+/is', ' ', $sql);
				$sql = preg_replace('/(CREATE TABLE|DROP TABLE|SELECT|UPDATE|SET|REPLACE|INSERT|DELETE|VALUES|FROM|LEFT JOIN|INNER JOIN|LIMIT|WHERE|HAVING|GROUP BY|ORDER BY) /is', "\n\t$1 ", $sql);
				return $this->highlightString($sql);
			}

			function highlightString($string)
			{
				if (!$this->constOn('DBG_USE_HIGHLIGHT')) {
					return $string;
				}

				$string = str_replace( Array('\\', '/') , Array('_no_match_string_', '_n_m_s_'), $string);
				$string = highlight_string('<?php'.$string.'?>', true);
				$string = str_replace( Array('_no_match_string_', '_n_m_s_'), Array('\\', '/'), $string);
				return preg_replace('/&lt;\?(.*)php(.*)\?&gt;/Us', '\\2', $string);
			}

			/**
			 * Determine by php type of browser used to show debugger
			 *
			 * @return bool
			 */
			function isGecko()
			{
				return strpos(strtolower($_SERVER['HTTP_USER_AGENT']), 'firefox') !== false;
			}

			/**
			 * Returns link for editing php file (from error) in external editor
			 *
			 * @param string $file filename with path from root folder
			 * @param int $lineno line number in file where error is found
			 * @param string $title text to show on file edit link
			 * @return string
			 */
			function getFileLink($file, $lineno = 1, $title = '')
			{
				if (!$title) {
					$title = str_replace('/', '\\', $this->getLocalFile($file));
				}

				if ($this->isGecko()) {
					return '<a href="file://'.$this->getLocalFile($file).'">'.$title.'</a>';
				}
				else {
					return '<a href="javascript:$Debugger.editFile(\''.$this->getLocalFile($file).'\', '.$lineno.');" title="'.$file.'">'.$title.'</a>';
				}

			}

			/**
			 * Converts filepath on server to filepath in mapped DocumentRoot on developer pc
			 *
			 * @param string $remoteFile
			 * @return string
			 */
			function getLocalFile($remoteFile)
			{
				return preg_replace('/^'.preg_quote(DOC_ROOT, '/').'/', DBG_LOCAL_BASE_PATH, $remoteFile, 1);
			}

			/**
			 * Appends call trace till this method call
			 *
			 */
			function appendTrace()
			{
				$trace = debug_backtrace();
				array_shift($trace);

				$this->Data[] = Array('trace' => $trace, 'debug_type' => 'trace');
			}

			function appendMemoryUsage($msg, $used = null)
			{
				if (!isset($used)) {
					$used = round(memory_get_usage() / 1024);
				}
				$this->appendHTML('<b>Memory usage</b> '.$msg.' '.$used.'Kb');
			}

			/**
			 * Appends HTML code whithout transformations
			 *
			 * @param string $html
			 */
			function appendHTML($html)
			{
				$this->Data[] = Array('html' => $html, 'debug_type' => 'html');
			}

			/**
			 * Change debugger info that was already generated before.
			 * Returns true if html was set.
			 *
			 * @param int $index
			 * @param string $html
			 * @param string $type = {'append','prepend','replace'}
			 * @return bool
			 */
			function setHTMLByIndex($index, $html, $type = 'append')
			{
				if (!isset($this->Data[$index]) || $this->Data[$index]['debug_type'] != 'html') {
					return false;
				}

				switch ($type) {
					case 'append':
						$this->Data[$index]['html'] .= '<br>'.$html;
						break;

					case 'prepend':
						$this->Data[$index]['html'] = $this->Data[$index]['html'].'<br>'.$html;
						break;

					case 'replace':
						$this->Data[$index]['html'] = $html;
						break;
				}
				return true;
			}

			/**
			 * Move $debugLineCount lines of input from debug output
			 * end to beginning.
			 *
			 * @param int $debugLineCount
			 */
			function moveToBegin($debugLineCount)
			{
				$lines = array_splice($this->Data,count($this->Data)-$debugLineCount,$debugLineCount);
				$this->Data = array_merge($lines,$this->Data);
			}

			function moveAfterRow($new_row, $debugLineCount)
			{
				$lines = array_splice($this->Data,count($this->Data)-$debugLineCount,$debugLineCount);
				$rows_before = array_splice($this->Data,0,$new_row,$lines);
				$this->Data = array_merge($rows_before,$this->Data);
			}

			function appendRequest()
			{
				if (isset($_SERVER['SCRIPT_FILENAME'])) {
					$script = $_SERVER['SCRIPT_FILENAME'];
				}
				else {
					$script = $_SERVER['DOCUMENT_ROOT'].$_SERVER['PHP_SELF'];
				}

				$this->appendHTML('ScriptName: <b>'.$this->getFileLink($script, 1, basename($script)).'</b> (<b>'.dirname($script).'</b>)');
				if (isset($_REQUEST['ajax']) && $_REQUEST['ajax'] == 'yes') {
					$this->appendHTML('RequestURI: '.$_SERVER['REQUEST_URI'].' (QS Length:'.strlen($_SERVER['QUERY_STRING']).')');
				}
				$this->appendHTML('<a href="http://www.brainjar.com/dhtml/domviewer/" target="_blank">DomViewer</a>: <input id="dbg_domviewer" type="text" value="window" style="border: 1px solid #000000;"/>&nbsp;<button class="button" onclick="return $Debugger.OpenDOMViewer();">Show</button>');

				ob_start();
				?>
				<table border="0" cellspacing="0" cellpadding="0" class="dbg_flat_table" style="width: <?php echo $this->getWindowWidth(); ?>px;">
					<thead style="font-weight: bold;">
						<td width="20">Src</td><td>Name</td><td>Value</td>
					</thead>
				<?php
					foreach ($_REQUEST as $key => $value) {
						if(!is_array($value) && trim($value) == '') {
							$value = '<b class="debug_error">no value</b>';
						}
						else {
							$value = htmlspecialchars($this->print_r($value, true));
						}

						$in_cookie = isset($_COOKIE[$key]);
						$src = isset($_GET[$key]) && !$in_cookie ? 'GE' : (isset($_POST[$key]) && !$in_cookie ? 'PO' : ($in_cookie ? 'CO' : '?') );
						echo '<tr><td>'.$src.'</td><td>'.$key.'</td><td>'.$value.'</td></tr>';
					}
				?>
				</table>
				<?php
				$this->appendHTML(ob_get_contents());
				ob_end_clean();
			}

			/**
			 * Appends php session content to debugger output
			 *
			 */
			function appendSession()
			{
				if (isset($_SESSION) && $_SESSION) {
					$this->appendHTML('PHP Session: [<b>'.ini_get('session.name').'</b>]');
					$this->dumpVars($_SESSION);
					$this->moveToBegin(2);
				}
			}

			function profileStart($key, $description = null, $timeStamp = null)
			{
				if (!isset($timeStamp)) {
					$timeStamp = $this->getMoment();
				}
				$this->ProfilerData[$key] = Array('begins' => $timeStamp, 'ends' => 5000, 'debuggerRowID' => count($this->Data));
				if (isset($description)) {
					$this->ProfilerData[$key]['description'] = $description;
				}

				if (substr($key, 0, 4) == 'sql_') {
					// append place from what was called
					$trace_results = debug_backtrace();
					$trace_count = count($trace_results);
					$i = 0;
					while ($i < $trace_count) {
						$trace_file = basename($trace_results[$i]['file']);
						if ($trace_file != 'db_connection.php' && $trace_file != 'adodb.inc.php') {
							break;
						}
						$i++;
					}
					$this->ProfilerData[$key]['file'] = $trace_results[$i]['file'];
					$this->ProfilerData[$key]['line'] = $trace_results[$i]['line'];
					unset($trace_results);
				}

				$this->Data[] = Array('profile_key' => $key, 'debug_type' => 'profiler');
			}

			function profileFinish($key, $description = null, $timeStamp = null)
			{
				if (!isset($timeStamp)) {
					$timeStamp = $this->getMoment();
				}
				$this->ProfilerData[$key]['ends'] = $timeStamp;

				if (isset($description)) {
					$this->ProfilerData[$key]['description'] = $description;
				}

				if (substr($key, 0, 4) == 'sql_') {
					$func_arguments = func_get_args();
					$rows_affected = $func_arguments[3];

					if ($rows_affected > 0) {
						$additional = Array (
							Array ('name' => 'Affected Rows', 'value' => $rows_affected),
						);

						if (isset($func_arguments[4])) {
							$additional[] = Array ('name' => 'Result', 'value' => $func_arguments[4]);
						}
						$this->ProfilerData[$key]['additional'] =& $additional;
					}
				}
			}

			function profilerAddTotal($total_key, $key = null, $value = null)
			{
				if (!isset($this->ProfilerTotals[$total_key])) {
					$this->ProfilerTotals[$total_key] = 0;
					$this->ProfilerTotalCount[$total_key] = 0;
				}

				if (!isset($value)) {
					$value = $this->ProfilerData[$key]['ends'] - $this->ProfilerData[$key]['begins'];
				}

				if (isset($key)) {
					$this->ProfilerData[$key]['totalsKey'] = $total_key;
					$this->ProfilerData[$key]['totalsBefore'] = $this->ProfilerTotals[$total_key];
				}

				$this->ProfilerTotals[$total_key] += $value;
				$this->ProfilerTotalCount[$total_key]++;
			}

			function getMoment()
			{
				list($usec, $sec) = explode(' ', microtime());
	   			return ((float)$usec + (float)$sec);
			}

			function generateID()
			{
				list($usec, $sec) = explode(' ', microtime());

				$id_part_1 = substr($usec, 4, 4);
				$id_part_2 = mt_rand(1,9);
				$id_part_3 = substr($sec, 6, 4);
				$digit_one = substr($id_part_1, 0, 1);
				if ($digit_one == 0) {
					$digit_one = mt_rand(1,9);
					$id_part_1 = ereg_replace("^0",'',$id_part_1);
					$id_part_1=$digit_one.$id_part_1;
				}
				return $id_part_1.$id_part_2.$id_part_3;
			}


			function getErrorNameByCode($error_code)
			{
				$error_map = Array(
									'Fatal Error' 	=>	Array(E_USER_ERROR),
									'Warning'		=>	Array(E_WARNING, E_USER_WARNING),
									'Notice'		=>	Array(E_NOTICE, E_USER_NOTICE),
							);

				if (defined('E_STRICT')) {
					$error_map['PHP5 Strict'] = Array(E_STRICT);
				}

				if (defined('E_RECOVERABLE_ERROR')) {
					$error_map['Fatal Error (recoverable)'] = Array(E_RECOVERABLE_ERROR);
				}

				foreach ($error_map as $error_name => $error_codes) {
					if (in_array($error_code, $error_codes)) {
						return $error_name;
					}
				}

				return '';
			}

			/**
			 * Returns profile total key (check against unexisting key too)
			 *
			 * @param string $key
			 * @return int
			 */
			function getProfilerTotal($key)
			{
				if (isset($this->ProfilerTotalCount[$key])) {
					return (int)$this->ProfilerTotalCount[$key];
				}
				return 0;
			}

			function ProfilePoint($title, $level=1)
			{
				$trace_results = debug_backtrace();
				$level = min($level,count($trace_results)-1);

				do {
					$point = $trace_results[$level];
					$location = $point['file'].':'.$point['line'];
					$level++;
					$has_more = isset($trace_results[$level]);
				} while ($has_more && $point['function'] == $trace_results[$level]['function'] );

				if ($location == ':') {
					echo '';
				}

				if (!isset($this->ProfilePoints[$title])) {
					$this->ProfilePoints[$title] = array();
				}
				if (!isset($this->ProfilePoints[$title][$location])) {
					$this->ProfilePoints[$title][$location] = 0;
				}
				$this->ProfilePoints[$title][$location]++;
			}

			/**
			 * Generates report
			 *
			 */
			function printReport($returnResult = false, $clean_output_buffer = true)
			{
				if ($this->reportDone) {
					// don't print same report twice (in case if shutdown function used + compression + fatal error)
					return '';
				}

				$this->profileFinish('script_runtime');
				$this->breakOutofBuffering();

				$debugger_start = memory_get_usage();

				if (defined('SPACER_URL')) {
					$this->dummyImage = SPACER_URL;
				}

				$this->InitReport(); // set parameters required by AJAX

				// defined here, because user can define this contant while script is running, not event before debugger is started
				$this->safeDefine('DBG_RAISE_ON_WARNINGS', 	0);
				$this->safeDefine('DBG_TOOLBAR_BUTTONS', 	1);

				$this->appendSession(); // show php session if any

				// ensure, that 1st line of debug output always is this one:
				$top_line = '<table cellspacing="0" cellpadding="0" style="width: '.$this->getWindowWidth().'px; margin: 0px;"><tr><td align="left" width="50%">[<a href="javascript:window.location.reload();">Reload Frame</a>] [<a href="javascript:$Debugger.Toggle(27);">Hide Debugger</a>]</td><td align="right" width="50%">[Current Time: <b>'.date('H:i:s').'</b>] [File Size: <b>#DBG_FILESIZE#</b>]</td></tr></table>';

				$this->appendHTML($top_line);
				$this->moveToBegin(1);

				if (count($this->ProfilePoints)>0) {
					foreach($this->ProfilePoints as $point => $locations) {
						arsort($this->ProfilePoints[$point]);
					}

					$this->appendHTML($this->highlightString($this->print_r($this->ProfilePoints, true)));
					/*foreach ($this->ProfilePoints as $point => $locations) {
						foreach ($locations as $location => $occurences) {


						}
					}*/
				}

				if ($this->constOn('DBG_SQL_PROFILE') && isset($this->ProfilerTotals['sql'])) {
					// sql query profiling was enabled -> show totals
					$this->appendHTML('<b>SQL Total time:</b> '.$this->ProfilerTotals['sql'].' <b>Number of queries</b>: '.$this->ProfilerTotalCount['sql']);
				}

				if ($this->constOn('DBG_PROFILE_INCLUDES') && isset($this->ProfilerTotals['includes'])) {
					// included file profiling was enabled -> show totals
					$this->appendHTML('<b>Included Files Total time:</b> '.$this->ProfilerTotals['includes'].' Number of includes: '.$this->ProfilerTotalCount['includes']);
				}

				if ($this->constOn('DBG_PROFILE_MEMORY')) {
					// detailed memory usage reporting by objects was enabled -> show totals
					$this->appendHTML('<b>Memory used by Objects:</b> '.round($this->ProfilerTotals['objects'] / 1024, 2).'Kb');
				}

				if ($this->constOn('DBG_INCLUDED_FILES')) {
					$files = get_included_files();
					$this->appendHTML('<b>Included files:</b>');
					foreach ($files as $file) {
						$this->appendHTML($this->getFileLink($this->getLocalFile($file)).' ('.round(filesize($file) / 1024, 2).'Kb)');
					}
				}

				/*if ($this->constOn('DBG_PROFILE_INCLUDES')) {
					$this->appendHTML('<b>Included files statistics:</b>'.( $this->constOn('DBG_SORT_INCLUDES_MEM') ? ' (sorted by memory usage)':''));
					$totals = Array( 'mem' => 0, 'time' => 0);
					$totals_configs = Array( 'mem' => 0, 'time' => 0);
					if (is_array($this->IncludesData['mem'])) {
						if ( $this->constOn('DBG_SORT_INCLUDES_MEM') ) {
							array_multisort($this->IncludesData['mem'], SORT_DESC, $this->IncludesData['file'], $this->IncludesData['time'], $this->IncludesData['level']);
						}
						foreach ($this->IncludesData['file'] as $key => $file_name) {
							$this->appendHTML(  str_repeat('&nbsp;->&nbsp;', ($this->IncludesData['level'][$key] >= 0 ? $this->IncludesData['level'][$key] : 0)).$file_name.' Mem: '.sprintf("%.4f Kb", $this->IncludesData['mem'][$key]/1024).' Time: '.sprintf("%.4f", $this->IncludesData['time'][$key]));
							if ($this->IncludesData['level'][$key] == 0) {
								$totals['mem'] += $this->IncludesData['mem'][$key];
								$totals['time'] += $this->IncludesData['time'][$key];
							}
							else if ($this->IncludesData['level'][$key] == -1) {
								$totals_configs['mem'] += $this->IncludesData['mem'][$key];
								$totals_configs['time'] += $this->IncludesData['time'][$key];
							}
						}
						$this->appendHTML('<b>Sub-Total classes:</b> '.' Mem: '.sprintf("%.4f Kb", $totals['mem']/1024).' Time: '.sprintf("%.4f", $totals['time']));
						$this->appendHTML('<b>Sub-Total configs:</b> '.' Mem: '.sprintf("%.4f Kb", $totals_configs['mem']/1024).' Time: '.sprintf("%.4f", $totals_configs['time']));
						$this->appendHTML('<span class="error"><b>Grand Total:</b></span> '.' Mem: '.sprintf("%.4f Kb", ($totals['mem']+$totals_configs['mem'])/1024).' Time: '.sprintf("%.4f", $totals['time']+$totals_configs['time']));
					}
				}*/

				$is_ajax = isset($_GET['ajax']) && $_GET['ajax'] == 'yes';
				$skip_reporting = $this->constOn('DBG_SKIP_REPORTING') || $this->constOn('DBG_ZEND_PRESENT');

				if (($is_ajax && !constOn('DBG_SKIP_AJAX')) || !$skip_reporting) {
					$debug_file = $this->tempFolder.'/debug_'.$this->rowSeparator.'.txt';
					if (file_exists($debug_file)) unlink($debug_file);

					$i = 0;
					$fp = fopen($debug_file, 'a');
					$lineCount = count($this->Data);
					while ($i < $lineCount) {
						fwrite($fp, $this->prepareHTML($i).$this->rowSeparator);
						$i++;
					}
					fclose($fp);
				}

				if ($skip_reporting) {
					// let debugger write report and then don't output anything
					$this->reportDone = true;
					return '';
				}

				$dbg_path = str_replace(FULL_PATH, '', $this->tempFolder);
				ob_start();
				// the <script .. /script> and hidden div helps browser to break out of script tag or attribute esacped
				// with " or ' in case fatal error (or user-error) occurs inside it in compiled template,
				// otherwise it has no effect
			?>
					<div style="display: none" x='nothing'><script></script></div><html><body></body></html>
					<script type="text/javascript" src="<?php echo $this->baseURL; ?>/debugger.js"></script>
					<link rel="stylesheet" rev="stylesheet" href="<?php echo $this->baseURL; ?>/debugger.css" type="text/css" media="screen" />
					<div id="debug_layer" class="debug_layer_container" style="display: none; width: <?php echo DBG_WINDOW_WIDTH; ?>px;">
						<div class="debug_layer" style="width: <?php echo $this->getWindowWidth(); ?>px;">
							<table width="100%" cellpadding="0" cellspacing="1" border="0" class="debug_layer_table" style="width: <?php echo $this->getWindowWidth(); ?>px;" align="left">
								<tbody id="debug_table"></tbody>
							</table>
						</div>
					</div>

					<script type="text/javascript">
						var $Debugger = new Debugger(<?php echo "'".$this->rowSeparator."', ".$this->getProfilerTotal('error_handling').', '.($this->IsFatalError ? 'true' : 'false').', '.$this->getProfilerTotal('sql'); ?>);
						$Debugger.DOMViewerURL = '<?php echo constant('DBG_DOMVIEWER'); ?>';
						$Debugger.EditorPath = '<?php echo defined('DBG_EDITOR') ? addslashes(DBG_EDITOR) : '' ?>';
						$Debugger.DebugURL = '<?php echo $this->baseURL.'/debugger_responce.php?sid='.$this->rowSeparator.'&path='.urlencode($dbg_path); ?>';

						<?php
							if ($this->IsFatalError || DBG_RAISE_ON_WARNINGS) {
								echo '$Debugger.Toggle();';
							}
							if (DBG_TOOLBAR_BUTTONS) {
								echo '$Debugger.AddToolbar("$Debugger");';
							}
						?>
						window.focus();
					</script>
				<?php
					if (!isset($this->ProfilerTotals['error_handling'])) {
						$memory_used = $debugger_start;
						$this->ProfilerTotalCount['error_handling'] = 0;
					}
					else {
						$memory_used = $debugger_start - $this->ProfilerTotals['error_handling'];
					}

					if ($returnResult) {
						$ret = ob_get_contents();
						if ($clean_output_buffer) {
							ob_end_clean();
						}
						$ret .= $this->getShortReport($memory_used);

						$this->reportDone = true;
						return $ret;
					}
					else {
						if (!$this->constOn('DBG_HIDE_FULL_REPORT')) {
							$this->breakOutofBuffering();
						}
						elseif ($clean_output_buffer) {
							ob_clean();
						}
						echo $this->getShortReport($memory_used);

						$this->reportDone = true;
					}
			}

			/**
			 * Format's memory usage report by debugger
			 *
			 * @return string
			 * @access private
			 */
			function getShortReport($memory_used)
			{
				if ($this->constOn('DBG_TOOLBAR_BUTTONS')) {
					// we have sql & error count in toolbar, don't duplicate here
				$info = Array(
						'Script Runtime'	=>	'PROFILE:script_runtime',
						'SQL\'s Runtime'	=>	'PROFILE_T:sql',
					);
				}
				else {
					// toolbar not visible, then show sql & error count too
					$info = Array (
								'Script Runtime'	=>	'PROFILE:script_runtime',
								'SQL\'s Runtime'	=>	'PROFILE_T:sql',
								'-'					=>	'SEP:-',
								'Notice / Warning'	=>	'PROFILE_TC:error_handling',
								'SQLs Count'		=>	'PROFILE_TC:sql',
						);
				}

				$ret = '<tr><td>Application:</td><td><b>'.$this->formatSize($memory_used).'</b> ('.$memory_used.')</td></tr>';
				foreach ($info as $title => $value_key) {
					list ($record_type, $record_data) = explode(':', $value_key, 2);
					switch ($record_type) {
						case 'PROFILE': // profiler totals value
							$Data =& $this->ProfilerData[$record_data];
							$profile_time = ($Data['ends'] - $Data['begins']); // in seconds
							$ret .= '<tr><td>'.$title.':</td><td><b>'.sprintf('%.5f', $profile_time).' s</b></td></tr>';
							break;

						case 'PROFILE_TC': // profile totals record count
							$record_cell = '<td>';
							if ($record_data == 'error_handling' && $this->ProfilerTotalCount[$record_data] > 0) {
								$record_cell = '<td class="debug_error">';
							}
							$ret .= '<tr>'.$record_cell.$title.':</td>'.$record_cell.'<b>'.$this->ProfilerTotalCount[$record_data].'</b></td></tr>';
							break;

						case 'PROFILE_T': // profile total
							$record_cell = '<td>';
							$ret .= '<tr>'.$record_cell.$title.':</td>'.$record_cell.'<b>'.sprintf('%.5f', $this->ProfilerTotals[$record_data]).' s</b></td></tr>';
							break;

						case 'SEP':
							$ret .= '<tr><td colspan="2" style="height: 1px; background-color: #000000; padding: 0px;"><img src="'.$this->dummyImage.'" height="1" alt=""/></td></tr>';
							break;
					}
				}

				return '<br /><table class="dbg_stats_table"><tr><td style="border-color: #FFFFFF;"><table class="dbg_stats_table" align="left">'.$ret.'</table></td></tr></table>';
			}

			/**
			 * User-defined error handler
			 *
			 * @param int $errno
			 * @param string $errstr
			 * @param string $errfile
			 * @param int $errline
			 * @param array $errcontext
			 */
			function saveError($errno, $errstr, $errfile = '', $errline = '', $errcontext = '')
			{
				$this->ProfilerData['error_handling']['begins'] = memory_get_usage();

				$errorType = $this->getErrorNameByCode($errno);
				if (!$errorType) {
					trigger_error('Unknown error type ['.$errno.']', E_USER_ERROR);
					return false;
				}

				if ($this->constOn('DBG_IGNORE_STRICT_ERRORS') && defined('E_STRICT') && ($errno == E_STRICT)) return;

				if (preg_match('/(.*)#([\d]+)$/', $errstr, $rets) ) {
					// replace short message with long one (due triger_error limitations on message size)
					$long_id = $rets[2];
					$errstr = $this->longErrors[$long_id];
					unset($this->longErrors[$long_id]);
				}

				if (strpos($errfile,'eval()\'d code') !== false) {
					$errstr = '[<b>EVAL</b>, line <b>'.$errline.'</b>]: '.$errstr;
					$tmpStr = $errfile;
					$pos = strpos($tmpStr,'(');
					$errfile = substr($tmpStr, 0, $pos);
					$pos++;
					$errline = substr($tmpStr,$pos,strpos($tmpStr,')',$pos)-$pos);
				}

				$this->Data[] = Array('no' => $errno, 'str' => $errstr, 'file' => $errfile, 'line' => $errline, 'context' => $errcontext, 'debug_type' => 'error');
				$this->ProfilerData['error_handling']['ends'] = memory_get_usage();
				$this->profilerAddTotal('error_handling', 'error_handling');
				if (substr($errorType, 0, 5) == 'Fatal') {
					$this->IsFatalError = true;
					// append debugger report to data in buffer & clean buffer afterwards
					die( $this->breakOutofBuffering(false) . $this->printReport(true) );
				}
			}

			function breakOutofBuffering($flush = true)
			{
				$buffer_content = Array();
				while (ob_get_level()) {
					$buffer_content[] = ob_get_clean();
				}
				$ret = implode('', array_reverse($buffer_content));
				if ($flush) {
					echo $ret;
					flush();
				}
				return $ret;
			}

			function saveToFile($msg)
			{
				$fp = fopen($_SERVER['DOCUMENT_ROOT'].'/vb_debug.txt', 'a');
				fwrite($fp, $msg."\n");
				fclose($fp);
			}

			/**
			 * Formats file/memory size in nice way
			 *
			 * @param int $bytes
			 * @return string
			 * @access public
			 */
			function formatSize($bytes)
			{
				if ($bytes >= 1099511627776) {
					$return = round($bytes / 1024 / 1024 / 1024 / 1024, 2);
					$suffix = "TB";
				} elseif ($bytes >= 1073741824) {
					$return = round($bytes / 1024 / 1024 / 1024, 2);
					$suffix = "GB";
				} elseif ($bytes >= 1048576) {
					$return = round($bytes / 1024 / 1024, 2);
					$suffix = "MB";
				} elseif ($bytes >= 1024) {
					$return = round($bytes / 1024, 2);
					$suffix = "KB";
				} else {
					$return = $bytes;
					$suffix = "Byte";
				}
				$return .= ' '.$suffix;
				return $return;
			}

			function printConstants($constants)
			{
				if (!is_array($constants)) {
					$constants = explode(',', $constants);
				}
				$contant_tpl = '<tr><td>%s</td><td><b>%s</b></td></tr>';
				$ret = '<table class="dbg_flat_table" style="width: '.$this->getWindowWidth().'px;">';
				foreach ($constants as $constant_name) {
					$ret .= sprintf($contant_tpl, $constant_name, constant($constant_name));
				}
				$ret .= '</table>';
				$this->appendHTML($ret);
			}

			function AttachToApplication() {
				if (!$this->constOn('DBG_HANDLE_ERRORS')) return true;

				if (class_exists('kApplication')) {
					restore_error_handler();
					$application =& kApplication::Instance();
					$application->Debugger =& $this;
					$application->errorHandlers[] = Array(&$this, 'saveError');
				}
				else {
					set_error_handler(Array(&$this, 'saveError'));
				}
			}
		}

		if (!function_exists('memory_get_usage')) {
			function memory_get_usage(){ return -1;	}
		}

		if (!Debugger::constOn('DBG_ZEND_PRESENT')) {
			$debugger = new Debugger();
		}

		if (Debugger::constOn('DBG_USE_SHUTDOWN_FUNC')) {
			register_shutdown_function( Array(&$debugger, 'printReport') );
		}
	}
?>