Oldie but a Goodie: Hierarchical Menu Rendering

I’ve been coding on experts exchange, testing myself. Here’s some relevant code for one of the answers.

They’re classes that have been used to generate hierarchical menus.

<?php
/**
 * @package menu
 * @author John Kawakami <johnk---.net>
 * @link http://www.slaptech.net Developed by The Slaptech Collective
 * @copyright 2006 Public Domain
 * @version $Id: Menu.class.php 97 2006-07-27 10:43:40Z johnk $
 *
 * Library to help create hierarchical menus.
 * Use MenuRenderer to display the menus.
 *
 * 
 * $menuBar = new Menu('MenuBar');
 * $fileMenu = $menuBar->addMenu( 'File' );
 * $fileMenu->addUrl( 'Open', '/admin/foo.php' );
 * $fileMenu->addUrl( 'Save', '/admin/bar.php' );
 * $fileMenu->addDivider();
 * $fileMenu->addUrl( 'Close', '/admin/baz.php' );
 * 
 */

include_once('ACL.class.php');

class Menu
{
    var $acl;
    var $menu;
    var $menuHash; //fixme - combine these two structures into one
    var $name;
    var $description;
    var $icon;
    var $url;
    var $highlight;

    function Menu( &$acl, $name, $description='', $icon='', $url='' )
    {
        $this->acl          =& $acl;
        $this->menu         = array();
        $this->name         =& $name;
        $this->description  =& $description;
        $this->icon         =& $icon;
        $this->url          =& $url;
        $this->highlight    = false;
    }
    function addObject( &$obj )
    {
        array_push( $this->menu, &$obj );
    }
    function addNamedObject( $name, &$obj )
    {
        $this->menuHash[$name] =& $obj;
        $this->addObject( &$obj );
    }
    /**
     * Utility function.
     */
    function &addMenu( &$acl, $name, $description='', $icon='', $url='' )
    {
        $new = new Menu( &$acl, $name, $description, $icon, $url );
        $this->addObject( $new );
        return $new;
    }
    function &getNamedObject( $name )
    {
        return $this->menuHash[$name];
    }
    function &addUrl( &$acl, $name, $url, $description='', $popup='' )
    {
        $new = new UrlMenuItem( &$acl, $name, $url, $description, $popup );
        $this->addObject( $new );
        return $new;
    }
    function &addDivider()
    {
        $new = new DividerMenuItem();
        $this->addObject( $new );
        return $new;
    }
    function select( $name )
    {
        for( $i=0; $i < count($this->menu); $i++ )
        {
            if ($this->menu[$i]->name==$name)
            {
                return $this->menu[$i];
            }
        }
    }
    function rewind()
    {
        reset($this->menu);
    }
    function &next()
    {
        // check the acl to see if this user can see the next item - fixme
        list( $index, $obj ) = each( $this->menu );
        return $obj;
    }
    function disable()
    {
        $this->enabled = false;
    }
    function enable()
    {
        $this->enabled = true;
    }
    function highlight()
    {
        $this->highlight = true;
    }
}

class UrlMenuItem extends Menu
{
    var $name;
    var $url;
    var $description;
    var $popup;

    function &UrlMenuItem( &$acl, $name, $url, $description='', $popup='' )
    {
        $this->acl          =& $acl;
        $this->name         =& $name;
        $this->url          =& $url;
        $this->description  =& $description;
        $this->popup        =& $popup;
        return $this;
    }

}
class DividerMenuItem extends Menu
{
    function DividerMenuItem()
    {
        $this->name = 'divider';
    }
}


?>

The menus are rendered via another class:

<?php
/**
 * @package menu
 * @author John Kawakami <johnk----.net>
 * @link http://www.slaptech.net Developed by The Slaptech Collective
 * @copyright 2006 Public Domain
 * @version $Id: MenuRenderer.class.php 92 2006-07-27 09:13:10Z johnk $
 *
 * Rendering classes for menus.  These are kind of like "templates"
 * for menus, except they can render hierarchies, portions of menus,
 * etc.  There's HTML code in here.  Yuk.
 */

/**
 * Rendering interface.  Each renderer must implement the
 * url, divider, and menu methods.  The menu() method is the heart
 * of the renderer.
 */
class MenuRenderer
{
    function MenuRenderer() {}
    function divider() {}
    function url() {}
    function menu() {}
    function item( $obj ) 
    {
        return $obj->name;
    }
}

/**
 * Renders a menu as an html unordered list (UL).
 */
class HtmlMenuRenderer extends MenuRenderer
{
    function HtmlMenuRenderer() {}

    function menu( &$menu, $indent=0 )
    {
        $t = str_repeat( ' ', $indent );
        $m =& $menu;
        $m->rewind();
        if ($m->name != '@')
            $output .= "$t<li>";
        if ($m->name != '@')
        {
            if ($m->url)
                $output .= "<a href='$m->url'>";
            if ($m->icon)
            {
                $output .= "<img src='$m->icon' border='0' />";
                $output .= "<br>";
            }
            $output .= $m->name."n";
            if ($m->url)
                $output .= '</a>';
        }
        $output .= "$t<ul>n";
        while( $obj =& $m->next() )
        {
            $class = strtolower(get_class($obj));
            // echo "$class<br>";
            switch( $class )
            {
                case 'apmenu':
                case 'menu':
                    $output .= $this->menu( $obj, $indent+4 );
                break;
                case 'urlmenuitem':
                    $output .= $this->url( $obj, $indent+4 );
                break;
                case 'dividermenuitem':
                    $output .= $this->divider( $obj, $indent+4 );
                break;
                default:
                    $output .= $this->item( $obj, $indent+4 );
                break;
            }
        }
        $output .= "$t</ul>n";
        if ($m->name != '@')
            $output .= "$t</li>n";
        return $output;
    }
    function divider( &$obj, $indent=0 )
    {
        $t = str_repeat( ' ', $indent );
        return "$t<li>-----------</li>n";
    }
    function url( &$obj, $indent=0 )
    {
        $t = str_repeat( ' ', $indent );
        return "$t<li><a href='$obj->url'>$obj->name</a></li>n";
    }
}