Code converted by PHP and I use tabs of 8 (because tabs are 8) and PHP uses tabs of half-tabs... so some stuff don't line up.
Download this PHP DEBUG Archive. Readme this README.
But if you are willing to take a look at this, please just take a look at this. (It ain't too scary...)
But I do say that this is pretty sloppy code, and sometimes kinda lame, so there's that. There's a complete rewrite "Real Soon Now™".
PHP code /*
  debug - "debugging" for PHP by G.A.Jennings - MRU March, 2021.
  creativecommons.org/publicdomain/zero/1.0
  Please read README.TXT first...
*/
/*
  This is a "debug" API. It is very simple yet very versatile. Just define
  DEBUG_LOG, include this file and strategically place calls to the debug()
  function in and around the code you want to debug. The debug() function
  takes anything as an argument which will be displayed at program
  termination in a list. The output can be customized in many ways.

   * This code is also too big and too complex (really).
   * This code is meant to be modified for your use (if wanted).
      * Like removing a few near useless functions.
   * It may be split to work for just HTML or for no HTML output.

  See debug_simple.php for an example of way to make it simpler, which can
  be made, um, simpler...

  This code is **horrible**. Seriously. It was really small at first, but
  has evolved into a bowl of spaghetti, thrown against the wall, and all
  dried up. The intertwining of code for Terminal vs. HTML is abombinable,
  the debug() formating options are horrendous, how debug() uses runtime
  options (_debug() data) is pathetic.

  But it does work.

  THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
  WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/

// example of how to "auto start" stuff below; see INDEX.PHP for more
//define('DEBUG_INI','');        // config file
//define('DEBUG_CONFIG',1);        // use debug_config.php
//define('DEBUG_NOAUTO',1);        // to setup during load
//define('DEBUG_LOG',1);        // debuglog(DEBUG_LOG)
//define('DEBUG_ERR',1);        // debugerr(DEBUG_ERR)
//define('DEBUG_DUMP',1);        // debugdump(DEBUG_DUMP)
//define('DEBUG_DUMP_DATA','config');    // for DEBUG_DUMP = 1

define('DEBUG_VERSION','2.3.6t');    // not really [major][minor][patch]

// debug() $arg constants
define('DBG_HERENOW',-1);        // print immediately (also for LOG)
define('DBG_ALWAYS',-2);        // log message regardless of filters
define('DBG_RETURN',-3);        // return log string
define('DBG_NOTRACE',-4);        // without the trace
define('DBG_UP',-5);            // back one in call trace
define('DBG_NOENTS',-6);        // without htmlentities
define('DBG_DEEP',-7);            // many call traces
define('DBG_NOQ',-8);            // do not quote strings
define('DBG_Q',-9);            // always quote argument
define('DBG_NOQNL',-10);
# SEE NOTEQ NOTEO

// debuglog() $log constants
define('DBG_HTML_COMMENT',99);        // messages within "<!-- -->"
define('DBG_HTML',100);            // message in "<!-- -->"
define('DBG_MESSAGES',200);        // $GLOBALS[DEBUG_MESSAGES_GLOBAL]
define('DBG_OUTPUT_WINDOW',300);    // see index.php for usage

// datadump() constants
define('DUMP_USER',1);            // "user" defined data
define('DUMP_INPUTS',2);        // $_GET/$_POST data
define('DUMP_USER_CONSTANTS',3);    // user constants
define('DUMP_GLOBALS',4);        // $GLOBALS
define('DUMP_CONSTANTS',5);        // all constants
define('DUMP_SERVER',6);        // $_SERVER
define('DUMP_DEBUG',7);            // internal debug data

// set defines/options via INI file
if (defined('DEBUG_INI'))
   
debug_config(DEBUG_INI);
// set defines/options via PHP/INI file
if (defined('DEBUG_CONFIG'))
    include
'debug_config.php';
# SEE NOTED

// output functions
defined('DEBUG_FUNC_HOOK') || define('DEBUG_FUNC_HOOK','');
defined('DEBUG_MSGS_FUNC') || define('DEBUG_MSGS_FUNC','debug_msgs');
defined('DEBUG_DUMP_FUNC') || define('DEBUG_DUMP_FUNC','debug_datadump');
// error handlers - default arguments to debugerr()
defined('DEBUG_EHAND_FUNC') || define('DEBUG_EHAND_FUNC','debug_error_handler');
defined('DEBUG_XCEPT_FUNC') || define('DEBUG_XCEPT_FUNC','debug_exception_handler');
// messages global
defined('DEBUG_MESSAGES_GLOBAL') || define('DEBUG_MESSAGES_GLOBAL','debug_messages');
// output strings
defined('DEBUG_MSG') || define('DEBUG_MSG','message log:');
defined('DEBUG_DAT') || define('DEBUG_DAT','data dump:');
defined('DEBUG_OPEN') || define('DEBUG_OPEN',"\n<pre style='background:#fff;color:#000;font-family:monospace' id='debugmsgs'>");
defined('DEBUG_CLOSE') || define('DEBUG_CLOSE',"</pre>\n");
defined('DEBUG_EOL') || define('DEBUG_EOL',"\n");
// basic options
defined('DEBUG_EMPTY') || define('DEBUG_EMPTY',0);
defined('DEBUG_CALLS') || define('DEBUG_CALLS',2);
defined('DEBUG_TRUNC') || define('DEBUG_TRUNC',100);
defined('DEBUG_QSTR') || define('DEBUG_QSTR',1);
defined('DEBUG_ESCSTR') || define('DEBUG_ESCSTR',1);
defined('DEBUG_MSG_SEP') | define('DEBUG_MSG_SEP',' ');
defined('DEBUG_EMPTY_STR') || define('DEBUG_EMPTY_STR',1);
defined('DEBUG_TIMESTAMP') || define('DEBUG_TIMESTAMP',0);
defined('DEBUG_CONVERTNL') || define('DEBUG_CONVERTNL','\n');
// output options
defined('DEBUG_BASENAME') || define('DEBUG_BASENAME',2);
defined('DEBUG_EXECPT_FULL') || define('DEBUG_EXECPT_FULL',0);

// some new and/or just here
define('DBG_ESCNL',1);            // for debug_escape()
define('DBG_ESCNL2',2);

// output terminal or HTML
if (defined('DEBUG_TERM') || isset($GLOBALS['argv'])) {
   
define('DEBUG_STDERR',1);
   
define('DEBUG_HTML',FALSE);
   
define('DEBUG_BR',"\n");
   
define('DEBUG_MSG_STR',"\n".DEBUG_MSG);
   
define('DEBUG_DAT_STR',DEBUG_DAT);
}
else {
   
define('DEBUG_STDERR',0);
   
define('DEBUG_HTML',TRUE);
   
define('DEBUG_BR',"<br>\n");
   
define('DEBUG_MSG_STR',"\n<b>".DEBUG_MSG."</b>");
   
define('DEBUG_DAT_STR',"<b>".DEBUG_DAT."</b>");
}

# UGH the diagnostics in the global presents some special problems: see DOCS
if (!defined('DEBUG_NO_GLOBAL')) {
   
$GLOBALS[DEBUG_MESSAGES_GLOBAL] = '';
}

//  localhost example (for website development)
if (isset($_SERVER['SERVER_NAME']) && $_SERVER['SERVER_NAME'] == 'localhost') {
    if (!
defined('DEBUG_LOG'))
       
define('DEBUG_LOG',1);
   
// also can do this (change 'debug' to something else):
    //    define('DEBUG_LOG',(isset($_GET['debug'])) ? $_GET['debug'] : 1);
    // also can do this:
    //    error_reporting(-1);
}

// auto start - otherwise one must call these functions manually
if (!defined('DEBUG_NOAUTO') || !DEBUG_NOAUTO) {
    if (
defined('DEBUG_LOG'))
       
debuglog(DEBUG_LOG);
    if (
defined('DEBUG_ERR'))
       
debugerr(DEBUG_ERR);
    if (
defined('DEBUG_DUMP')) {
        if (
defined('DEBUG_DUMP_DATA'))
           
debugdump(DEBUG_DUMP,DEBUG_DUMP_DATA);
        else
           
debugdump(DEBUG_DUMP);
    }
}


/* API Start Here */

/* debuglog - set/reset the log level and/or error handler (see debugerr()) */

/*
  Typical of my programming style, braces not used if not required (it's part
  of the PHP standard), and _debug() is used as config data and as a state
  machine.
*/
function debuglog($log = NULL, $ehand = FALSE) {
    if (
$log === NULL)            // just return the log value
       
return _debug('log');

    if (
$ehand !== FALSE)            // pass on ehand value
       
debugerr($ehand);

   
$dlog = _debug('log',$log);

    if (
$log !== 0) {
        if (!
_debug('reg',1))        // do only once // 'debug_msgs'
           
register_shutdown_function(DEBUG_MSGS_FUNC);

       
// if new value is to store in global, set it empty
       
if ($log == DBG_MESSAGES)
           
$GLOBALS[DEBUG_MESSAGES_GLOBAL] = '';
    }
    if (
$log === 0) {

       
// if previous value was to store in global, reset
       
if ($dlog == DBG_MESSAGES)
           
$GLOBALS[DEBUG_MESSAGES_GLOBAL] = '';
    }
}

/* debugerr - independently install/restore error/exception handler */

function debugerr($ehand = 0, $efunc = DEBUG_EHAND_FUNC, $xfunc = DEBUG_XCEPT_FUNC) {
   
$err =& _debug('err');
   
$xerr =& _debug('xerr');

    if (
$ehand == 0)
        return array(
$err,$xerr);

    if (
$ehand & 1 && !$err) {
       
$err = 1;
       
_debug('ehand',$efunc);
       
// DEBUG_EHAND_FUNC 'debug_error_handler'
       
set_error_handler($efunc);
    }
    if (!
$ehand & 1 && $err) {
       
$err = 0;
       
_debug('ehand','');
       
restore_error_handler();
    }

    if (
$ehand & 2 && !$xerr) {
       
$xerr = 1;
       
_debug('xhand',$xfunc);
       
// DEBUG_XCEPT_FUNC 'debug_exception_handler'
       
set_exception_handler($xfunc);
    }
    if (!
$ehand & 2 && $xerr) {
       
$xerr = 0;
       
_debug('xhand','');
       
restore_exception_handler();
    }
}

/* debugdump - set data to dump at the end; default func: debug_datadump()

  This is a way to "assign" data to be displayed after all the messages.
  The data can be predefined PHP data such as $_GET or $_SERVER or an
  application's data (i.e. any data); see the DUMP_* defines above.

  This function sets the data to display via the function debug_datadump(),
  defined as 'DEBUG_DUMP_FUNC' above, but which can be changed to a user
  function. The function is a shutdown function registered here.

  The first arg is usually one of DUMP_ defines, but is overloaded descibed
  next. The second arg is to assign user data, a *space delimited* string of
  data to dump; the data must be either a global or a function that returns
  data. The third is the function to display the data as mentioned above.

  In PHP one cannot "unregister" a registered shutdown function, but it can
  be made to "do nothing".

  The term "dump" is not truly accurate (as is it's not a "data dump"); it
  is used because of PHP's var_dump().
*/

//                                                'debug_datadump'
function debugdump($dump = '', $data = '', $func = DEBUG_DUMP_FUNC) {
    if (
$data)
       
_debug('dumpdata',$data);    // so can do just this

   
if ($dump === '')
        return
_debug('dump');        // want to know what it is

   
_debug('dump',$dump);            // a defined constant

   
if (!$dump)                // it's been turned off
       
return;

    if (!
_debug('dreg',1))            // only do this part once
       
register_shutdown_function($func);
}

# Note on 'funcs' and 'files' settings: If 'funcs' is set it means ONLY
# that/those functions produce debug messages. This is important because
# if one forgets that a 'funcs' (or 'files') is set and one places debug()
# calls elsewhere, THEY will not show up!

/* debugfiles - set/unset messages per file/not file */

# UGH sloppy code
function debugfiles($files = '', $nofiles = '') {
// alternatively just call:
// _debug('files','file.php');        // only from these files
// _debug('files','index.php');        // (appends)
// _debug('nofiles','file.php');    // skip from this file
// see also debug.ini
   
if ($files == '' && $nofiles == '')
        return array(
_debug('files'),_debug('nofiles'));
   
_debug('files',$files);
   
_debug('nofiles',$nofiles);
}

/* debugfuncs - set/unset messages per function */

# UGH sloppy code
function debugfuncs($funcs = '', $nofuncs = '') {
// alternatively just call:
// _debug('funcs','functiona');        // only from these functions
// _debug('funcs','functionb');        // (appends)
// _debug('nofuncs','functionc');    // skip from this function
// see also debug.ini
   
if ($funcs == '' && $nofuncs == '')
        return array(
_debug('funcs'),_debug('nofuncs'));
   
_debug('funcs',$funcs);
   
_debug('nofuncs',$nofuncs);
}

/* debug - add (or display) data/message */

function debug($msg, $arg = 0, $aux = '') {
# UGH getting way to complicated

   
$log = _debug('log');
    if (
$log === 0 && $arg != DBG_HERENOW)
        return;

    if (
$a=_debug('arg')) {
       
$arg = $a;
    }

# SEE NOTEA
#    if (is_numeric($aux) && $aux > _debug('level')) {
#        return;
#    }
# SEE NOTEF
#    if (function_exists($t=DEBUG_FUNC_HOOK) || function_exists($t=$arg))
#        return $t($msg,$arg);

   
$d =& _debug();
    if (
$arg !== DBG_HERENOW && $arg !== DBG_ALWAYS) {
        list(
$file,$line,$func) = debug_caller(1);
       
$file = basename($file,'.php');
        if (
$d['files'] && !in_array($file,$d['files'])) {
            return
FALSE;
        }
        if (
$d['nofiles'] && in_array($file,$d['nofiles'])) {
            return
FALSE;
        }
        if (
$d['funcs'] && !in_array($func,$d['funcs'])) {
            return
FALSE;
        }
        if (
$d['nofuncs'] && in_array($func,$d['nofuncs'])) {
            return
FALSE;
        }
    }

# this is an interesting section; by creating $arg "words" rather than yet
# even more defines... so these got added everytime I had an idea for specific
# output; there can be an external file with these kinds of things... or a
# function or something... (a switch/case will actually not work here but
# this can be split up; and === nust be used as $arg can be different types)

   
if ($arg === '.!') {
        if (
$msg) return;
    }
    else
    if (
$arg === '.?') {
        if (!
$msg) return;
    }
    else
    if (
$arg === '.x')
        throw new
Exception($msg);
    else
    if (
$arg === '.exit')
        exit(
$msg);
    else
    if (
$arg === '.esc')
       
$msg = debug_escape($msg);
    else
    if (
$arg === '.def') {
       
$msg = debug_define($msg);
       
$arg = DBG_NOQNL;        // YAU (yet another UGH)
   
}
    else
    if (
$arg === '.version') {
       
$msg .= 'Debug v'.DEBUG_VERSION.' ('.__DIR__.')';
       
$arg = DBG_NOQ;
    }
    else
    if (
$arg === '.mem')
       
$msg = 'mem: '.memory_get_usage();
    else
    if (
$arg === '.type')
       
$msg = gettype($msg);
    else
    if (
$arg === '.dump') {
       
$msg = debug_dump($msg);    // var_dump
#        $msg = debug_data($msg);    // simpler string
   
}
    else
       
$msg = debug_typestr($msg,$arg);// this does the type to string

# UGH and this one sometimes is a bother...
   
if (is_string($msg) && DEBUG_QSTR && $arg !== DBG_NOQ)
       
$msg = "\"$msg\"";
    else
    if (
$arg === DBG_Q)
       
$msg = "\"$msg\"";

# TMU TOTALLY MESSED UP
   
if ($arg !== '.dump' && DEBUG_TRUNC > 0 && strlen($msg) > DEBUG_TRUNC)
       
$msg = substr($msg,0,DEBUG_TRUNC).' ...';//['.DEBUG_TRUNC.']';

   
if (DEBUG_CONVERTNL !== 0) {
       
$f = ($arg !== DBG_ESCNL) ? DBG_ESCNL : $arg;
       
$msg = debug_escape($msg,$f);
    }
# ??? should there be an else 'tween
   
if (DEBUG_ESCSTR && is_string($msg) && $arg !== '.noesc')
       
$msg = debug_escape($msg);

    if (
is_string($arg) && $arg[0] != '.')
       
$msg = $arg.$msg;

    if (
DEBUG_TIMESTAMP == 1 && $arg !== '.time')
       
$msg = microtime(1).' '.$msg;

// final format
# UGH if html output, convert entities, but not if:
#     $log is DBG_HTML_COMMENT  which is diagnostics within <!-- -->
#     $log is DBG_HTML          which is each message in <!-- -->
#     $arg is DBG_HTML          which is one-time message in "
#     $arg is DBG_NOENTS

   
if (DEBUG_HTML && $arg != DBG_HTML && $arg != DBG_NOENTS &&
               
$log != DBG_HTML_COMMENT && $log != DBG_HTML)
       
$msg = htmlentities($msg,ENT_NOQUOTES);
// call trace
# UGH messy code
   
if ($arg === DBG_DEEP) {
       
$msg = debug_calls('',7).DEBUG_MSG_SEP.$msg;
    } else
    if (
$d['calls'] && $arg !== DBG_NOTRACE) {
       
$p = $d['depth'];
       
$c = $d['calls'];
        if (
is_numeric($aux))
           
$p = $aux;
        if (
$arg == DBG_UP) ++$p;
       
$cs = debug_calls($p,$c);
       
$msg = $cs.DEBUG_MSG_SEP.$msg;
    }

// return?
   
if ($arg == DBG_RETURN)
        return
$msg;
// print?
   
if ($log == DBG_HERENOW || $arg == DBG_HERENOW)
        print
$msg . DEBUG_BR;
    else
    if (
$log == DBG_HTML || $arg == DBG_HTML)
       
debug_html($msg);
// store
   
else
    if (
$log == DBG_MESSAGES)
       
$GLOBALS[DEBUG_MESSAGES_GLOBAL] .= $msg.DEBUG_BR;
    else
       
_debug('.msg',$msg);

    return
TRUE;
}

/* debug_typstr - convert type to string */

function debug_typestr($msg, $arg = false) {
    if (
$arg === '.time') {
       
$end = microtime(true);
       
$time = $end - _debug('time');
       
$msg = "$msg: $time seconds";
    }
    if (
$msg === NULL)
       
$msg = '(null)';
    else
    if (
$msg === TRUE)
       
$msg = '(true)';
    else
    if (
$msg === FALSE)
       
$msg = '(false)';
    else
    if (
$msg === '' && (DEBUG_EMPTY_STR || $arg))
       
$msg = '(empty)';
    else
    if (
is_resource($msg))
       
$msg = 'Resource '.get_resource_type($msg);
    else
    if (
is_object($msg))
       
$msg = 'Object '.get_class($msg);
    else
    if (
is_array($msg))
       
$msg = debug_data($msg,'',0);

    return
$msg;
}

/* debug_global_end - put some extra feedback in the debug result data */

function debug_global_end($e = 0) {
    if (
$nf = debug_skipping()) {
       
$GLOBALS[DEBUG_MESSAGES_GLOBAL] =
           
$nf.$GLOBALS[DEBUG_MESSAGES_GLOBAL];
    }
    if (
$e)
       
debug_echo($GLOBALS[DEBUG_MESSAGES_GLOBAL]);
}

/* SHUTDOWN FUNCTIONS - register_shutdown_function() */

/* debug_msgs - display the message log [DEBUG_MSGS_FUNC] */

# UGH this function especially needs to be rewritten/redesigned; perhaps
#    split off into two or three similar, and simpler, versions

function debug_msgs() {
   
restore_error_handler();        // no longer wanted
                        //  (in case error here)
   
$log = _debug('log');
    if (
$log <= 0)                // logging is off
       
return;           
    if (
$log >= DBG_MESSAGES) {
        if (
$log == DBG_MESSAGES)    // is in global
           
debug_global_end();
        return;
    }

   
$msgs = _debug('msgs');
    if (
count($msgs) == 0 && !DEBUG_EMPTY)
        return;

    if (
$log == DBG_HTML_COMMENT)        // 1
       
print "\n<!--";
    else
    if (
DEBUG_HTML)
        print
DEBUG_OPEN;

   
debug_echo(DEBUG_MSG_STR);

    if (
$nf = debug_skipping())
       
debug_echo($nf);

    foreach (
$msgs as $s) {
        if (
$log == DBG_HTML_COMMENT)
           
$s = debug_obcomments($s);
       
debug_echo($s);
    }

    if (
DEBUG_TIMESTAMP == 2) {
       
$end = microtime(true);
       
$time = $end - _debug('time');
       
debug_echo("debug end: run was $time seconds");
    }

    if (
$log == DBG_HTML_COMMENT)
        print
"-->\n";
    else
    if (
DEBUG_HTML && !_debug('dump'))
        print
DEBUG_CLOSE;
}
// 1 With all diagnostics within "<!--", "-->", any "--" in a diagnostic
//  will be considered and HTML specification error.

/* debug_datadump - display various global data */

# UGH overly complicated near stupid and definately sloppy code

function debug_datadump() {

    if ((
$dump = _debug('dump')) == 0)
        return;
   
$log = _debug('log');

# SEE NOTEF
   
if (function_exists($dump))
        return
$dump($log);

    if (
$log == DBG_HTML_COMMENT || $log == DBG_HTML) // see 1 above
       
print "\n<!--";
    else
    if (
DEBUG_HTML && ($log <= 0 || $log >= DBG_MESSAGES))
        print
DEBUG_OPEN;

   
debug_echo(DEBUG_DAT_STR);

   
ob_start();
# SEE NOTEO

# UMM if $dump were bit values the outputs could be combined, but DUMP_USER
#    if highly configurable

   
if ($dump == DUMP_USER) {
       
$data = explode(' ',_debug('dumpdata'));
        foreach (
$data as $var) {
            if (
function_exists($var))
               
debug_data($var(),"$var() ",2);
            else
            if (isset(
$GLOBALS[$var]))
               
debug_data($GLOBALS[$var],"\$$var ",2);
            else
                print(
"\"$var\" not global or function\n");
        }
    }
    else
    if (
$dump == DUMP_INPUTS) {
       
debug_data($_GET,'$_GET: ');
       
debug_data($_POST,'$_POST: ');
    }
    else
    if (
$dump == DUMP_USER_CONSTANTS) {
       
$defs = get_defined_constants(true);
       
debug_data($defs['user'],'CONSTANTS: ');
    }
    else
    if (
$dump == DUMP_CONSTANTS) {
       
$defs = get_defined_constants(true);
       
debug_data($defs,'CONSTANTS: ');
    }
    else
    if (
$dump == DUMP_GLOBALS) {
       
debug_data($GLOBALS,'$GLOBALS: ');
    }
    else
    if (
$dump == DUMP_SERVER) {
       
debug_data($_SERVER,'$_SERVER: ');
    }
    else
    if (
$dump == DUMP_DEBUG) {
       
$debug = _debug();
       
$debug['msgs'] = array();
       
debug_data($debug,'$debug: ');
    }

   
$data = ob_get_contents();
   
ob_end_clean();

    if (
DEBUG_HTML)
       
$data = ($log == DBG_HTML_COMMENT) ? debug_obcomments($data) : debug_obentities($data);

   
debug_echo($data);

    if (
$log == DBG_HTML_COMMENT || $log == DBG_HTML)
        print
"-->\n";
    else
    if (
DEBUG_HTML)
        print
DEBUG_CLOSE;
}

/* SUPPORT FUNCTIONS */

/* debug_echo - echo string  */

function debug_echo($msg = '', $eol = DEBUG_EOL) {
    if (
DEBUG_STDERR)
       
fwrite(STDERR,$msg.$eol);
    else
        echo
$msg.$eol;
}

function
debug_html($msg, $eol = DEBUG_EOL) {
    print
'<!-- '.debug_obcomments($msg).' -->'.$eol;
}

/* debug_exit - echo string and exit with context */

function debug_exit($msg = '', $x = 0) {
    if (
is_numeric($msg)) {
       
$x = (integer)$msg;
       
$msg = '';
    }
    if (
debuglog() == 1) {
       
$c = '';
       
$f = basename(__FILE__). ',' . __LINE__;
       
$c = debug_calls();
        if (!
is_numeric($msg) && $msg != '') {
           
debug_echo("$msg $c");
        } else
           
debug_echo("\nexit($x)\n[from $f $c]");
    }
    exit(
$x);
}

/* debug_caller - caller data (file,line,function) as array or one as string */

function debug_caller($depth = 1, $n = '') {
# DND SEE NOTEN
   
$bt = debug_backtrace();
    if (!isset(
$bt[$depth]))
       
$a = array('','','');
    else {
       
$b =& $bt[$depth];
       
$file = (isset($b['file'])) ? $b['file'] : '-';
       
$line = (isset($b['line'])) ? $b['line'] : '-';
       
$func = (isset($bt[$depth+1])) ? $bt[$depth+1]['function'] : '-';
       
$file = debug_remdir($file);
       
$a = array($file,$line,$func);
    }
    if (
$n) {
       
$n = array_search($n,array('file','line','function'));
        return
$a[$n];
    }
    return
$a;
}

/* debug_calls - caller data (file,line,function) as string */

# UGH Yes, two of these IS bothersome...
function debug_calls($depth = '', $count = '') {
# DND
   
if ($depth === '') $depth = _debug('depth');
    if (
$count === '') $count = _debug('calls');

   
$bt = debug_backtrace();
   
$n = count($bt) - 1;
   
$s = '';
// look backward into the call stack
   
do {
       
$b =& $bt[$depth];
       
$file = (isset($b['file'])) ? $b['file'] : '-';
       
$line = (isset($b['line'])) ? $b['line'] : '-';
       
$func = (isset($bt[$depth+1])) ? $bt[$depth+1]['function'] : '-';
       
$file = debug_remdir($file);
       
$s = "($file,$line,$func)" . $s;
        if (++
$depth > $n)
            break;
    }
    while (--
$count);
    return
$s;
}
/*
  See NOTEL, below, as every "environment" PHP runs in can/will be different:
  CLI, Web server, IDE... And then! Oh! VSCODE/Cygwin makes the backtrace()
  wierd; sometimes doing this:

   ["file"]=>
  string(62) "/srv/www/htdocs/test/c:\cygwin\srv\www\htdocs\test\test.php"

  I ain't gonna waste time on WHY (already wasted too much on that).
*/


const DMP_VAR = 1;    // use var_export
const DMP_RED = 2;    // reduce array data
const DMP_HTM = 4;    // use htmlentities
const DMP_PRE = 8;    // <pre></pre>
const DMP_OUT = 16;    // display result
const DMP_BR  = 32;    // convert "\n" to "<br>"
const DMP_HCM = 64;    // within "<!-- -->"

/* debug_dump - a variation of var_dump() (the output a condensed string) */

# UGH overly complicated near stupid and definately sloppy code

function debug_dump($var, $msg = '', $opt = DMP_RED) {
static
$s = array("\n", '> ', ', )');
static
$r = array(' ' , '> ', ' )');

   
$func = ($opt & DMP_VAR) ? 'var_export' : 'var_dump';

    foreach (
$var as &$v)
       
$v = debug_escape($v,DBG_ESCNL);

   
ob_start();
    if (
$msg)
       
debug_echo($msg);
   
$func($var);
   
$out = ob_get_contents();
   
ob_end_clean();
    if (
$opt & DMP_BR) {
       
$out = str_replace("\n","<br>",$out);
    }
    if (
$opt & DMP_RED) {
       
$out = str_replace($s,$r,$out);
       
$out = preg_replace('/\s\s+/',' ',$out);
    }
    if (
$opt & DMP_HTM) {
       
$out = htmlentities($out,ENT_NOQUOTES);
    }
    if (
$opt & DMP_PRE) {
       
$out = "\n<pre>\n$out\n</pre>\n";
    }
    if (
$opt & DMP_HCM) {
       
$out = "\n<!-->\n$out\n-->\n";
    }
    if (
$opt & DMP_OUT) {
       
debug_echo($out);
        return;
    }
    return
$out;
}

/* debug_data - format any PHP type to string */

/*
  Example of bad coding is this: multiple return points ain't bad in
  of itself but combined with multiple return types and echo or return
   *IS* a bad thing... Instead of one function that does three things
  based on parameters, there should be three functions (perhaps with a
  "helper" function).
*/
function debug_data($var, $s = '', $p = 1, $nl = 0) {

    if (!
is_array($var)) {
       
$out = debug_typestr($var);
        goto
out;
    }
   
$out = '(';
    foreach (
$var as $k => $v) {
        if (
is_array($v))
           
$v = debug_data($v,$s,$p,$nl);
# FIX use _typestr(), recursing for each array elmement, ...
       
if ($v === '')
           
$v = "''";
//        else
//            $v = debug_escape($v,DBG_ESCNL);

       
$out .= " $k => $v,";
        if (
$nl)
           
$out .= "\n";
    }
   
$out .= " )";
out:
   
$out = $s . $out;
    if (
$p === 0)
        return
$out;

    if (
$p === 1)
       
debug_echo($out);
    else
    if (
$p === 2)
        print(
"$out\n");
}

/*
func tion debug_datax($var, $s = '', $p = 0) {
    $out = print_r($var,true);
    if ($s) {
        $out = $s . $out;
    }
    if ($p == -1) {
        return $out;
    }
    if ($p == 0) {
        debug_echo($out);
        return;
    }
    // convert a bit to show some escapes
    $out = str_replace("\r",'\r',$out);
    $out = str_replace("\n",'\n',$out);
    // and then, once, at band camp...
    if ($p == 1) {
        $out = htmlentities($out);
    }
    debug_echo($out);
}
*/

# NUY
function debug_vardump() {
    echo
debug_calls(),' ';
   
call_user_func_array('var_dump',func_get_args());
    if (
DEBUG_HTML) echo "<br>\n";
}

/* NOTEL (location)

  Figure out where the code is (this weird sh^Htuff is terminal vs. HTML
  or if the included file lies outside the working directory).

  This is really sloppy code, but... the "issue" is "where are the files?"
  The question is for removing the dirname from the debug output, e.g.:

      "/srv/www/htdocs/dir/index.php" to: "index.php" [OR]
      "/srv/www/htdocs/dir/index.php" to: "dir/index.php"

  Which is just to lessen the diagnostic string length as generally one
  knows where one's files are.

  The "thing" is, in terminal mode, DOCUMENT_ROOT is not defined and PHP_SELF
  and SCRIPT_FILENAME do not have a path, so there's that.

  But the previous way this "figured out" the path to strip expected this
  file to be in the same base directory as the code being debugged. It broke
  if that was not the case - e.g. "include '../debug/debug.php';".

  To not make the following even worse, in the special case, put this before
  the include: "define('DOC_ROOT',dirname(__DIR__).DIRECTORY_SEPARATOR);".

  This section (which should be in a function) has always bugged me (pun not
  intended) depending on where the code is located and, importantly, where
  server is -- i.e. Cygwin/Linux/Windows -- as often the path detection
  fails under different configurations (sigh).
*/

# UGH sloppy code
if (defined('DEBUG_TERM') || isset($argv)) {
   
define('DEBUG_ROOT',__DIR__.DIRECTORY_SEPARATOR);
}
elseif (empty(
$_SERVER['DOCUMENT_ROOT'])) {
   
define('DEBUG_ROOT',dirname($_SERVER['SCRIPT_FILENAME']).DIRECTORY_SEPARATOR);
}
else {
   
define('DEBUG_ROOT',$_SERVER['DOCUMENT_ROOT'].dirname($_SERVER['PHP_SELF']).DIRECTORY_SEPARATOR);
}
if (!
defined('DOC_ROOT')) {
    if (!
$_SERVER['DOCUMENT_ROOT']) $_SERVER['DOCUMENT_ROOT'] = getcwd();
   
$t = str_replace("/","\\",$_SERVER['DOCUMENT_ROOT']);
   
define('DOC_ROOT',$_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR);
}

/* debug_remdir - remove path(s) from string */

function debug_remdir($file) {
# DND
   
if (DEBUG_BASENAME == 0)
        return
$file;
    if (
DEBUG_BASENAME == 2)
        return
basename($file);
   
$s = (strpos($file,DEBUG_ROOT)!==FALSE) ? DEBUG_ROOT : DOC_ROOT;
   
$f = str_replace($s,'',$file);
    return
$f;
}

/* debug_escape - turn low value characters into escape sequences */

function debug_escape($str, $type = 0) {
    if (
$type & DBG_ESCNL) {
       
$f = array("\n","\r","\t",);
//        if ($type & DBG_ESCNL2)
//            $t = array('"\n"','"\r"','"\t"',);
//        else
           
$t = array('\n','\r','\t',);
        return
str_replace($f,$t,$str);
    }
   
$s = '';
    for (
$i = 0, $n = strlen($str); $i < $n; $i++) {
       
$c = $str[$i];
        if ((
$o=ord($c)) < 32 && $o != 27 && $o != 10)
           
$c = sprintf('\\%03o',ord($c));
       
$s .= $c;
    }
    return
$s;
}

function
debug_define($s) {
    if (
defined($s)) {
       
$c = constant($s);
       
$s = "'$s' = \"$c\"";
    }
    return
$s;
}

/* debug_obentities - OB callback for dump data for HTML */

function debug_obentities($buffer) {
    return
htmlentities($buffer);
}

/* debug_obcomments - OB callback for dump data for in HTML comment */

# UGH Why, WHY is it so bad to have a "--" inside an HTML comment? Why?

function debug_obcomments($buffer) {
    return
preg_replace('/\-{2,}/','-',str_replace('-->','',$buffer));
}

/* debug_php_error - get last PHP error string */

function debug_php_error() {
   
$e = error_get_last();            // sometimes does not work
   
if (!$e)
       
$s = _debug('php_error','');    //  and this will (get, unset)
   
else
       
$s = $e['message'];

    return
$s;
}

/* debug_color - pass color and message to wrap for html/terminal color */

# NTS makes $head BOLD

function debug_color($color, $msg, $head = '') {
static
$ANS = [
'normal' => "\e[0m",
'bold'  => "\e[1m",
'under'  => "\e[4m",
'red'    => "\e[31m",
'green'  => "\e[32m",
'yellow' => "\e[33m",
'orange' => "\e[33m", // no orange, use yellow
'blue'  => "\e[34m",
'purple' => "\e[35m",
'cyan'  => "\e[36m",
'white'  => "\e[37m",
];
# SEE NOTEC
   
if (DEBUG_HTML) {
        if (
$head)
           
$head = "<b>$head</b>";
       
$msg = "<span style='color:$color'>$head$msg</span><br>";
    }
    else {
        if (
$head)
           
$head = $ANS['bold'].$head.$ANS['normal'].$ANS[$color];
       
$msg = $ANS[$color].$head.$msg.$ANS['normal']."\n";
    }
    return
$msg;
}


/* ERROR HANDLERS */

/* error_handler - default error handler [DEBUG_EHAND_FUNC] - via debugerr() */

function debug_error_handler($errno, $errstr, $errfile, $errline) {
   
$errfile = basename($errfile);
   
$error = "$errstr ($errfile, $errline)";

   
_debug('php_error',$error);        // save for later use

   
if (ini_get('html_errors'))
       
$error = preg_replace('/\[.*\]/','',$error);

    if ((
$log = debuglog()) != DBG_HTML_COMMENT && $log != DBG_HTML) {
        if (
$errno == E_NOTICE)
           
$error = debug_color('orange',$error,'Notice: ');
        else
           
$error = debug_color('red',$error,'Error: ');
    }
# SEE NOTEH

   
if (!debuglog()) {
       
debug_echo($error);
    }
    else {
       
$c = debug_calls();
       
$log = _debug('log');
       
$error = "$c $error";
        if (
$log == DBG_MESSAGES)
           
$GLOBALS[DEBUG_MESSAGES_GLOBAL] .= $error.DEBUG_EOL;
        else {
            if (
$log == DBG_HTML)
               
debug_html($error);
            else
               
_debug('.msg',$error);
        }
# SEE NOTEQ
   
}
    return
TRUE;
}

/* debug_exception_handler - an example exception handler [DEBUG_XCEPT_FUNC] */

function debug_exception_handler($ex) {
   
$s = $ex->__toString();
   
$h = '';
    if (
preg_match('/^([^:]*:)/',$s,$m)) {
       
$h = $m[1];                // get "header"
       
$s = str_replace($h,'',$s);        // remove it
       
$s = str_replace(DOC_ROOT,'',$s);    // trim paths
       
$s = trim($s);
    }
    if (
DEBUG_HTML) {
       
$s = nl2br($s);
       
$h .= '<br>';
    } else {
       
$h = "\n";
    }
   
$x = debug_color('orange','EXCEPTION');        // extra header
   
$s = debug_color('red',$s,$h);            // color it
   
if (DEBUG_HTML) $s = "<p><b>$x</b> $s</p>";
   
debug_echo($s);       
   
debug_exit('',1);
}

/* _debug - debug initialize and configuration */

# UGH If called too early, like during debug_config(), can cause undefined
#    DEBUG_CALLS error, but let's wait on fixing that...

function &_debug($key = NULL, $val = NULL) {
static
$debug = array();
static
$nil = NULL;
//static $n = 1; // se below
# NTS for $var =& _debug('var') to work 'var' has to exist in the array, and
#    if not, must return something static
   
if ($debug === array()) {
       
$debug['1st'] = 0;
       
$debug['log'] = 0;
       
$debug['err'] = 0;
       
$debug['xerr'] = 0;
       
$debug['level'] = 0;
       
$debug['depth'] = 1;
       
$debug['msgs'] = array();
       
$debug['php_error'] = '';
       
$debug['calls'] = 2; // DEBUG_CALLS;
       
$debug['time'] = microtime(true);
       
$debug['dump'] = $debug['dumpdata'] = '';
       
$debug['files'] = $debug['nofiles'] = array();
       
$debug['funcs'] = $debug['nofuncs'] = array();
    }

    if (
$key === NULL) {                // return array
       
return $debug;
    }

   
// special cases
   
if (is_array($key)) {                // return some
       
foreach ($key as $k) {
           
$a[] = (isset($debug[$k])) ? $debug[$k] : $nil;
        }
        return
$a;
    }

    if (
$key == '.msg') {                // add a message
       
$debug['msgs'][] = $val;
//        $debug['msgs'][] = "$n $val";        // look ma, line nu.s
//        $n++;
       
return $nil;
    }

    if (
strpos('files funcs nofiles nofuncs',$key) !== FALSE) {
       
$v = $debug[$key];
        if (!
$val)                // clear
           
$debug[$key] = array();
        else {                   
// add
           
if (strpos($val,' '))
               
$val = explode(' ',$val);
            if (
is_array($val))
               
$debug[$key] += $val;
            else
               
$debug[$key][] = $val;
        }
        return
$v;
    }

    if (
$val !== NULL) {                // set a value
       
$v = (isset($debug[$key])) ? $debug[$key] : $nil;
       
$debug[$key] = $val;
        return
$v;                // (return old value)
   
}

    if (!isset(
$debug[$key]))            // get a value
       
return $nil;

    return
$debug[$key];
}

/* debug_skipping - experimental string for feedback of files/funcs skipped */

function debug_skipping($eol = DEBUG_EOL) {
   
$nf = '';
    if (
$nofuncs = _debug('nofuncs'))
       
$nf .= 'nofuncs: '.implode(' ',$nofuncs). DEBUG_BR;
    if (
$nofiles = _debug('nofiles'))
       
$nf .= 'nofiles: '.implode(' ',$nofiles). DEBUG_BR;
    return
$nf;
}

/* debug_config - set debug internal data and/or make defines from INI file */

function debug_config($file) {
# DND
   
if ($file === '')                // install dir
       
$file = getcwd().'/debug.ini';
    else if (
$file === false)
       
$file = __DIR__.'/debug.ini';        // current dir

   
if (!file_exists($file))
        return;

   
$data = parse_ini_file($file,TRUE);
    if (isset(
$data['defines'])) {
        foreach (
$data['defines'] as $def => $val) {
           
$def = 'DEBUG_'.strtoupper($def);
            if (
is_numeric($val))
               
$val = (int)$val;    // cosmetic
           
if (!defined($def))
               
define($def,$val);
        }
    }
   
// "top" variables
   
foreach ($data as $k => $v) {
        if (
is_array($v))
            break;
       
_debug($k,$v);
    }
}

/* debug_js_print - a hastily created thing; kind of a proof of concept */

function debug_js_print($link = "debug window") {
//<a href='#' onclick='dwindow();return false;'>$link</a>
$html = "
<script type='text/javascript'>
function dwindow(){
var w = window.open('','debug','width=800,height=400,left=10,top=10,resizable,scrollbars=yes');
w.document.write(\\\"<pre>\$messages</pre>\\\");
w.document.close();
}
</script>
"
;
   
$messages = $GLOBALS[DEBUG_MESSAGES_GLOBAL];    // copy
   
$GLOBALS[DEBUG_MESSAGES_GLOBAL] = '';        // empty
   
$messages = str_replace("\n",'',$messages);
   
$messages = addcslashes($messages,"\"");
    eval(
"print \"$html\";");
}

/* END */

/* NOTES

  NOTE0

  The use of negative numbers, and numbers 99 and above, did have logic
  when this code was started - some range tests to control the output for
  example... But complexity is the root of, um, complexity. Some of these
  should have been bit-based for OR and AND.

  NOTEQ

  Sometimes a string would be nice to be in quotes; but not always. Like:

  debug($arg);                // nice if $arg is string

  debug($name,'load: ');            // not nice as it's a message
  debug("load: $name",DBG_NOQ);    // ah...

  NOTED

  All these defines look like cruel and unusual programming. The use of
  constants to to change how the code works (and some output) does eliminate
  globals variables or a function to do the equivalent.

  Instead of having a config file "you must create before using the API",
  this way it works "by default", and if one wants to change anything, one
  defines those constants before including the API.

  But there is a "debug.ini" config file, and a "debug_config.php" file. The
  idea of those is to use them and to remove all the defines here...

  NOTEA

  The $aux argument is experimental and comes with no examples - but it is
  kind of obvious... and later versions may do something different.

  NOTEN

  Lines with '# DND' is a reminder that debug() can not be called in such a
  function (result is recursion). However, a string can be "put into the
  debug queque" directly:

      _debug('.msg',$msg); // OR:
      _debug('.msg',debug_calls().$msg);

  And (yeah, quite a few of those), some functions like debug_calls() can
  also not be called from some functions, like _debug(), as the possiblity
  of INF_REC...

  NOTEF

  This allows for an external function to run in place of debug(), which is
  not as odd as it sounds in that in that it is yet another way to change
  how things work during runtime.

  NOTET

  The call trace needs to skip the handler's function itself which would be:

      (test.php,49,-)(debug.php,698,debug_error_handler) Notice: Undefined variable: x

  This is done by just getting it by:

      $c = debug_calls();

  The result is put into the output queue directly (as it has already been
  formatted):

      _debug('.msg',"$c $error");

  NOTEX

  A typical trace output in the exception handler (using debug_backtrace()),
  will be less than useful:

      (debug.php,714,debug_exception_handler) Fatal Error: Call to undefined function x()

  But the exception data has it's own backtrace which is used. And like the
  error handler the message is put into the queue.

  NOTEE

  PHP says, "errcontext will contain an array of every variable that existed
  in the scope the error was triggered in. User error handler must not modify
  error context."

  Unsetting it is not really modifying it - as opposed to changing it's data.
  But since it's passed by value...

  NOTEC

  The adding of color (see also NOTEH) was poorly done: can not turn it
  off; can not easily change the colors; doing so in a similar manner of
  everything else would mean even more DEBUG_ constants... The PHP error
  handler (debug_error_handler) uses 'orange' and 'red' (string literals
  are constants) for warnings and errors respectively. The exception
  handler uses 'red'.

  NOTEH

  The use of HTML in the output, though it may "look nice", does make the
  code - in my opinion (and I wrote it) - way too complex with much more
  work needed to maintain it (and with complexity comes error proneness).

  NOTEH

  The HTML in the exception handler is a DIV - see index.php - though the
  other messages with color are all inline string literals... A later version
  may enable the debug diagnostics' formatting to be configurable.

  NOTEO

  In terminal mode debug_echo() uses fwrite(STDERR,) which is not buffered by
  ob_start(). The debug_data() (and debug_dump()) function is fairly complex,
  and giving it an option to not use STDERR just makes it worse...

*/

// END