#native_company# #native_desc#
#native_cta#

phpOBrowser

By Scott Greenberg
on July 10, 2004

Version: 1.1.0

Type: Full Script

Category: Other

License: GNU General Public License

Description: phpOBrowser is a dynamic object browser that allows you to browse through all available folders, files, sections, and objects in your php application and see their classes, properties, methods and constants that are available.

<?php
/**
 * phpOBrowser is a dynamic object browser that allows you to browse
 * through all available folders, files, sections, and objects in any
 * php application and see classes, properties, methods and constants
 * that are available.
 *
 * - Website:    http://gogogadgetscott.info
 * - Requires:   PHP 4.2.0, JavaScript 1.2+ enable browser
 * - Additional files: images and CSS can be found at
 *    http://www.gogogadgetscott.info/computers/scripts/phpob.php
 *
 * @package phpOBrowser
 * @author Scott Greenberg <[email protected]>
 * @version $Id: index.php, v 1.1.0, 05/14/2004 -- 07/10/2004$
 * @copyright Copyright 2004. SG Technology. All rights reserved.
 */

define('VERSION', '1.1.0');
define('CREATED', '05/14/2004');
define('UPDATED', '07/10/2004');

$notes = <<<EOB
 Change Log:
  1.1.0
   - Parses DocBlocks
   - Displays DocBlocks as a tooltip for individual objects
   - Fixed error 404 bug when using non-standard script 
     file name i.e. phpOBrowser-1.1.0.php
  1.0.2
   - Finds multiply classes in a single document.
   - Convert source comment to phpDocyment tags.
   - Multi-line array are found as constents.
   - Reformatted source to conform with PEAR Coding Standards,
     with a exption of the header comment block.
   - Allowed for forward-slash, '/' in section name.
  1.0.1
   - Better file handling methods, hands exceptions.
   - Prevent finding non-php objects i.e. JavaScript, by
     removing Heredoc style strings before parsing.
   - Allows for a third section ("fold markers")
     style '// {{{ section name // }}}'.
   - Finds returning reference functions, '&returns_reference()'.
   - Allows for properties set as an array.
  
 To do:

 References:
  1. The PHP Documentation Group. PHP Manual.
  <a href="http://www.php.net/docs.php">http://www.php.net/docs.php</a>: The PHP Documentation Group, 2004.
  2. Mark Wilton-Jones. Collapsible lists. HowToCreate.co.uk, 2002.
  <a href="http://www.howtocreate.co.uk/tutorials/jsexamples/collapseList.html">http://www.howtocreate.co.uk/tutorials/jsexamples/collapseList.html</a>.
  3. The PEAR Documentation Group. PEAR Manual.
  <a href="http://pear.php.net/manual/en/">http://pear.php.net/manual/en/</a>: The PEAR Documentation Group, 2004.
  

EOB;
$notes = nl2br($notes);

/*
 * Start the timer for the page parse time
 */
define('PAGE_PARSE_START_TIME', microtime());

// -------------------------------------------------
// Script Setup
// -------------------------------------------------
    
/*
 * Sets the name of your application:
 */
$AppTitle = "phpOBrowser";

/*
 * Folder where your source files reside:
 * Don't add trailing slash, '/' or ''
 */
$AppFolder = "";

/*
 * Folder where image files reside:
 * May be left empty if image are in same folder as script
 * requires trailing slash, '/'.
 */
$imgFolder = "img/";

/*
 * Source files have one of these extensions:
 */
$phpexten = array('php'); // 'inc','php4','php3','php','phtml','class'
      
// -------------------------------------------------
// Script Options.
// -------------------------------------------------

/*
 * Secure page.
 */
define('SECURE', false); # true);
define('USER', 'jane');
define('PASSWORD', 'abc');

/*
 * Use frames as default, this is best for large applications.
 * Option to switch is available at run-time.
 */
define('FRAMESDEFAULT', false); # true);
/*
 * When frames are use the file path of reviled object is passed
 * in the URL at a get variable.  If appellation is not secure.
 * unauthorized access to objects could be granted.                    
 */
define('ALLOWFRAMES', true); # false);

/* 
 * Parse Classes.
 * If set to false object within classes will still be parsed.
 * This just prevent the class name from being listed.
 * Use this if more then more then one class is within a file.
 */
define('PARSECLASSES', true); # true false

/*
 * Object sorting.
 */
define('SORT', 'none'); # 'none' 'aAsc' 'aDes' '1Asc' '1Des'

/*
 * Display bar setting.
 * Tool bar options include: -img -text
 */
define('TOOLBAR', '-img');
define('SHOWPARSETIME', true); # true false

/*
 * Treeview display.
 */
define('SHOWLINES', false);  # true false

/*
 * Show DocBlocks as a infomation tool tip.
 */
define('SHOWINFO', true);  # true false

/*
 * Retrieve script filename.
 */
$path_parts = pathinfo($_SERVER['SCRIPT_NAME']);
$file = $path_parts["basename"];

/*
 * Retrieve global post/get arguments.
 * Prevents the need for register_globals to be on.
 */
$request_args = array('username','password','action','path','section');
foreach ($request_args as $i => $value) {
    if (isset($_REQUEST[$value])) {
        ${$value} = $_REQUEST[$value];
    } else {
        ${$value}  = '';
    }
}

if (ALLOWFRAMES == true) {
    $valid_action = array('noFrame','about','copying','license','Frame','topFrame','leftFrame','mainFrame');
} else {
    $valid_action = array('noFrame','about','copying','license');
}
if (!in_array($action, $valid_action)) {
    if (FRAMESDEFAULT == true && ALLOWFRAMES == true) {
        $action = "Frame";
    } else {
        $action = "noFrame";
    }
}
$AppTitle .= ' - phpOBrowser';
if ($action == 'about') {
    $AppTitle .= ' :: About';
}
if (empty($AppFolder) || $action == 'about') {
    $AppFolder = $file;
}
$sort = 'none';
$valid_sort = array('none', 'aAsc', 'aDes', '1Asc', '1Des');
if (defined('SORT')) {
    if(in_array(SORT, $valid_action)) {
        $sort = SORT;
    }
}

// -------------------------------------------------
// System values
// -------------------------------------------------

$subs[0] = -1;        // Array containing position of list
$node = 0;            // indication of nodes reltive positon
$nodes = array();     // all of the folders, files and objects
$img_types = array(); // node image list of javascript treeview
$html['nodes'] = '';  // HTML output
$error_msg = '';      // Error messages

// -------------------------------------------------
// HTML
// -------------------------------------------------

$html['type'] = <<<EOH
    <?xml version "1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">

EOH;
$html['type_frame'] = <<<EOH
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">

EOH;
$html['head_top'] = <<<EOH
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
    <title>phpOBrowser: PHP Web Application object browser</title>
    <link href="phpOBrowser.css" rel="stylesheet" type="text/css" />
    
EOH;
$html['head_bottom'] = "</head>n";
$html['body_top']['noFrame'] = <<<EOH
<body>
    <div class="display-1-columns"><div class="display-padded">
        <div class="window-outside">
            <div class="window-inside">
                <div class="content-border"><div class="content-body">
                
EOH;
$html['body_top']['topFrame'] = <<<EOH
<body>
    <div class="display-1-columns"><div class="display-padded-top">
        <div class="window-outside">
            <div class="window-inside">
                <div class="content-border"><div class="content-body">
                
EOH;
$html['body_top']['leftFrame'] = <<<EOH
<body>
    <div class="display-padded-left">
        <div class="window-outside">
            <div class="window-inside">
                <div class="content-border"><div class="content-body">
                
EOH;
$html['body_top']['mainFrame'] = <<<EOH
<body>
    <div class="display-padded-right">
        <div class="window-outside">
            <div class="window-inside">
                <div class="content-border"><div class="content-body">
                
EOH;
$html['body_top']['bottomFrame'] = <<<EOH
<body>
    <div class="display-1-columns"><div class="display-padded-column-bottom">
        <div class="window-outside">
            <div class="window-inside">
                <div class="content-border"><div class="content-body">
                
EOH;
$html['body_bottom']['noFrame'] = <<<EOH
                </div></div>
            </div>
        </div>
    </div></div>
</body></html>
EOH;
$html['body_bottom']['topFrame'] = $html['body_bottom']['noFrame'];
$html['body_bottom']['leftFrame'] = <<<EOH
                </div></div>
            </div>
        </div>
    </div>
</body></html>
EOH;
$html['body_bottom']['mainFrame'] = $html['body_bottom']['leftFrame'];
$html['body_bottom']['bottomFrame'] = $html['body_bottom']['noFrame'];
$html['link']['help'] = '';
$html['link']['frame_on'] = '';
$html['link']['frame_off'] = '';
$html['link']['collapse'] = '';
$html['link']['expand'] = '';
$html['link']['anchor_top'] = '';
$html['link']['anchor_bottom'] = '';
$html['link']['about'] = '';
if (strpos(TOOLBAR, '-text') !== false) {
    $html['link']['frame_on'] .= 'Turn frames on';
    $html['link']['frame_off'] .= 'Turn frames off';
    $html['link']['collapse'] .= 'Collapse';
    $html['link']['expand'] .= 'Exapand';
    $html['link']['anchor_top'] .= 'GoTo Bottom';
    $html['link']['anchor_bottom'] .= 'GoTo Top';
    $html['link']['about'] .= 'About';
}
if (strpos(TOOLBAR, '-img') !== false) {
    $html['link']['frame_on'] .= '<img border="0" height="16" width="16" alt="Frames" src="' . $imgFolder . 'frames.gif" />';
    $html['link']['frame_off'] .= '<img border="0" height="16" width="16" alt="No Frames" src="' . $imgFolder . 'noframes.gif" />';
    $html['link']['collapse'] .= '<img border="0" height="16" width="16" alt="Collapse All" src="' . $imgFolder . 'collapse.gif" />';
    $html['link']['expand'] .= '<img border="0" height="16" width="16" alt="Expand All" src="' . $imgFolder . 'expand.gif" />';
    $html['link']['anchor_top'] .= '<img border="0" height="16" width="16" alt="Goto Bottom" src="' . $imgFolder . 'bottom.gif" />';
    $html['link']['anchor_bottom'] .= '<img border="0" height="16" width="16" alt="Goto Top" src="' . $imgFolder . 'top.gif" />';
    $html['link']['about'] .= '<img border="0" height="16" width="16" alt="About" src="' . $imgFolder . 'help.gif" />';
}
$html['link']['frame_on'] = '<a href="' . $file . '?action=Frame" target="_parent">' . $html['link']['frame_on'] . '</a>';
$html['link']['frame_off'] = '<a href="' . $file . '?action=noFrame" target="_parent">' . $html['link']['frame_off'] . '</a>';
$html['link']['collapse'] = '<a href="java_script_:null();" onclick="spantoggleall(true);return false;">' . $html['link']['collapse'] . '</a>';
$html['link']['expand'] = '<a href="java_script_:null();" onclick="spantoggleall(false);return false;">' . $html['link']['expand'] . '</a>';
$html['link']['anchor_top'] = '<a href="#bottom">' . $html['link']['anchor_top'] . '</a>';
$html['link']['anchor_bottom'] = '<a href="#top">' . $html['link']['anchor_bottom'] . '</a>';
$html['link']['about'] = '<a href="' . $file . '?action=about" target="_parent">' . $html['link']['about'] . '</a>';
$html['bar']['tmplate_top'] = <<<EOH
                    <div class="header">
                        <a name="top"></a>
                        <div><span>$AppTitle</span>
                
EOH;
$html['bar']['frame'] = $html['bar']['tmplate_top'] . $html['link']['about'] . ' | ' . $html['link']['frame_off'] . "nttt</div></div>";
$html['bar']['top'] = $html['bar']['tmplate_top'] . $html['link']['about'] . ' | ';
$html['bar']['about_top'] = $html['bar']['tmplate_top'];
if (ALLOWFRAMES == true) {
    $html['bar']['top'] .= $html['link']['frame_on'] . ' | ';
    $html['bar']['about_top'] .= $html['link']['frame_on'] . ' | ';
}
$html['bar']['top'] .= $html['link']['collapse'] . ' | ' . $html['link']['expand'] . ' | ' . $html['link']['anchor_top'] ."nttt</div>n</div>";
$html['bar']['about_top'] .= $html['link']['frame_off'] . ' | ' . $html['link']['anchor_top'] ."nttt</div>n</div>";
$html['bar']['tmplate_bottom'] = <<<EOH
                    <a name="bottom"></a>
                    <div class="header">
                        <div>
                        
EOH;
$html['bar']['bottom'] = $html['bar']['tmplate_bottom'];
if (SHOWPARSETIME == true) $html['bar']['bottom'] .= "tt<span>Parse time: {parse_time}s</span>n";
$html['bar']['leftFrame'] = $html['bar']['bottom'] . $html['link']['collapse'] . ' | ' . $html['link']['expand'] . "</div>n</div>n</div>";
$html['bar']['about_bottom'] = $html['bar']['bottom'] . $html['link']['anchor_bottom'] . "</div>n</div>n</div>";
$html['bar']['bottom'] .= $html['link']['collapse'] . ' | ' . $html['link']['expand'] . ' | ' . $html['link']['anchor_bottom'] . "</div>n</div>n</div>";
$html['frame_top'] = <<<EOH
        <frameset rows="45,*">
            <frame src="$file?action=topFrame" name="topFrame" scrolling="no" noresize/>
            <frameset cols="250,*">
                <frame src="$file?action=leftFrame" name="leftFrame" scrolling="auto" />
                <frame src="$file?action=mainFrame" name="mainFrame" />
            </frameset>
            <noframes>
                <body>
        
EOH;
$html['frame_top'] .= $html['link']['frame_off'] . "n";
$html['frame_bottom'] = "tt</body></noframes>ntt</frameset>n</html>";
$html['form'] = <<<EOH
                    <div class="form">
                    <form name="logon" method="post" action="phpOBrowser.php">
                    <p><b>Please login with user name and password</b></p>
                    <div class="row">
                        <span class="label">Username:</span>
                        <span class="formw"><input name="username" type="text" id="username" maxlength="15" /></span>
                    </div>
                    <div class="row">
                        <span class="label">Password:</span>
                        <span class="formw"><input name="password" type="password" id="password" maxlength="15" /></span>
                    </div>
                    <div class="row">
                        <span class="formw"><input type="submit" name="Submit" value="Submit" /></span>
                    </div>
                    <span>&nbsp;</span>
                    </form>
                    </div>
                    
EOH;
$html['about'] = <<<EOH
<div class="display-padded">
<a name="toc1"></a><h2>Table of contents</h2>
<ol>
    <li><a href="#toc1">Table of contents</a></li>
    <li><a href="#toc2">About</a></li>
    <li><a href="#toc3">phpOBrowser Features</a></li>
    <li><a href="#toc4">Using phpOBrowser</a>
        <ol>
            <li><a href="#toc4a">phpOBrowser Installation</a></li>
            <li><a href="#toc4b">phpOBrowser Setup</a></li>
            <li><a href="#toc4c">phpOBrowser Options</a></li>
            <li><a href="#toc4d">phpOBrowser Usage</a></li>
        </ol>
    </li>
    <li><a href="#toc5">Element Definitions</a></li>
    <li><a href="#toc6">Element Structure</a></li>
    <li><a href="#toc7">Icons Used in phpOBrowser</a></li>
    <li><a href="#toc8">phpOBrowser Performance</a></li>
    <li><a href="#toc9">phpOBrowser Limitations</a></li>
    <li><a href="#toc9a">phpOBrowser Notes</a></li>
    <li><a href="#toc9b">Rate phpOBrowser</a></li>
    <li><a href="#toc10">Copyright/Licensing Information</a></li>
    <li><a href="#toc11">phpOBrowser Objects</a></li>
</ol>
<a name="toc2"></a><h2>About</h2>
<ul>

EOH;
$html['about'] .= "t<li>File: $file</li>n" .
    "t<li>Version: <span class="about_r">" . VERSION . "</span></li>n" .
    "t<li>Created: <span class="about_r">" . CREATED . "</span></li>n" .
    "t<li>Updated: <span class="about_r">" . UPDATED . "</span></li>n";
$html['about'] .= <<<EOH
    <li>Author: <span class="about_r">Scott Greenberg</span></li>
    <li>Company: <span class="about_r">SG Technology</span></li>
    <li>Website: <span class="about_r"><a href="http://gogogadgetscott.info">http://gogogadgetscott.info</a></span></li>
    <li>Decription: <b>phpOBrowser</b> is a dynamic object browser that allows you to browse through all available 
        folders, files, sections, and objects in any php application and see classes, properties, methods and constants that
        are available.</li>
    <li>Requires:
        <ul>
            <li>PHP 4.2.0 or later</li>
            <li>File read permissions to source application files</li>
            <li>JavaScript 1.2 or later enable web-browser</li> 
        </ul>
    </li>
</ul>
<a name="toc3"></a><h2>phpOBrowser Features</h2>
<ul>
    <li>Lists folders, files, sections, classes, constants, properties, functions</li>
    <li>Built-in security option with username, password and php sessions</li>
    <li>Displays DocBlocks as tooltips for individual objects</li>
    <li>Uses reentry to searches application source folders and files, including subfolders</li>
    <li>Uses advanced Regular Expressions to produce very fast code</li>
    <li>Select valid source file extensions i.e. php and/or inc</li>
    <li>Frames or no frames option at runtime</li>
    <li>Collapse/Expanded all nodes at runtime</li>
    <li>Copies selected object to clipboard<a href="#note1">*&sup1;</a></li>
    <li>Customizable toolbars</li>
    <li>Customizable treeview</li>
    <li>Clean user interface</li>
    <li>Produces valid XHMTL 1.0 output</li>
    <li>Allows for separate image folder</li>
</ul>
<p class="notes"><a name="note1"></a>*&sup1;Requires a browser using Microsoft's Internet Explorer engine. i.e. IE, MyIE2, Avant, etc..</p>
<a name="toc4"></a><h2>Using phpOBrowser</h2>
<a name="toc4a"></a><h3>Script Installation</h3>
<p>Unzip the archive in a directory and configure web serve to serve it. The object browser does not need a 
live web server to operate, a localhost setup will do. There is no need to set any file permissions.<br />
- register_globals can either be on or off, at no comprise to the security or functionality of phpOBrowser.</p>
<a name="toc4b"></a><h3>Script Setup</h3>
<p>Open phpOBrowser.php in a text/php editor.  Scroll to the Script Setup section.  Edit the setup values 
to reflect your php application.  These setting include the title, source folder, images folder and the list 
of valid php file extensions.  The default settings will list all objects used in phpOBrowser.</p>
<a name="toc4c"></a><h3>Script Options</h3>
<p>Open phpOBrowser.php in a text/php editor.  Scroll to the Script Options section.  Edit the setup values 
to reflect your desired settings.  Options are explained in comments above setting.  These setting include 
turning security on.  By default, security and frames are turned off.</p>
<a name="toc4d"></a><h3>phpOBrowser Usage</h3>
<p>Using your web-browser open the phpOBrowser.php file, being sure to request it through the web server. 
i.e. localhost/phpOBrowser/phpOBrowser.php There are two interfaces, with and without frames.  Both have the 
same functionally, though they have different layouts.  In either setup, the icon to the left of the element<a href="#note2">*&sup2;</a> 
will symbolize the elements type.  An icon key maybe found <a href="#toc5"> below</a>.  A plus to the left of the icon 
element will indicate it can be expanded to expose more elements listed within.  A minus will indicate it can be 
collapsed to hide the child elements.  Click on the plus/minus or the icon to the right will invoke the appropriate 
collapse/expand action.  If nither a plus or minus is present, then that element has no children.  In addition, 
clicking on the icon of a element without children will copy the elements text to the clipboard.<a href="#note1">*&sup1;</a></p>
<p>Both layout contain a top toolbar.  In no frame mode you will find the links to display about information, 
switch to framed layout, collapse all, expand all and move to bottom of list.  In frame mode you will find the 
links to display help and switch to no frame layout.  Both layouts have a bottom toolbar where you will find the 
parse time to create the object browser list and the links to collapse all, expand all. Only the no frame bottom 
toolbar has a move to top of list link.</p>
<p>phpOBrowser is a dynamic object browser.  By refreshing the web page all folder, files, and object will update to 
reflect any changes made to you php application.  Using the find feature of your browser and element can be found.  
Only those element in visible display, this includes any elements that may be hidden by the screen size restrictions 
with be search.  This allows you to limit your search to a folder, file, section and/or class.  By using the expand 
all feature from the toolbar the entire php application can be searched.</p>
<a name="note2"></a>*&sup2; element is used to describe php application folders, files, sections and/or objects.
<a name="toc5"></a><h3>Element Definitions</h3>
<dl>
    <dt>Folder</dt>
    <dd>A self-contained collection of varies kinds of files, including source code, images, documentation, etc. 
    Used to organize files that makeup a php application.  An application can consist of one or more folders or even 
    share a folder with other applications. </dd>
    <dt>File</dt>
    <dd>A basic unit of code storage for php applications. An application may consist of one or more files.</dd>
    <dt>Section</dt>
    <dd>A self-contained collection of objects within a file.  Used to aid the programming when coding the script 
    and helps produce script documentation.</dd>
    <dt>Class</dt>
    <dd>A collection of properties and functions that acts as a template from which an instance of an 
    object is created at run time. The class defines the properties of the object and the functions used to control 
    the object's behavior.</dd>
    <dt>Property</dt>
    <dd>A symbol into which data can be stored. It is used to describe the class in with is belongs. The value, or contents, 
    of a property can change during script execution. Property are defined by preceding <i>var</i>. Properties are 
    represented by a dollar sign followed by the name and are case-sensitive.</dd>
    <dt>Function</dt>
    <dd>A subroutine called from within an expression in which a value is computed and returned to the script that 
    called it through its name.  Any number of arguments, including no arguments may be passed to it either by value 
    or by reference.</dd>
    <dt>Constant</dt>
    <dd>An identifier (name) for a simple value. As the name suggests, that value cannot change during the execution 
    of the script.</dd>
</dl>
<a name="toc6"></a><h3>Element Structure</h3>
<p>Sections are defined with the follow structure:</p>
<pre>
    // ==
    // Section name
    // ==

or

    // --
    // Section name
    // --

or

    // {{{ Section name
    // }}}

</pre>
<p>Where the sepration symbol [-/=] can be repeated one or more times</p>
<p>All other element require valid php structure as defined by <a href="http://www.php.net/copyright.php">The PHP Group</a>.
For more information refer to the php <a href="http://www.php.net/manual/en/langref.php">language reference</a> manual.</p>
<a name="toc7"></a><h2>Icons Used in phpOBrowser</h2>
<p>The <b>phpOBrowser</b> contain many icons that represent classes and members.
        The following is a list of icons and what they represent.</p>
<table>
    <tr valign="top">
        <th width="30%">This Icon:</th>
        <th width="30%">Represents a:</th>
    </tr>
EOH;
$icons[0] = array($imgFolder . 'folderclosed.gif','Folder');
$icons[1] = array($imgFolder . 'file_php.gif','File');
$icons[2] = array($imgFolder . 'section.gif','Section');
$icons[3] = array($imgFolder . 'class.gif','Class');
$icons[4] = array($imgFolder . 'property.gif','Property');
$icons[5] = array($imgFolder . 'function.gif','Function');
$icons[6] = array($imgFolder . 'constant.gif','Constant');
for ($i = 0; $i <= count($icons) - 1; $i++) {
    $html['about'] .= '<tr valign="top"><td width="30%"><img src="' . $icons[$i][0] . '" alt="' . $icons[$i][1] . '" /></td>' .
        '<td width="30%">' . $icons[$i][1] . "</td></tr>n";
}
$html['about'] .= <<<EOH
    </table>
<a name="toc8"></a><h2>phpOBrowser Performance<a href="#note3">*&sup3;</a></h2>
    <p>Lists objects from 23 php applications, 4,166 files, 251 Folders on a single html page</p>
    <table>
        <tr>
            <td width="20%">&nbsp;</td>
            <th width="25%">Parse time (s)</th>
            <th width="25%">Total load time (s)</th>
        </tr>
        <tr>
            <td><div align="right">with frames:</div></td>
            <td><div align="center">1.338</div></td>
            <td><div align="center">4.187</div></td>
        </tr>
        <tr>
            <td><div align="right">without frames:</div></td>
            <td><div align="center">17.979</div></td>
            <td><div align="center">33.187</div></td>
        </tr>
    </table>
    <p class="notes"><a name="note3"></a>*&sup3;Times are an average preformed using:</p>
    <ul class="notes">
        <li>PHP Version 4.36</li>
        <li>IIS Version 5.1</li>
        <li>Opera Version 7.5</li>
        <li>MyIE Version 0.9.26</li>
        <li>Windows XP Pro</li>
        <li>AMD Athlon XP 2200+</li>
        <li>1.00 GB of RAM</li>
    </ul>
<a name="toc9"></a><h2>phpOBrowser Limitations</h2>
<ol>
    <li>
        Any object listed after a class and not part of a second class will be include as part of the class.<br />
        This is only an issue, if any single file has any objects after a first class that are not part of any subsequent class. 
        In the future a method maybe implemented to determine the end of classes, this is not used currently.
    </li>
</ol>
<a name="toc9a"></a><h2>phpOBrowser Notes</h2>
<p class="notes">
    $notes
</p>
<a name="toc9b"></a><h2>Rate phpOBrowser</h2>
<p class="notes">
<form action="http://www.hotscripts.com/cgi-bin/rate.cgi" method="POST">
If you like phpOBrowser, please rate it at <a href="http://www.hotscripts.com/">Hotscripts.com</a>!<br />
<input type="hidden" name="ID" value="34974">
<input type="hidden" name="external" value="1">
<select name="rate" size="1">
    <option value="5" selected>Excellent!</option>
    <option value="4">Very Good</option>
    <option value="3">Good</option>
    <option value="2">Fair</option>
    <option value="1">Poor</option>
</select>
<input name="submit" type="submit" value="Cast My Vote!">
</form>
</p>
<a name="toc10"></a><h2>Copyright/Licensing Information</h2>
<p>Copyright 2004. <a href="http://gogogadgetscott.info">SG Technology</a>. All rights reserved.</p>

EOH;
$html['about'] .= '<p><a href="' . $file . '?action=copying#info">Click here</a> for copyright information.</p>' . "n" .
    '<p><a href="' . $file . '?action=license#info">Click here</a> for licensing information.</p>' . "n" .
    '</div>';
$js['head'] = <<<EOJ
    <script type="text/javascript" language="javascript1.2"><!--
        function copy_clip(text)
        {
            if (window.clipboardData) {window.clipboardData.setData("Text", text);}
            return false;
        }
        function spantoggle(node_info, state)
        {
            var node_id = node_info;
            node_info = node_info.split("-");
            node_img = node_info[0];
            var this_type = typeList.type[node_info[1]];
            var theSpan = document.getElementById ? document.getElementById(node_id) : document.all ? document.all[node_id] : (new Object());
            if(!theSpan) {return;}
            if(theSpan.style) {theSpan = theSpan.style;}
            if (theSpa.display == 'none') {
                var collapse = false;
            } else {
                var collapse = true;
            }
            if (state != null) collapse = state;
            if (collapse == false) {
                theSpan.display = 'inline';
                document.images[node_img + '_icon'].src = this_type.mImg;
                document.images[node_img + '_branch'].src = this_type.bmImg;
            } else {
                theSpan.display = 'none';
                document.images[node_img + '_icon'].src = this_type.pImg;
                document.images[node_img + '_branch'].src = this_type.bpImg;
            }
        }
        function spantoggleall(value)
        {
            var oSpan = document.getElementsByTagName('span');
            var node_id = '';
            var this_type = '';
            for (var i = 0; i < oSpan.length; i++) {
                node_info = oSpan[i].id.split("-");
                if (node_info[1] != null) spantoggle(oSpan[i].id, value);
            }
        }
        function typeList() {this.type = [];}
        function type()
        {
            this.pImg = arguments[0];
            this.mImg = arguments[1];
            this.bpImg = arguments[2];
            this.bmImg = arguments[3];
        }
        var typeList = new typeList();

EOJ;
$js['bottom'] = <<<EOJ
    //--></script>
EOJ;

// -------------------------------------------------
// Security
// -------------------------------------------------

if (SECURE == true) {
    session_name('phpOBrowser');
    session_start();
    if (isset($username) && isset($password)) {
        if ($username == USER && $password == PASSWORD) {
            $_SESSION['ID'] = 'ok';
        }
    }
    if (!isset($_SESSION['ID'])) {
        echo $html['head_top'] . $html['head_bottom'] . $html['body_top']['noFrame'] . $html['form'] . $html['body_bottom']['noFrame'];
        exit;
    }
}

// -------------------------------------------------
// Start parsing of sorce files
// -------------------------------------------------

/*
 * Individual files or folder may be specified
 */
if ($action == 'Frame') {
} elseif ($action == 'leftFrame' || $action == 'noFrame' || $action == 'about') {
    parse_folder($AppFolder, $nodes);
} elseif ($action == 'mainFrame') {
    parse_file($path, $nodes);
}
if ($action == 'leftFrame' || $action == 'noFrame' || $action == 'mainFrame' || $action == 'about') {
    /*
     * Convert nodes array to HTML
     */
    nodes2html($nodes);
}

/*
 * Send HTML heading.
 */
if ($action == 'Frame') {
    echo $html['type_frame'] . $html['head_top'] . $html['head_bottom'] . $html['frame_top'];
} elseif  ($action == 'topFrame') {
    echo $html['type'] . $html['head_top'] . $html['head_bottom'] . $html['body_top'][$action] . $html['bar']['frame'];
} elseif ($action == 'leftFrame' || $action == 'mainFrame' || $action == 'noFrame' || $action == 'about' || $action == 'copying' || $action == 'license') {
    echo $html['type'] . $html['head_top'] . $js['head'];
    /*
     * Print image data for javascript.
     */
    foreach ($img_types as $k => $v) {
        echo "tttypeList.type[$k] = new type($v);n";
    }
    echo $js['bottom'] . $html['head_bottom'];
}
if ($action == 'leftFrame' || $action == 'mainFrame') {
    echo $html['body_top'][$action];
} elseif ($action == 'noFrame') {
    echo $html['body_top']['topFrame'] . $html['bar']['top'];
} elseif ($action == 'about' || $action == 'copying' || $action == 'license') {
    echo $html['body_top']['topFrame'] . $html['bar']['about_top'] . $html['about'];
}

/*
 * Print copying or license infomation by request.
 */
if ($action == 'copying' || $action == 'license') {
    if ($action == 'copying') {
        $file = 'COPYING';
    } else {
        $file = 'LICENSE';
    }
    if (is_file($file) && filesize($file) > 0) {
        $handle = fopen($file, 'r');
        $sFile = fread($handle, filesize($file));
        fclose($handle);
        echo '<div class="display-padded"><a name="info"></a>' . nl2br($sFile) . '</div>';
    }
}

/* 
 * Calculate parse time.
 */
$time_start = explode(' ', PAGE_PARSE_START_TIME);
$time_end = explode(' ', microtime());
$parse_time = number_format(($time_end[1] + $time_end[0] - ($time_start[1] + $time_start[0])), 3);

/* 
 * Print any error message.
 */
if (!empty($error_msg)) {
    echo $error_msg;
}
    
/* 
 * Send html footer.
 */
if ($action == 'Frame') {
    echo $html['frame_bottom'];
} elseif  ($action == 'topFrame') {
    echo $html['body_bottom'][$action];
} elseif ($action == 'leftFrame') {
    echo $html['nodes'] . preg_replace ("/{parse_time}/", $parse_time, $html['bar']['leftFrame']) . $html['body_bottom'][$action];
} elseif ($action == 'mainFrame') {
    echo $html['nodes'] . $html['body_bottom'][$action];
} elseif ($action == 'noFrame') {
    echo $html['nodes'] . preg_replace ("/{parse_time}/", $parse_time, $html['bar']['bottom']) . $html['body_bottom'][$action];
} elseif ($action == 'about' || $action == 'copying' || $action == 'license') {
    echo '<a name="toc11"></a><h2>phpOBrowser Objects</h2>' . 
        $html['nodes'] . preg_replace ("/{parse_time}/", $parse_time, $html['bar']['about_bottom']) . $html['body_bottom']['noFrame'];
}

// -------------------------------------------------
// functions
// -------------------------------------------------

/**
 * Search a folder for valid php files with reentry.
 * 
 * @param string Path to folder.
 * @param array Collection of nodes that make up objects.
 */
function parse_folder($path, &$nodes)
{
    global $node, $error_msg;
    if (is_dir($path))  {
        $handle = @opendir($path);
        if ($handle !== false) {
            while (($filename = readdir($handle)) !== false) { // Iterate over file list.
                if (($filename != '.') && ($filename != '..')) {
                    $fullpath = $path . '/' . $filename;
                    if (is_dir($fullpath)) { // Found a folder
                        $node_key = count($nodes);
                        $nodes[$node_key]['text'] = $filename;
                        $nodes[$node_key]['type'] = 'folder';
                        $nodes[$node_key]['key'] = create_node();
                        $node = 1;
                        parse_folder($fullpath, $nodes[$node_key]['sub']);
                        $node--;
                    }
                    if (is_file($fullpath)) { // Found a file
                        parse_file($path . '/' . $filename, $nodes);
                    }
                }
            }
        /*
         * Close directory.
         */
        closedir($handle);
      }
    } elseif (is_file($path)) { // File is passed
        parse_file($path, $nodes);
    } else {
        $error_msg = '<div class="error">Application path is invalid.</div>';
    }
}

/**
 * Read in file, check that it is a valid php file
 * and start parsing objects by extraction text.
 * 
 * @param string Path to folder.
 * @param array Collection of nodes that make up objects.
 */
function parse_file($path, &$nodes)
{
    global $action, $node, $phpexten;
    /*
     * Check if path is a valid file
     */
    if (is_file($path)) {
        /* 
         * Check file has a valid php extention
         */
        if (in_array(substr(strrchr($path, '.'), 1), $phpexten)) {
            if ($action != 'mainFrame') {
                $node_key = count($nodes);
                $nodes[$node_key]['text'] = basename($path);
                $nodes[$node_key]['type'] = 'file';
                $nodes[$node_key]['path'] = $path;
                $nodes[$node_key]['key'] = create_node();
            }
            if (filesize($path) > 0 && ($action == 'mainFrame' || $action == 'noFrame' || $action == 'about')) {
                $handle = fopen($path, 'r');
                $sFile = fread($handle, filesize($path));
                fclose($handle);
                /* 
                 * Remove multi-line comments.
                 * No loger user, as multi-line comments are pasred to extract
                 * DocBlocks.
                 */
                /*
                $pattern =  "//*";        // Opening comment section tag
                $pattern .= "([S|s]*?)";  // Comments
                $pattern .= "*//";        // Closing comment section tag
                $sFile = preg_replace($pattern, '', $sFile);
                */
                /* 
                 * Remove Heredoc style strings.
                 */
                $pattern =  "/<{3,3}";      // Opening tag of Heredoc string
                $pattern .= "(w+)/";       // Identifier
                preg_match_all($pattern, $sFile, $matches, PREG_SET_ORDER);
                /* 
                 * Iterates Heredoc string opening tag.
                 */
                foreach ($matches as $match) {
                    $pattern =  "/<{3,3}";          // Opening tag of Heredoc string
                    $pattern .= $match[1];          // Identifier
                    $pattern .= "[S|s]*?";        // String
                    $pattern .= $match[1] . ";/";   // end of line
                    $sFile = preg_replace($pattern, '', $sFile);
                }
                if ($action != 'mainFrame') {
                    $node = 1;
                    find_classes($sFile, $nodes[$node_key]['sub']);
                    $node--;
                } else {
                    find_classes($sFile, $nodes);
                }
            } else {
                $node = 0;
            }
        }
    }
}

/**
 * Search file for a class. Then send class to find sections.
 * 
 * @param string Text extracted from file.
 * @param array Collection of nodes that make up objects.
 * @return void
 */
function find_classes($haystack, &$nodes)
{
    global $node;
    $pattern =  "/";            // Start of pattern
    $pattern .= "(?>/**";    // Opening of DocBlock
    $pattern .= "([S|s]*?)";  // DocBlock
    $pattern .= "*/)?";       // Closing of DocBlock
    $pattern .=  "s+class";    // First portion of function statement
    $pattern .= " +(.+)s*{/i";// class name and start of class, allows for extended classes
    $matches = array();
    preg_match_all($pattern, $haystack, $matches, PREG_SET_ORDER);
    if (count($matches) > 0 && PARSECLASSES == true) {
        $sSection = substr($haystack, 0, strpos($haystack, $matches[0][0]));
        find_sections($sSection, $nodes); // Search for objects before classes
        sort_matches($matches);
        for ($i = 0; $i <= count($matches) - 1; $i++) {
            $sSection = substr($haystack, strlen($haystack) - (strlen($haystack) - strpos($haystack, $matches[$i][0]) - strlen($matches[$i][0])));
            $node_key = count($nodes);
            $nodes[$node_key]['type'] = 'class';
            $nodes[$node_key]['key'] = create_node();
            $nodes[$node_key]['info'] = $matches[$i][1];
            $nodes[$node_key]['text'] = $matches[$i][2];
            $node = 1;
            find_sections($sSection, $nodes[$node_key]['sub']); // Search for objects after class defintion
            $node--;
        }
        /*
         * Need to create method to find end of class
         * - find all close brackets, loop, if number of open = close then end of class
         * - use odd even count
         */
        /*
        for ($i = 0; $i <= count($matches) - 1; $i++) {
        $sSection = substr($haystack, strlen($haystack) - (strlen($haystack) - strpos($haystack, $matches[$i][0]) - strlen($matches[$i][0])));
        preg_match_all("/{/", $sSection, $brackets[0], PREG_SET_ORDER);
        preg_match_all("/}/", $sSection, $brackets[1], PREG_SET_ORDER);
        $brac_open = count($brackets[0]);
        $brac_close = count($brackets[1]);
        strpos();
            $tmp .= $matches[$i][1];
        } 
        */
    } else {
        find_sections($haystack, $nodes);
    }
}

/**
 * Search string for a sections or folds. 
 * Then send class to find objects.
 * 
 * @param string Text extracted from file.
 * @param array Collection of nodes that make up objects.
 * @return void
 */
function find_sections($haystack, &$nodes)
{
    global $node;
    $matches = array();
    $tmp_matches = array();
    /* 
     * Section format 1 and 2.
     */
    $pattern =  "/// *[=|-]+";        // First line of section
    $pattern .= "s*//s*";           // Opening of section name
    $pattern .= "([w* |/]*)s*";      // Section name
    $pattern .= "// *[=|-]+/";        // Last line of section
    preg_match_all($pattern, $haystack, $matches, PREG_SET_ORDER);
    /* 
     * Section format 3.
     */
    $pattern =  "/// *{{3,3} *";     // Opening section
    $pattern .= "([w* ]*)/";           // Section name
    preg_match_all($pattern, $haystack, $tmp_matches, PREG_SET_ORDER);
    $matches = array_merge($matches, $tmp_matches);
    if (count($matches) > 0) {
        $sSection = substr($haystack, 0, strpos($haystack, $matches[0][0]));
        find_objects($sSection, $nodes); // Search for object before any Sections
        sort_matches($matches);
        for ($i = 0; $i <= count($matches) - 1; $i++) {
            $node_key = count($nodes);
            $nodes[$node_key]['type'] = 'section';
            $nodes[$node_key]['key'] = create_node();
            $nodes[$node_key]['text'] = $matches[$i][1];
            $node = 1;
            $sSection = substr($haystack, strlen($haystack) - (strlen($haystack) - strpos($haystack, $matches[$i][0]) - strlen($matches[$i][0])));
            if ($i < (count($matches) - 1)) {
                $j = $i + 1;
                $iOffset = strpos($haystack, $matches[$j][0]) - strpos($haystack, $matches[$i][0]) - strlen($matches[$i][0]);
            } else {
                $iOffset = strlen($sSection);
            }
            $sSection = substr($sSection, 0, $iOffset);
            find_objects($sSection, $nodes[$node_key]['sub']); // Search for objects within Section
            $node--;
        }   
    } else {
        find_objects($haystack, $nodes); // No sections, Search for objects
  }
}

/**
 * Prepare section of code to search for objects.
 * 
 * Removes comments from section of code. Sends section of code to
 * parse out all object in separate functions.
 * 
 * @param string Text extracted from file.
 * @param array Collection of nodes that make up objects.
 * @return void
 */
function find_objects($haystack, &$nodes)
{
    /* 
     * Remove single line comments.
     */
    $pattern =  "//{2,}";              // Commented line
    $pattern .= "[S|s]*?";            // Comment
    $pattern .= "[n|;]/";             // end of line or block
    $haystack = preg_replace($pattern, '', $haystack);
    /* 
     * Remove single line shell-style style comments.
     */
    $pattern =  "/#";                   // Commented line
    $pattern .= "[S|s]*?";            // Comment
    $pattern .= "[n|;]/";             // end of line
    $haystack = preg_replace($pattern, '', $haystack);
    /* 
     * Serach for objects.
     */
    find_constants($haystack, $nodes);
    find_properties($haystack, $nodes);
    find_functions($haystack, $nodes);
}
/**
 * Search section of code for constants.
 * 
 * @param string Text extracted from file.
 * @param array Collection of nodes that make up objects.
 * @return void
 */
function find_constants($haystack, &$nodes)
{
    global $node, $bGrouptypes;
    $pattern =  "/";                // Start of pattern
    $pattern .= "(?>/**";        // Opening of DocBlock
    $pattern .= "([S|s]*?)";      // DocBlock
    $pattern .= "*/)?";           // Closing of DocBlock
    $pattern .= "s+define(['|"]";   // First portion of define statement
    $pattern .= "([w|s]*)";       // Constant name
    $pattern .= "['|"|s],";       // Seperator
    $pattern .= "([w|s|.|,|'|"|$|=||/|(||)]*)"; // Constant Value
    $pattern .= ")s*;/i";         // Closeing
    $matches = array();
    preg_match_all($pattern, $haystack, $matches, PREG_SET_ORDER);
    if (count($matches) > 0) {
        sort_matches($matches);
        foreach ($matches as $match) { // Iterate matches collection
            $node_key = count($nodes);
            $nodes[$node_key]['info'] = $match[1];
            $nodes[$node_key]['type'] = 'constant';
            $nodes[$node_key]['key'] = create_node();
            $nodes[$node_key]['text'] = $match[2] . ' =>' . $match[3];
            $node = 0;
        }
    } 
}

/**
 * Search section of code for properties.
 * 
 * @param string Text extracted from file.
 * @param array Collection of nodes that make up objects.
 * @return void
 */
function find_properties($haystack, &$nodes)
{
    global $node, $bGrouptypes;
    $pattern =  "/";                // Start of pattern
    $pattern .= "(?>/**";        // Opening of DocBlock
    $pattern .= "([S|s]*?)";      // DocBlock
    $pattern .= "*/)?";           // Closing of DocBlock
    $pattern .= "s+vars+";        // First portion of defineed variable
    $pattern .= "([w|s|.|'|"|$|=||/|(|)|,]*;)/i"; // Variable name, value, and closing
    $matches = array();
    preg_match_all($pattern, $haystack, $matches, PREG_SET_ORDER);
    if (count($matches) > 0) {
        sort_matches($matches);
        foreach ($matches as $match) { // Iterate matches collection
            $node_key = count($nodes);
            $nodes[$node_key]['type'] = 'property';
            $nodes[$node_key]['key'] = create_node();
            $nodes[$node_key]['info'] = $match[1];
            $nodes[$node_key]['text'] = $match[2];            
            $node = 0;
        }
    }
}

/**
 * Search section of code for functions.
 * 
 * @param string Text extracted from file.
 * @param array Collection of nodes that make up objects.
 * @return void
 */
function find_functions($haystack, &$nodes)
{
    global $node, $bGrouptypes;
    /* 
     * Find functions
     */
    $pattern =  "/";                // Start of pattern.
    $pattern .= "(?>/**";        // Opening of DocBlock.
    $pattern .= "([S|s]*?)";      // DocBlock.
    $pattern .= "*/)?";           // Closing of DocBlock.
    $pattern .= "s+function";      // Start of function deceleration.
    $pattern .= " +(&?w* *()";    // Function name and start of aguments.
    $pattern .= "([s|S]*?))";    // Aguments and close of arguments.
    $pattern .= "s*{";             // End of function deceleration.
    $pattern .= "/i";               // End of pattern.
    $matches = array();
    preg_match_all($pattern, $haystack, $matches, PREG_SET_ORDER);
    if (count($matches) > 0) {
        sort_matches($matches);
        foreach ($matches as $match) { // Iterate matches collection
            $node_key = count($nodes);
            $nodes[$node_key]['type'] = 'function';
            $nodes[$node_key]['key'] = create_node();
            $nodes[$node_key]['info'] = $match[1];
            $nodes[$node_key]['text'] = $match[2] . $match[3] . ';';
            $node = 0;
        }
    } 
}

/**
 * Sorts an array of objects.
 * 
 * @param array Collection of objects extraced from code.
 * @return void
 */
function sort_matches(&$matches)
{
    global $sort;
    switch ($sort) {
    case 'aAsc':
        $r = asort($matches, SORT_REGULAR);
        break;
    case 'aDes':
        $r = arsort($matches, SORT_REGULAR);
        break;
    case '1Asc':
        $r = asort($matches, SORT_NUMERIC);
        break;
    case '1Des';
        $r = arsort($matches, SORT_NUMERIC);
        break;
    default:
        $r = true;
    }
    reset($matches);
    return $r;
}

/**
 * Format a single line to remove and whiespaces.
 * 
 * @param string Line of text.
 * @return string
 */
function formatline_php($line)
{
    $remove_chr = array("n","r","t");
    foreach ($remove_chr as $chr) {
        $line = str_replace($chr, '', $line);
    }
    $line = str_replace('&', '&amp;', $line);
    // $line = addslashes($line);
    return trim($line);
}

/**
 * Format a single line of JavaScript sorce code.
 * 
 * @param string Line of text.
 * @return string Formated sorce code for JavaScript.
 */
function formatline_js($line)
{
    $line = str_replace(';', ';', $line);
    $line = str_replace("'", "'", $line);
    $line = str_replace("'", "'", $line);
    $line = str_replace('"', '&quot;', $line);
    $line = str_replace('&', '&amp;', $line);
    return trim($line);
}

/**
 * Determines if node is a parent, sibling or child and
 * assigns a key.
 * 
 * @return string Key of node.
 */
function create_node()
{
    global $subs, $node;
    /* 
     * Check if a new next node should be created.
     */
    if ($node < 0) {
        for ($i = 1; $i <= abs($node); $i++) {
            unset($subs[count($subs) - 1]);
        }
        $subs[count($subs) - 1]++;
    } elseif ($node > 0) {
        for ($i = 1; $i == $node; $i++) {
            $subs[count($subs)] = 0;
        }
        $node = 0;
    } else {
        $subs[count($subs) - 1]++;
    }
    /* 
     * Create node key.
     */
    $sNode = '';
    for ($i = 0; $i < count($subs); $i++) {
        $sNode .= '_' . $subs[$i];
    }
    return $sNode;
}

/**
 * Convert collection of nodes to html treeview
 *
 * @param array Collection of nodes that make up objects.
 * @param string Nodes key.
 * @param bool Indicates if currrent nodes to collapsed.
 */
function nodes2html($nodes, $currentKey = '_', $collapsed = false)
{
    global $file, $html, $action, $imgFolder;
    if ($collapsed == true) {
        $display = 'none';
    } else {
        $display = '';  
    }
    /* 
     * Branch open tag.
     */
    $html['nodes'] .= '<span style="display:' . $display . ';" id="' . $currentKey . '">';
    foreach ($nodes as $node) {
        $img['icon']['open'] = $imgFolder;
        $img['icon']['closed'] = $imgFolder;
        switch ($node['type']) {
        case 'folder':
            $img['icon']['open'] .= 'folderopen.gif';
            $img['icon']['closed'] .= 'folderclosed.gif';
            $collapsed = true;
            break;
        case 'file':
            $img['icon']['closed'] .= 'file_php.gif';
            $collapsed = true;
            break;
        case 'class':
            $img['icon']['closed'] .= 'class.gif';
            $collapsed = true;
            break;
        case 'section':
            $img['icon']['closed'] .= 'section.gif';
            $collapsed = true;
            break;
        case 'property':
            $img['icon']['closed'] .= 'property.gif';
            break;
        case 'function':
            $img['icon']['closed'] .= 'function.gif';
            break;
        case 'constant':
            $img['icon']['closed'] .= 'constant.gif';
            break;
        }
        if ($img['icon']['open'] == $imgFolder) {
            $img['icon']['open'] = $img['icon']['closed'];
        }
        /* 
         * A count of how deep node is.
         */
        $node_keys = explode('_', $node['key']);
        // $node_nextkey = ;
        $node_depth = count($node_keys);
        if ($node['key'] !== '_0') { // line break on all except first branch
            $html['nodes'] .= "n" . '<br />';
        }
        /* 
         * Initialize values.
         */
        $line = '';
        $spacers = '';
        /* 
         * Print what may or may not remain of branch.
         */
        for ($i = 2; $i < $node_depth; $i++) {
            /* 
             * Spacer or vertical line img.
             */
            if (SHOWLINES) {
                $spacers .=  '<img src="' . $imgFolder . 'vertline.gif" align="middle" alt="|" />';
            } else {
                $spacers .=  '<img src="' . $imgFolder . 'blank.gif" align="middle" alt="&nbsp;&nbsp;&nbsp;" />';
            }
        }
        $html['nodes'] .= $spacers;
        $img['branch']['closed'] = $imgFolder;
        $img['branch']['open'] = $imgFolder;
        $img['branch']['none'] = $imgFolder;
        if (SHOWLINES) {
            if(next($nodes) === false) { // Last branch
                $img['branch']['closed'] .= 'plastbranch.gif';
                $img['branch']['open'] .= 'mlastbranch.gif';
                $img['branch']['none'] .= 'lastbranch.gif';
            } elseif ($node['key'] == '_0') { // First branch
                $img['branch']['closed'] .= 'pfirstbranch.gif';
                $img['branch']['open'] .= 'mfirstbranch.gif';
                $img['branch']['none'] .= 'firstbranch.gif';
            } else { // middle branch
                $img['branch']['closed'] .= 'pbranch.gif';
                $img['branch']['open'] .= 'mbranch.gif';
                $img['branch']['none'] .= 'branch.gif';
            }
        } else {
            $img['branch']['closed'] .= 'pbranch_nl.gif';
            $img['branch']['open'] .= 'mbranch_nl.gif';
            $img['branch']['none'] .= 'blank.gif';
        }
        $node['text'] = formatline_php($node['text']);
        /* 
         * Setup object infomation.
         */
        if (SHOWINFO == true && isset($node['info']) && !empty($node['info'])) {
            $node['info'] = htmlentities(trim($node['info']));
            $node['info'] = '<span><pre>' . ereg_replace('*+ *', '', $node['info']) . '</pre></span>';
            $node['text'] = '<a class="info" href="#">' . $node['text'] . $node['info'] . '</a>';
        }
        /* 
         * Print branchs.
         */
        if (!empty($node['sub'])) { // has children
            $img_type = img_node_type("'" . $img['icon']['open'] . "','" . $img['icon']['closed'] . "','" . $img['branch']['closed'] . "','" . $img['branch']['open'] . "'");
            $line .= '<a href="#" onclick="spantoggle(' . "'node$node[key]-$img_type'" . ');return false;">';
            if ($collapsed) { // with children, collapsed
                $line .= '<img src="' . $img['branch']['closed'] . '" name="node' . $node['key'] . '_branch" align="middle" alt="`-[+/-]" />' .
                    '<img src="' . $img['icon']['closed'] . '" name="node' . $node['key'] . '_icon" align="middle" alt="---" /></a> ';
            } else { // last branch, has children, expanded
                $line .= '<img src="' . $img['branch']['open'] . '" name="node' . $node['key'] . '_branch" align="middle" alt="`-[+/-]" />' .
                    '<img src="' . $img['icon']['open'] . '" name="node' . $node['key'] . '_icon" align="middle" alt="---" /></a> ';
            }
            $line .= $node['text'];
            $html['nodes'] .= $line;
            nodes2html($node['sub'], "node$node[key]-$img_type", $collapsed);
        } else { // no children
            $line .= '<img src="' . $img['branch']['none'] . '" align="middle" alt=",-" />' .
                '<a href="#" onclick="javascript:copy_clip(' . "'" . formatline_js($node['text']) . "'" . ');return false;">' .
                '<img src="' . $img['icon']['closed'] . '" align="middle" alt="---" /></a> ';
            if ($action == 'leftFrame' && isset($node['path'])) {
                $line .= '<a href="' . $file . '?action=mainFrame&amp;path=' . $node['path'] . '" target="mainFrame">' . $node['text'] . '</a>';
            } else {
                $line .= $node['text'];
            }
            $html['nodes'] .= $line;
        }
    }
    /* 
     * Branch close tag.
     */
    $html['nodes'] .= '</span>';
}

/**
 * Retrieves image collection id.
 * 
 * Checks collection of images for current node image.
 * If image is not found, a new entry is created with an id.
 * 
 * @param string
 * @return int Id of image in collection.
 */
function img_node_type($value)
{
    global $img_types;
    $id = array_search($value, $img_types);
    if ($id === false) {
        $id = count($img_types);
        $img_types[$id] = $value;
    }
    return $id;
}

?>