The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
<?php
//
// +----------------------------------------------------------------------+
// | PEAR :: DB_NestedSet_TigraMenu                                        |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2003 The PHP Group                                |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.0 of the PHP license,       |
// | that is bundled with this package in the file LICENSE, and is        |
// | available at through the world-wide-web at                           |
// | http://www.php.net/license/2_02.txt.                                 |
// | If you did not receive a copy of the PHP license and are unable to   |
// | obtain it through the world-wide-web, please send a note to          |
// | license@php.net so we can mail you a copy immediately.               |
// +----------------------------------------------------------------------+
// | Authors: Daniel Khan <dk@webcluster.at>                              |
// +----------------------------------------------------------------------+
//
// $Id: TigraMenu.php,v 1.6 2003/05/25 00:46:46 datenpunk Exp $
//

// {{{ DB_NestedSet_TigraMenu:: class

/**
* This class can be used to generate the data to build javascript popup menu
* from a DB_NestedSet node array.
* The Javascript part is done using the free available TigraMenu
* available at http://www.softcomplex.com/products/tigra_menu/.
* Currently version 1.0 is supported.
* Parts of this class where taken ftom the TreemMenu driver by Jason Rust
*
* @author       Daniel Khan <dk@webcluster.at>
* @package      DB_NestedSet
* @version      $Revision: 1.6 $
* @access       public
*/
// }}}
class DB_NestedSet_TigraMenu extends DB_NestedSet_Output {
    // {{{{ properties
	
	/**
	* @var integer The depth of the current menu.
	* @access private
	*/
	var $_levels	= 1;

	/**
	* @var integer The level we started at
	* @access private
	*/	
	var $_levelOffset = false;
	
	
	/**
	* @var array The current menu structure
	* @access private
	*/
	var $_structTigraMenu = false;

	/**
	* @var array The longest text for each level
	* @access private
	*/	
	var $_strlenByLevel	= array();

    // }}}
	// {{{ DB_NestedSet_TigraMenu
	
	/**
	* Constructor
	*
	* @param array $params A hash with parameters needed by the class
	* @see _createFromStructure()
	* @return bool
	**/
	function &DB_NestedSet_TigraMenu($params) {
		$this->_menu_id = $params['menu_id'];
		$this->_structTigraMenu = $this->_createFromStructure($params);
		return true;
	}
	
	// }}}
	// {{{ _createFromStructure()
	
	/**
	* Creates the JavaScript array for TigraMenu
	* Initially this method was introduced for the TreeMenu driver by Jason Rust
	*
	* o 'structure' => the result from $nestedSet->getAllNodes(true)
	* o 'textField' => the field in the table that has the text for node
	* o 'linkField' => the field in the table that has the link for the node
	*
	* @access private
	* @return string The TigraMenu JavaScript array
	*/
	function &_createFromStructure($params)
	{
		// Basically we go through the array of nodes checking to see
		// if each node has children and if so recursing.  The reason this
		// works is because the data from getAllNodes() is ordered by level
		// so a root node will always be first, and sub children will always
		// be after them.
		
		static $rootlevel;
		
		// always start at level 1
		if (!isset($params['currentLevel'])) {
			$params['currentLevel'] = 1;
		}
		
		if (!isset($rootlevel)) {
			$rootlevel = $params['currentLevel'];
		}
		
		if (isset($params['tigraMenu'])) {
			$tigraMenu = $tigraMenu.$params['tigraMenu'];
		}
		
		if(!$this->_levelOffset) {
			$this->_levelOffset = $params['currentLevel'];
		}

		if($this->_levels < ($params['currentLevel']- $this->_levelOffset)) {
			$this->_levels = $params['currentLevel'] - $this->_levelOffset;
		}
		
		
		// have to use a while loop here because foreach works on a copy of the array and
		// the child nodes are passed by reference during the recursion so that the parent
		// will know when they have been hit.
		reset($params['structure']);
		while(list($key, $node) = each($params['structure'])) {
			// see if we've already been here before
			if (isset($node['hit']) || $node['level'] < $params['currentLevel']) {
				continue;
			}
			
			// mark that we've hit this node
			$params['structure'][$key]['hit'] = $node['hit'] = true;
			
			$tag = array(
			isset($node[$params['textField']]) ? "'".$node[$params['textField']]."'" : 'null',
			isset($node[$params['linkField']]) ? "'".$node[$params['linkField']]."'" : 'null'
			);
			
			if (!$this->_strlenByLevel[$params['currentLevel'] - $this->_levelOffset] || 
                strlen($node[$params['textField']]) > $this->_strlenByLevel[$params['currentLevel'] - $this->_levelOffset]) {
				$this->_strlenByLevel[$params['currentLevel'] - $this->_levelOffset] = strlen($node[$params['textField']]);
			};
			
			$tigraMenu = $tigraMenu.$this->_openSubMenu($tag);
			
			// see if it has children
			if (($node['r'] - 1) != $node['l']) {
				$children = array();
				// harvest all the children
				$tempStructure = $params['structure'];
				foreach ($tempStructure as $childKey => $childNode) {
					if (!isset($childNode['hit']) &&
					$node['rootid'] == $childNode['rootid'] &&
					$node['l'] < $childNode['l'] &&
					$node['r'] > $childNode['r'] &&
					$childNode['level'] > $params['currentLevel']) {
						// important that we assign it by reference here, so that when the child
						// marks itself 'hit' the parent loops will know
						$children[] =& $params['structure'][$childKey];
					}
				}
				
				$recurseParams = $params;
				$recurseParams['structure'] = $children;
				$recurseParams['currentLevel']++;
				$tigraMenu = $tigraMenu.$this->_createFromStructure($recurseParams);
			}
			
			$tigraMenu = $tigraMenu.$this->_closeSubMenu();
		}
		return $tigraMenu;
	}
	
	// }}}
	// {{{ _openMenu()
	
	/**
	* Returns the string which opens the JavaScript menu
	* 
	* @access private
	* @param int $menu_id ID of the menu needed to use more than one menu on a page
	* @return string The JavaScript piece
	*/
	function _openMenu($menu_id=1) 
    {
		$str = false;
		$str = $str."var MENU_ITEMS".$menu_id." = new Array();\n";
		$str = $str."MENU_ITEMS".$menu_id." = [\n";
		return $str;
	}

	// }}}
	// {{{ _openSubMenu()	
	
	/**
	* Returns the string which opens a submenu within the JavaScript menu
	* 
	* @access private
	* @param array $tag Contains the content of the current item (name, link)
	* @return string The JavaScript piece
	*/
	function _openSubMenu($tag) 
    {
		$rtag = implode(', ', $tag);
		return "\n[".$rtag.',';
	}

	// }}}
	// {{{ _closeMenu()	
		
	/**
	* Closes the JavaScript array
	* 
	* @access private
	* @return string The JavaScript piece
	*/	
	function _closeMenu() 
    {
		
		return '];';
	}

	// }}}
	// {{{ _closeSubMenu()	
		
	/**
	* Closes the JavaScript array of a submenu
	* 
	* @access private
	* @return string The JavaScript piece
	*/		
	function _closeSubMenu() 
    {
		return "\n],";
	}
	
	// }}}
	// {{{ _addStyles()

	/**
	* Creates the JavaScript code which sets the styles for each level
	* 
	* @access private
	* @param int $menu_id ID of the menu needed to use more than one menu on a page
	* @param array $rootStyles Array of style attributes for the top items
	* @param array $childStyles Array of style attributes for the sub items
	* @return string The JavaScript piece
	*/		
	function _addStyles($menu_id, $rootStyles, $childStyles = false) 
    {
		if (!$childStyles) {
			$childStyles = $rootStyles;
		}
		
		$styles = array();
		foreach ($rootStyles as $key => $val) {
			foreach ($val as $skey => $sval) {
				$styles["'$key'"][$skey][] = "'$sval'";
			}
		}
		
		foreach ($childStyles as $key => $val) {
			foreach ($val as $skey => $sval) {
				for ($i = 1; $i <= $this->_levels; $i++) {
					$styles["'$key'"][$skey][] = "'$sval'";
				}
			}
		}
		
		$menustyles = false;
		$menustyles = $menustyles . 'var MENU_STYLES'.$menu_id." = new Array();\n";
		foreach ($styles as $key => $val) {
			$menustyles = $menustyles.'MENU_STYLES'.$menu_id."[$key] = [\n";
			foreach ($val as $skey => $sval) {
				$menustyles = $menustyles . "'$skey', [".implode(', ', $sval)."],\n";
			}
			$menustyles = $menustyles."];\n";
		}
		
		return $menustyles;
	}

	// }}}	
	// {{{ _addGeometry()

	/**
	* Creates the JavaScript code which sets the position and geometry of the menu
	* 
	* @access private
	* @param int $menu_id ID of the menu needed to use more than one menu on a page
	* @param array $rootGeometry Array of geometry attributes for the top items
	* @param array $childGeometry  Array of geometry attributes for the sub items
	* @return string The JavaScript piece
	*/		
	function _addGeometry($menu_id, $rootGeometry, $childGeometry = false) 
    {
		if (!$childGeometry) {
			$childGeometry = $rootGeometry;
		}
		
		$params = array();
		$geometry = array();
		foreach ($rootGeometry as $key => $val) {
			$geometry["'$key'"][] = $val;
			$incr = false;
			if (strpos($val, ',') !== false) {
				list($start, $interval) = explode(',',$val);
				$incr = true;
			}
			
			$ratio = false;
			if ($key == 'width' && strpos($val, '*') !== false) {
				$ratio = trim(str_replace('*','', $val));
			}
			if ($incr) {
				$val = trim($interval);
				if ($key == 'left' && preg_match('/[+-]/', $interval)) {
					$val = $params[0]['width'] + trim($val);
				}
			} elseif ($incr) {
				$val = trim($start);
			} elseif ($ratio) {
				$val = $ratio * $this->_strlenByLevel[0];
			}
			$geometry["'$key'"][0] = $val;
			$params[0][$key] = $val;
		}
		
		foreach($childGeometry as $key => $val) {
			$incr = false;
			if (strpos($val, ',') !== false) {
				list($start, $interval) = explode(',', $val);
				$incr = true;
			}
			
			$ratio = false;
			if ($key == 'width' && strpos($val, '*') !== false) {
				$ratio = trim(str_replace('*', '', $val));
			}
			
			for ($i = 1; $i <= $this->_levels; $i++) {
				if ($incr && isset($lastval[$key])) {
					$val = trim($interval);
					if($key == 'block_left' && preg_match('/[+-]/', $interval)) {
						$val = $params[$i - 1]['width'] + trim($val);
					}
				} elseif($incr) {
					$val = trim($start);
				} elseif ($ratio) {
					$val = $ratio * $this->_strlenByLevel[$i];
					if($val < $params[0]['width']) {
						$val = 	$params[0]['width'];
					}
				}
				
				$lastval[$key] = $val;
				$geometry["'$key'"][] = $val;
				$params[$i][$key] = $val;
			}
			
		}
		
		$pos = false;
		$pos = $pos . 'var MENU_POS'.$menu_id." = new Array();\n";
		foreach ($geometry as $key => $val) {
			$pos = $pos . 'MENU_POS' . $menu_id . "[$key] = [" . implode(', ', $val) . "];\n";
		}
		
		return $pos;
	}

	// }}}	
	// {{{ printTree()
		
	/**
	* Print's the current tree using the output driver
	*
	* @access public
	*/
	function printTree() 
    {
		if (!$options = $this->_getOptions('printTree')) {
            return PEAR::raiseError("TigraMenu::printTree() needs options. See TigraMenu::setOptions()", NESEO_ERROR_NO_OPTIONS, PEAR_ERROR_TRIGGER, E_USER_ERROR);
		}
		
		echo $this->_openMenu($options['menu_id']) . $this->_structTigraMenu  .$this->_closeMenu();
		echo "\n\n";
		echo $this->_addStyles($options['menu_id'], $options['rootStyles'], $options['childStyles']);
		echo "\n\n";
		echo $this->_addGeometry($options['menu_id'], $options['rootGeometry'], $options['childGeometry']);
	}
	
	// }}}
}
?>