home *** CD-ROM | disk | FTP | other *** search
/ PC World 2001 August / PCWorld_2001-08_cd.bin / Komunikace / phptriad / phptriadsetup2-11.exe / php / pear / Experimental / HTML / Menu.php
PHP Script  |  2001-03-13  |  22KB  |  653 lines

  1. <?php
  2. // +----------------------------------------------------------------------+
  3. // | PHP version 4.0                                                      |
  4. // +----------------------------------------------------------------------+
  5. // | Copyright (c) 1997, 1998, 1999, 2000, 2001 The PHP Group             |
  6. // +----------------------------------------------------------------------+
  7. // | This source file is subject to version 2.0 of the PHP license,       |
  8. // | that is bundled with this package in the file LICENSE, and is        |
  9. // | available at through the world-wide-web at                           |
  10. // | http://www.php.net/license/2_02.txt.                                 |
  11. // | If you did not receive a copy of the PHP license and are unable to   |
  12. // | obtain it through the world-wide-web, please send a note to          |
  13. // | license@php.net so we can mail you a copy immediately.               |
  14. // +----------------------------------------------------------------------+
  15. // | Authors: Ulf Wendel <ulf.wendel@phpdoc.de>                           |
  16. // +----------------------------------------------------------------------+
  17. //
  18. // $Id: Menu.php,v 1.6 2001/03/13 00:18:38 uw Exp $
  19.  
  20. /**
  21. * Generates a HTML menu from a multidimensional hash.
  22. * Special thanks to the original author: Alex Vorobiev  <sasha@mathforum.com>
  23. *
  24. * @version  $id: $
  25. * @author   Ulf Wendel <ulf.wendel@phpdoc.de>
  26. * @access   public
  27. * @package  HTML
  28. */
  29. class menu {
  30.  
  31.  
  32.     /**
  33.     * Menu structure as a multidimensional hash.
  34.     * 
  35.     * @var  array
  36.     * @see  setMenu(), Menu()
  37.     */    
  38.     var $menu = array();
  39.     
  40.     
  41.     /**
  42.     * Mapping from URL to menu path.
  43.     *
  44.     * @var  array
  45.     * @see  getPath()
  46.     */
  47.     var $urlmap = array();
  48.     
  49.     
  50.     /**
  51.     * Menu type: tree, rows, you-are-here.
  52.     * 
  53.     * @var  array
  54.     * @see  setMenuType()
  55.     */
  56.     var $menu_type = "tree";
  57.     
  58.     
  59.     /**
  60.     * Path to a certain menu item.
  61.     * 
  62.     * Internal class variable introduced to save some recursion overhead. 
  63.     *
  64.     * @var  array
  65.     * @see  get(), getPath()
  66.     */
  67.     var $path = array();
  68.     
  69.     
  70.     /**
  71.     * Generated HTML menu.
  72.     * 
  73.     * @var  string
  74.     * @see  get()
  75.     */
  76.     var $html = "";
  77.     
  78.     
  79.     /**
  80.     * URL of the current page.
  81.     *
  82.     * This can be the URL of the current page but it must not be exactly the 
  83.     * return value of getCurrentURL(). If there's no entry for the return value
  84.     * in the menu hash getPath() tries to find the menu item that fits best
  85.     * by shortening the URL sign by sign until it find an entry that fits.
  86.     * 
  87.     * @see  getCurrentURL(), getPath()
  88.     */
  89.     var $current_url = "";
  90.  
  91.     
  92.     /**
  93.     * Initializes the menu, sets the type and menu structure.
  94.     * 
  95.     * @param    array
  96.     * @param    string
  97.     * @see      setMenuType(), setMenu()
  98.     */
  99.     function menu($menu = "", $type = "tree") { 
  100.         
  101.         if (is_array($menu))
  102.             $this->setMenu($menu);
  103.  
  104.         $this->setMenuType($type);
  105.         
  106.     } // end constructor   
  107.     
  108.     
  109.     /**
  110.     * Sets the menu structure.
  111.     *
  112.     * The menu structure is defined by a multidimensional hash. This is 
  113.     * quite "dirty" but simple and easy to traverse. An example 
  114.     * show the structure. To get the following menu:
  115.     * 
  116.     * 1  - Projects
  117.     * 11 - Projects => PHPDoc
  118.     * 12 - Projects => Forms
  119.     * 2  - Stuff
  120.     *
  121.     * you need the array:
  122.     * 
  123.     * $menu = array( 
  124.     *           1 => array( 
  125.     *                  "title" => "Projects", 
  126.     *                  "url" => "/projects/index.php",
  127.     *                  "sub" => array(
  128.     *                           11 => array(
  129.     *                                       "title" => "PHPDoc",
  130.     *                                       ...
  131.     *                                     ),
  132.     *                           12 => array( ... ),
  133.     *                 )
  134.     *             ),
  135.     *           2 => array( "title" => "Stuff", "url" => "/stuff/index.php" )
  136.     *        )
  137.     *
  138.     * Note the index "sub" and the nesting. Note also that 1, 11, 12, 2 
  139.     * must be unique. The class uses them as ID's. 
  140.     * 
  141.     * @param    array
  142.     * @access   public
  143.     * @see      append(), update(), delete()
  144.     */
  145.     function setMenu($menu) {
  146.         
  147.         $this->menu = $menu;
  148.         $this->urlmap = array();
  149.         
  150.     } // end func setMenu
  151.     
  152.     
  153.     /**
  154.     * Sets the type / format of the menu: tree, rows or urhere.
  155.     *
  156.     * @param    string  "tree", "rows", "urhere", "prevnext"
  157.     * @access   public
  158.     */
  159.     function setMenuType($menu_type) {
  160.     
  161.         $this->menu_type = strtolower($menu_type);
  162.         
  163.     } // end func setMenuType
  164.  
  165.  
  166.     /**
  167.     * Returns the HTML menu.
  168.     * 
  169.     * @param    string  Menu type: tree, urhere, rows, prevnext
  170.     * @return   string  HTML of the menu
  171.     * @access   public
  172.     */
  173.     function get($menu_type = "") {
  174.         if ("" != $menu_type)
  175.             $this->setMenuType($menu_type);
  176.  
  177.         $this->html = ""; 
  178.                    
  179.         // buildMenu for rows cares on this itself            
  180.         if ("rows" != $this->menu_type)             
  181.             $this->html  .= $this->getStart();
  182.         
  183.         // storing to a class variable saves some recursion overhead
  184.         $this->path = $this->getPath();
  185.         $this->buildMenu($this->menu);
  186.     
  187.         if ("rows" != $this->menu_typ)
  188.             $this->html .= $this->getEnd();
  189.         
  190.         return $this->html;
  191.     } // end func get
  192.     
  193.     
  194.     /**
  195.     * Prints the HTML menu.
  196.     * 
  197.     * @brother  get()
  198.     * @return   void
  199.     */
  200.     function show($menu_type = "") {
  201.         print $this->get($menu_type);
  202.     } // end func show
  203.     
  204.     /**
  205.     * Returns a sitemap.
  206.     * 
  207.     * @return string  HTML code
  208.     * @access public
  209.     */
  210.     function getSitemap() {
  211.       
  212.       $oldtype = $this->menu_type;
  213.       $this->setMenuType("tree");
  214.       
  215.       $this->html = $this->getStart();
  216.     
  217.       $this->path = $this->getPath();
  218.       $this->buildSitemap($this->menu);
  219.  
  220.       $this->setMenuType($oldtype);
  221.       
  222.       return $this->html . $this->getEnd();
  223.     } // end func getSitemap
  224.  
  225.     
  226.     /**
  227.     * Returns the prefix of the HTML menu items.
  228.     * 
  229.     * @return   string  HTML menu prefix
  230.     * @see      getEnd(), get()
  231.     */
  232.     function getStart() {
  233.     
  234.         $html = "";
  235.         switch ($this->menu_type) {
  236.             case "rows":
  237.             case "prevnext":
  238.                 $html .= "<table border><tr>";
  239.                 break;
  240.                 
  241.             case "tree":
  242.             case "urhere":
  243.                 $html .= "<table border>";
  244.                 break;
  245.         }
  246.         
  247.         return $html;
  248.     } // end func getStart
  249.  
  250.     
  251.     /**
  252.     * Returns the postfix of the HTML menu items.
  253.     *
  254.     * @return   string  HTML menu postfix
  255.     * @see      getStart(), get()
  256.     */
  257.     function getEnd() {
  258.  
  259.         $html = "";
  260.         switch ($this->menu_type) {
  261.             case "rows":
  262.             case "prevnext":
  263.                 $html .="</tr></table>";
  264.                 break;
  265.             
  266.             case "tree":
  267.             case "urhere":
  268.                 $html = "</table>";
  269.                 break;
  270.         }
  271.         
  272.         return $html;
  273.     } // end func getEnd
  274.     
  275.     
  276.     /**
  277.     * Build the menu recursively.
  278.     *
  279.     * @param    array   first call: $this->menu, recursion: submenu
  280.     * @param    integer level of recursion, current depth in the tree structure
  281.     * @param    integer prevnext flag
  282.     */
  283.     function buildMenu($menu, $level = 0, $flag_stop_level = -1) {
  284.         static $last_node = array(), $up_node = array();
  285.         
  286.         // the recursion goes slightly different for every menu type
  287.         switch ($this->menu_type) {
  288.             
  289.             case "tree":
  290.             
  291.                 // loop through the (sub)menu
  292.                 foreach ($menu as $node_id => $node) {
  293.  
  294.                     if ($this->current_url == $node["url"]) {
  295.                         // menu item that fits to this url - "active" menu item
  296.                         $type = 1;
  297.                     } else if (isset($this->path[$level]) && $this->path[$level] == $node_id) {
  298.                         // processed menu item is part of the path to the active menu item
  299.                         $type = 2;
  300.                     } else {
  301.                         // not selected, not a part of the path to the active menu item
  302.                         $type = 0;
  303.                     }
  304.         
  305.                     $this->html .= $this->getEntry($node, $level, $type);
  306.                     
  307.                     // follow the subtree if the active menu item is in it
  308.                     if ($type && isset($node["sub"]))
  309.                         $this->buildMenu($node["sub"], $level + 1);
  310.                 }
  311.                 break;
  312.                 
  313.             case "rows":
  314.                 
  315.                 // every (sub)menu has it's own table
  316.                 $this->html .= $this->getStart();
  317.                 $submenu = false;
  318.                 
  319.                 // loop through the (sub)menu
  320.                 foreach ($menu as $node_id => $node) {
  321.                 
  322.                     if ($this->current_url == $node["url"]) {
  323.                         // menu item that fits to this url - "active" menu item
  324.                         $type = 1;
  325.                     } else if (isset($this->path[$level]) && $this->path[$level] == $node_id) {
  326.                         // processed menu item is part of the path to the active menu item
  327.                         $type = 2;
  328.                     } else {
  329.                         // not selected, not a part of the path to the active menu item
  330.                         $type = 0;
  331.                     }
  332.  
  333.                     $this->html .= $this->getEntry($node, $level, $type);
  334.                     
  335.                     // remember the subtree
  336.                     if ($type && isset($node["sub"]))
  337.                         $submenu = $node["sub"];
  338.                         
  339.                 }
  340.                 
  341.                 // close the table for this level
  342.                 $this->html .= $this->getEnd();
  343.                 
  344.                 // go deeper if neccessary
  345.                 if ($submenu)
  346.                     $this->buildMenu($submenu, $level + 1);
  347.  
  348.                 break;
  349.  
  350.             case "urhere":
  351.  
  352.                 // loop through the (sub)menu
  353.                 foreach ($menu as $node_id => $node) {
  354.                         
  355.                     if ($this->current_url == $node["url"]) {
  356.                         // menu item that fits to this url - "active" menu item
  357.                         $type = 1;
  358.                     } else if (isset($this->path[$level]) && $this->path[$level] == $node_id) {
  359.                         // processed menu item is part of the path to the active menu item
  360.                         $type = 2;
  361.                     } else {
  362.                         // not selected, not a part of the path to the active menu item
  363.                         $type = 0;
  364.                     }
  365.         
  366.                     // follow the subtree if the active menu item is in it
  367.                     if ($type) {
  368.                         $this->html .= $this->getEntry($node, $level, $type);
  369.                         if (isset($node["sub"])) {
  370.                             $this->buildMenu($node["sub"], $level + 1);
  371.                             continue;
  372.                         }
  373.                     }
  374.                     
  375.                 }
  376.                 break;
  377.                 
  378.           case "prevnext":
  379.                 
  380.                 // loop through the (sub)menu
  381.                 foreach ($menu as $node_id => $node) {
  382.                     
  383.                     if (-1 != $flag_stop_level) {
  384.                     
  385.                         // add this item to the menu and stop recursion - (next >>) node
  386.                         if ($flag_stop_level == $level) {
  387.                             $this->html .= $this->getEntry($node, $level, 4);
  388.                             $flag_stop_level = -1;
  389.                         }
  390.  
  391.                         break;
  392.                         
  393.                     
  394.                     } else if ($this->current_url == $node["url"]) {
  395.                         // menu item that fits to this url - "active" menu item
  396.                         $type = 1;
  397.                         $flag_stop_level = $level;
  398.                         
  399.                         if (0 != count($last_node)) {
  400.                         
  401.                             $this->html .= $this->getEntry($last_node, $level, 3);
  402.                           
  403.                         } else {
  404.                         
  405.                             // WARNING: if there's no previous take the first menu entry - you might not like this rule!
  406.                             reset($this->menu);
  407.                             list($node_id, $first_node) = each($this->menu);
  408.                             $this->html .= $this->getEntry($first_node, $level, 3);
  409.                             
  410.                         
  411.                         }
  412.                         
  413.                         if (0 != count($up_node)) {
  414.                           
  415.                           $this->html .= $this->getEntry($up_node, $level, 5);
  416.                           
  417.                         } else {
  418.                         
  419.                             // WARNING: if there's no up take the first menu entry - you might not like this rule!
  420.                             reset($this->menu);
  421.                             list($node_id, $first_node) = each($this->menu);
  422.                             $this->html .= $this->getEntry($first_node, $level, 5);
  423.                           
  424.                         }
  425.                         
  426.                     } else if (isset($this->path[$level]) && $this->path[$level] == $node_id) {
  427.                         // processed menu item is part of the path to the active menu item
  428.                         $type = 2;
  429.                      
  430.                     } else {
  431.                     
  432.                         $type = 0;
  433.                     
  434.                     }
  435.                     
  436.                     // remember the last (<< prev) node
  437.                     $last_node = $node;
  438.                     
  439.                     // follow the subtree if the active menu item is in it
  440.                     if ($type && isset($node["sub"])) {
  441.                         $up_node = $node;
  442.                         $flag_stop_level = $this->buildMenu($node["sub"], $level + 1, (-1 != $flag_stop_level) ? $flag_stop_level + 1 : -1); 
  443.                     }
  444.                 }
  445.                 break;
  446.                 
  447.             
  448.         } // end switch menu_type
  449.         
  450.         return ($flag_stop_level) ? $flag_stop_level - 1 : -1;
  451.     } // end func buildMenu
  452.     
  453.     
  454.     /**
  455.     * Build the menu recursively.
  456.     *
  457.     * @param    array   first call: $this->menu, recursion: submenu
  458.     * @param    int     level of recursion, current depth in the tree structure
  459.     */
  460.     function buildSitemap($menu, $level = 0) {
  461.  
  462.         // loop through the (sub)menu
  463.         foreach ($menu as $node_id => $node) {
  464.         
  465.             if ($this->current_url == $node["url"]) {
  466.                 // menu item that fits to this url - "active" menu item
  467.                 $type = 1;
  468.             } else if (isset($this->path[$level]) && $this->path[$level] == $node_id) {
  469.                 // processed menu item is part of the path to the active menu item
  470.                 $type = 2;
  471.             } else {
  472.                 // not selected, not a part of the path to the active menu item
  473.                 $type = 0;
  474.             }
  475.  
  476.             $this->html .= $this->getEntry($node, $level, $type);
  477.             
  478.             // follow the subtree if the active menu item is in it
  479.             if (isset($node["sub"]))
  480.                 $this->buildSitemap($node["sub"], $level + 1);
  481.         }
  482.  
  483.     } // end func buildSitemap
  484.     
  485.     
  486.     /**
  487.     * Returns the HTML of one menu item.
  488.     * 
  489.     * @param    array   menu item data (node data)
  490.     * @param    integer level in the menu tree
  491.     * @param    string  menu item type: 0, 1, 2. 0 => plain menu item,
  492.     *                   1 => selected (active) menu item, 2 => item 
  493.     *                   is a member of the path to the selected (active) menu item
  494.     *                   3 => previous entry, 4 => next entry
  495.     * @return   string  HTML
  496.     * @see      buildMenu()
  497.     */
  498.     function getEntry(&$node, $level, $item_type) {
  499.  
  500.         $html = "";
  501.         
  502.         if ("tree" == $this->menu_type) {
  503.             // tree menu
  504.             $html .= "<tr>";
  505.             $indent = "";
  506.             if ($level) 
  507.                 for ($i = 0; $i < $level; $i++)
  508.                     $indent .= "   ";
  509.         }
  510.         
  511.         // draw the <td></td> cell depending on the type of the menu item
  512.         switch ($item_type) {
  513.             case 0:
  514.                 // plain menu item
  515.                 $html .= sprintf('<td>%s<a href="%s">%s</a></td>',
  516.                                     $indent,
  517.                                     $node["url"],
  518.                                     $node["title"]
  519.                                  );
  520.                 break;
  521.                 
  522.             case 1:
  523.                 // selected (active) menu item
  524.                 $html .= sprintf('<td>%s<b>%s</b></td>', 
  525.                                    $indent,
  526.                                    $node["title"]
  527.                                 );
  528.                 break;
  529.                 
  530.             case 2:
  531.                 // part of the path to the selected (active) menu item
  532.                 $html .= sprintf('<td>%s<b><a href="%s">%s</a></b>%s</td>',
  533.                                     $indent,
  534.                                     $node["url"],
  535.                                     $node["title"],
  536.                                     ("urhere" == $this->menu_type) ? " >> " : ""
  537.                                 );
  538.                 break;
  539.                 
  540.             case 3: 
  541.                 // << previous url
  542.                 $html .= sprintf('<td>%s<a href="%s"><< %s</a></td>',
  543.                                     $indent,
  544.                                     $node["url"],
  545.                                     $node["title"]
  546.                                 );
  547.                 break;
  548.  
  549.             case 4:
  550.                 // next url >>
  551.                 $html .= sprintf('<td>%s<a href="%s">%s >></a></td>',
  552.                                     $indent,
  553.                                     $node["url"],
  554.                                     $node["title"]
  555.                                 );
  556.                 break;
  557.  
  558.             case 5:
  559.                 // up url ^^
  560.                 $html .= sprintf('<td>%s<a href="%s">^ %s ^</a></td>',
  561.                                     $indent,
  562.                                     $node["url"],
  563.                                     $node["title"]
  564.                           );
  565.                 break;                          
  566.                 
  567.         }
  568.             
  569.         if ("tree" == $this->menu_type)
  570.             $html .= "</tr>\n";
  571.  
  572.         return $html;
  573.     } // end func getEnty
  574.  
  575.  
  576.     /*
  577.     * Returns the path of the current page in the menu "tree".
  578.     *
  579.     * @return   array    path to the selected menu item
  580.     * @see      buildPath(), $urlmap
  581.     */
  582.     function getPath() {
  583.  
  584.         $this->current_url = $this->getCurrentURL();        
  585.         $this->buildPath($this->menu, array());  
  586.  
  587.         while ($this->current_url && !isset($this->urlmap[$this->current_url]))
  588.           $this->current_url = substr($this->current_url, 0, -1);
  589.         
  590.         return $this->urlmap[$this->current_url];          
  591.     } // end func getPath
  592.     
  593.     
  594.     /**
  595.     * Computes the path of the current page in the menu "tree".
  596.     * 
  597.     * @param    array       first call: menu hash / recursion: submenu hash
  598.     * @param    array       first call: array() / recursion: path
  599.     * @return   boolean     true if the path to the current page was found, 
  600.     *                       otherwise false. Only meaningful for the recursion.
  601.     * @global   $PHP_SELF   
  602.     * @see      getPath(), $urlmap
  603.     */
  604.     function buildPath($menu, $path) {
  605.  
  606.         // loop through the (sub)menu
  607.         foreach ($menu as $node_id => $node) {
  608.         
  609.             // save the path of the current node in the urlmap
  610.             $this->urlmap[$node["url"]] = $path;
  611.             
  612.             // we got'em - stop the search by returning true
  613.             // KLUDGE: getPath() works with the best alternative for a URL if there's
  614.             // no entry for a URL in the menu hash, buildPath() does not perform this test
  615.             // and might do some unneccessary recursive runs.
  616.             if ($node["url"] == $this->current_url)
  617.                return true;
  618.                
  619.             // current node has a submenu               
  620.             if ($node["sub"]) {
  621.                 
  622.                 // submenu path = current path + current node
  623.                 $subpath = $path;
  624.                 $subpath[] = $node_id;
  625.                 
  626.                 // continue search recursivly - return is the inner loop finds the 
  627.                 // node that belongs to the current url
  628.                 if ($this->buildPath($node["sub"], $subpath))
  629.                     return true;
  630.             }
  631.  
  632.         } 
  633.  
  634.         // not found
  635.         return false;        
  636.     } // end func buildPath
  637.     
  638.     
  639.     /**
  640.     * Returns the URL of the currently selected page.
  641.     *
  642.     * The returned string is used for all test against the URL's
  643.     * in the menu structure hash.
  644.     *
  645.     * @return string
  646.     */
  647.     function getCurrentURL() {
  648.       return $GLOBALS["PHP_SELF"];
  649.     } // end func getCurrentURL
  650.     
  651. } // end class menu
  652. ?>