title: Menu Component CakePHP markstory
body: <p>I've edited the CakePHP Menu Component made by Mark Story to make it compatible with CakePHP 2.2.3.
  <a href="https://github.com/markstory/cakephp_menu_component" rel="nofollow">https://github.com/markstory/cakephp_menu_component</a></p>
  
  <p>The problem is that I still get an error that I can't figure out how to get rid of:</p>
  
  <p><a href="http://imm.io/MK9J" rel="nofollow">http://imm.io/MK9J</a></p>
  
  <p><a href="http://imm.io/MK9W" rel="nofollow">http://imm.io/MK9W</a></p>
  
  <p><a href="http://imm.io/MKa7" rel="nofollow">http://imm.io/MKa7</a></p>
  
  <p>Here is the modified code:</p>
  
  <pre><code>&lt;?php
  /**
   * Menu Component
   *
   * Uses ACL to generate Menus.
   *
   * Copyright 2008, Mark Story.
   *
   * Licensed under The MIT License
   * Redistributions of files must retain the above copyright notice.
   *
   * @copyright Copyright 2008, Mark Story.
   * @link http://mark-story.com
   * @version 1.1
   * @author Mark Story &lt;mark@mark-story.com&gt;
   * @license http://www.opensource.org/licenses/mit-license.php The MIT License
   */
  class MenuComponent extends Component {
  /**
   * The Default Menu Parent for things that have no parent element defined
   * used a lot by menu items generated by controller folder scrapings
   *
   * @var string
   */
      public $defaultMenuParent = null;
  /**
   * Set to false to disable the auto menu generation in startup()
   * Useful if you want your menus generated off of Aro's other than the user in the current session.
   *
   * @var boolean
   */
      public $autoLoad = true;
  /**
   * Controller reference
   *
   * @var object
   */ 
      public $Controller = null;
  /**
   * Components used by Menu
   *
   * @var array
   */
      public $components = array('Acl', 'Auth');
  
  /**
   * Key for the caching
   *
   * @var string
   */
      public $cacheKey = 'menu_storage';
  
  /**
   * Time to cache menus for.
   *
   * @var string  String compatible with strtotime.
   */
      public $cacheTime = '+1 day';
  /**
   * cache config key
   *
   * @var string
   */
      public $cacheConfig = 'menu_component';
  /**
   * Separator between controller and action name.
   *
   * @var string
   */
      public $aclSeparator = '/';
  
  /**
   * The Node path to get to the controller listing
   *
   * @var string
   **/
      public $aclPath = 'controllers/';
  
  /**
   * Array of Actions to exclude when making menus.
   * Per controller exclusions can be set with Controller::menuOptions
   *
   * @var array
   **/
      public $excludeActions = array('view', 'edit', 'delete', 'admin_edit', 'admin_delete', 'admin_edit', 'admin_view');
  
  /**
   * Completed list of methods to not include in menus. Includes all of Controller's methods.
   *
   * @var array
   **/
      public $excludedMethods = array();
  /**
   * The Completed menu for the current user.
   *
   * @var array
   */
      public $menu = array(); 
  
  /**
   * Raw menus before formatting, either loaded from parsing controllers directory or loading Cache
   *
   * @var array
   */
      public $rawMenus = array();
  /**
   * Internal Flag to check if new menus have been added to a cached menu set.  Indicates that new menu items
   * have been added and that menus need to be rebuilt.
   * 
   */
      protected $_rebuildMenus = false;
  
  /**
   * initialize function
   *
   * Takes Settings declared in Controller and assigns them.
   *
   * @return bool
   **/
      public function initialize(Controller $controller) {
          if (!empty($settings)) {
              $this-&gt;_set($settings);
          }
          return true;
      }
  
  /**
   * Startup Method
   *
   * Automatically makes menus for all a the controllers based on the current user.
   * If $this-&gt;autoLoad = false then you must manually loadCache(), 
   * contstructMenu() and writeCache().
   *
   * @param Object $Controller
   */
      public function startup(Controller $controller) {
          $this-&gt;Controller =&amp; $Controller;
  
          Cache::config($this-&gt;cacheConfig, array('engine' =&gt; 'File', 'duration' =&gt; $this-&gt;cacheTime, 'prefix' =&gt; $this-&gt;cacheKey));
  
          //no active session, no menu can be generated
          if (!$this-&gt;Auth-&gt;user()) {
              return;
          }
          if ($this-&gt;autoLoad) {
              $this-&gt;loadCache();
              $this-&gt;constructMenu($this-&gt;Auth-&gt;user());
              $this-&gt;writeCache();
          }
      }
  
  /**
   * Write the current Block Access data to a file.
   *
   * @return boolean on success of writing a file.
   */
      public function writeCache() {
          $data = array(
              'menus' =&gt; $this-&gt;rawMenus
          );
          if (Cache::write($this-&gt;cacheKey, $data, $this-&gt;cacheConfig)) {
              return true;
          }
          $this-&gt;log('Menu Component - Could not write Menu cache.');
          return false;
      }
  
  /**
   * Load the Cached Permissions and restore them
   *
   * @return boolean true if cache was loaded.
   */
      public function loadCache() {
          if ($data = Cache::read($this-&gt;cacheKey, $this-&gt;cacheConfig)) {
              $this-&gt;rawMenus = $this-&gt;_mergeMenuCache($data['menus']);
              return true;
          }
          $this-&gt;_rebuildMenus = true;
          return false;
      }
  
  /**
   * Clears the raw Menu Cache, this will in turn force
   * a menu rebuild for each ARO that needs a menu.
   *
   * @return boolean
   **/
      public function clearCache() {
          return Cache::delete($this-&gt;cacheKey, $this-&gt;cacheConfig);
      }
  
  /**
   * Construct the menus From the Controllers in the Application.  This is an expensive
   * Process Timewise and is cached.
   *
   * @param string $aro  Aro Alias / identification array that a menu is needed for.
   */ 
      public function constructMenu($aro) {
          $aroKey = $aro;
          if (is_array($aro)) {
              $aroKey = key($aro) . $aro[key($aro)];
          }
          $cacheKey = $aroKey . '_' . $this-&gt;cacheKey;
          $completeMenu = Cache::read($cacheKey, $this-&gt;cacheConfig);
          if (!$completeMenu || $this-&gt;_rebuildMenus == true) {
              $this-&gt;generateRawMenus();
  
              $menu = array();
              $size = count($this-&gt;rawMenus);
              for ($i = 0; $i &lt; $size; $i++) {
                  $item = $this-&gt;rawMenus[$i];
  
                  $aco = Inflector::underscore($item['url']['controller']);
                  if (isset($item['url']['action'])) {
                      $aco = $this-&gt;aclPath . $aco . $this-&gt;aclSeparator . $item['url']['action'];
                  }
                  if ($this-&gt;Acl-&gt;check($aco, $aro)) {
                      if (!isset($menu[$item['id']])) {
                          $menu[$item['id']] = $this-&gt;rawMenus[$i];
                      }
                  }
              }
              $completeMenu = $this-&gt;_formatMenu($menu);
              Cache::write($cacheKey, $completeMenu, $this-&gt;cacheConfig);
          }
          $this-&gt;menu = $completeMenu;
      }
  
  /**
   * Generate Raw Menus from Controller in the Application
   * Loads a list of All controllers in the app/controllers, imports the class and gets a method
   * list.  Uses a common exclusion list to remove unwanted methods.  Each Controller can specify a 
   * menuOptions var which allows additional menu configuration.
   * 
   * Menu Options for Controllers:
   *      exclude =&gt; actions to exclude from the menu list
   *      parent =&gt; Parent link to add a controller / actions underneath
   *      alias =&gt; array of action =&gt; aliases Allows you to set friendly link names for actions
   *
   * @return void sets $this-&gt;rawMenus
   */ 
      public function generateRawMenus() {
          $Controllers = $this-&gt;getControllers();
          $cakeAdmin = Configure::read('Routing.admin');
          $this-&gt;createExclusions();
  
          //go through the controllers folder and make an array of every menu that could be used.
          foreach($Controllers as $Controller) {
              if ($Controller == 'App') {
                  continue;
              }
  
              $ctrlName = $Controller;
              App::uses($ctrlName, 'Controller');
              $ctrlclass = $ctrlName;
              $methods = get_class_methods($ctrlclass);
              $classVars = get_class_vars($ctrlclass);
  
              $menuOptions = $this-&gt;setOptions($classVars);
              if ($menuOptions === false) {
                  continue;
              }
  
              $methods = $this-&gt;filterMethods($methods, $menuOptions['exclude']);
  
              $ctrlCamel = Inflector::variable($ctrlName);
              $ctrlHuman = Inflector::humanize(Inflector::underscore($ctrlCamel));
              $methodList = array();
              $adminController = false;
              foreach ($methods as $action) {
                  $camelAction = Inflector::variable($action);
  
                  if (empty($menuOptions['alias']) || !isset($menuOptions['alias'][$action])) {
                      $human = Inflector::humanize(Inflector::underscore($action));
                  } else {
                      $human = $menuOptions['alias'][$action];
                  }
                  $url = array(
                      'controller' =&gt; $ctrlCamel,
                      'action' =&gt; $action
                  );
                  if ($cakeAdmin) {
                      $url[$cakeAdmin] = false;
                  }
                  if (strpos($action, $cakeAdmin . '_') !== false &amp;&amp; $cakeAdmin) {
                      $url[$cakeAdmin] = true;
                      $adminController = true;
                  }
  
                  $parent = $menuOptions['controllerButton'] ? $ctrlCamel : $menuOptions['parent'];
                  $this-&gt;rawMenus[] = array(
                      'parent' =&gt; $parent,
                      'id' =&gt; $this-&gt;_createId($ctrlCamel, $action),
                      'title' =&gt; $human,
                      'url' =&gt; $url,
                      'weight' =&gt; 0,
                  );
              }
  
              if ($menuOptions['controllerButton']) {
                  //If an admin index exists use it.
                  $action = $adminController ? $cakeAdmin . '_index' : 'index';
                  $url = array(
                      'controller' =&gt; $ctrlCamel,
                      'action' =&gt; $action,
                      'admin' =&gt; $adminController,
                  );
                  $menuItem = array(
                      'parent' =&gt; $menuOptions['parent'],
                      'id' =&gt; $ctrlCamel,
                      'title' =&gt; $ctrlHuman,
                      'url' =&gt; $url,
                      'weight' =&gt; 0
                  );
                  $this-&gt;rawMenus[] = $menuItem;
              }
          }
      }
  
  /**
   * Get the Controllers in the Application
   *
   * @access public
   * @return void
   */
      public function getControllers() {
          return App::objects('controller');
      }
  
  /**
   * filter out methods based on $menuOptions.
   * Removes private actions as well.
   *
   * @param array $methods  Array of methods to prepare
   * @param array $remove Array of additional Methods to remove, normally options on the controller.
   * @return array
   **/
      public function filterMethods($methods, $remove = array()) {
          if (!empty($remove)) {
              $remove = array_map('strtolower', $remove);
          }
          $exclusions = array_merge($this-&gt;excludedMethods, $remove);
          foreach ($methods as $k =&gt; $method) {
              $method = strtolower($method);
              if (strpos($method, '_', 0) === 0) {
                  unset($methods[$k]);
              }
              if (in_array($method, $exclusions)) {
                  unset($methods[$k]);
              }
          }
          return array_values($methods);
      }
  
  /**
   * Set the Options for the current Controller.
   *
   * @return mixed.  Array of options or false on total exclusion
   **/
      public function setOptions($controllerVars) {
          $cakeAdmin = Configure::read('Routing.admin');
          $menuOptions = isset($controllerVars['menuOptions']) ? $controllerVars['menuOptions'] : array();
  
          $exclude = array('view', 'edit', 'delete', $cakeAdmin . '_edit', 
              $cakeAdmin . '_delete', $cakeAdmin . '_edit', $cakeAdmin . '_view');
  
          $defaults = array(
              'exclude' =&gt; $exclude, 
              'alias' =&gt; array(), 
              'parent' =&gt; $this-&gt;defaultMenuParent, 
              'controllerButton' =&gt; true
          );
          $menuOptions = Set::merge($defaults, $menuOptions);
          if (in_array('*', (array)$menuOptions['exclude'])) {
              return false;
          }
          return $menuOptions;
      }
  
  /**
   * Creates the Exclusions for generating menus.
   *
   * @return void
   **/
      public function createExclusions() {
          $methods = array_merge(get_class_methods('Controller'), $this-&gt;excludeActions);
          $this-&gt;excludedMethods = array_map('strtolower', $methods);
      }
  /**
   * Add a Menu Item.
   * Allows manual Insertion into the menu system.
   * If Added after constructMenu()  It will not be shown
   *
   * @param string $parent
   * @param array $menu
   *      'Menu' Array
   *          'title' =&gt; name
   *          'url' =&gt; url array of menu, url strings are lame and won't work
   *          'key' =&gt; unique name of this menu for parenting purposes.
   *          'controller' =&gt; controller Name this action is from
   */
      public function addMenu($menu) {
          $defaults = array(
              'title' =&gt; null,
              'url' =&gt; null,
              'parent' =&gt; null,
              'id' =&gt; null,
              'weight' =&gt; 0,
          );
          $menu = array_merge($defaults, $menu);
          if (!$menu['id'] &amp;&amp; isset($menu['url'])) {
              $menu['id'] = $this-&gt;_createId($menu['url']);
          }
          if (!$menu['title'] &amp;&amp; isset($menu['url']['action'])) {
              $menu['title'] = Inflector::humanize($menu['url']['action']);
          }
          $this-&gt;rawMenus[] = $menu;
      }
  
  /**
   * BeforeRender Callback.
   *
   */
      public function beforeRender(Controller $controller) {
          $this-&gt;Controller-&gt;set('menu', $this-&gt;menu);
      }
  
  /**
   * Make a Unique Menu item key
   *
   * @param array $parts
   * @return string Unique key name
   */
      protected function _createId() {
          $parts = func_get_args();
          if (is_array($parts[0])) {
              $parts = $parts[0];
          }
          $key = Inflector::variable(implode('-', $parts));
          return $key;
      }
  
  /**
   * Recursive function to construct Menu
   *
   * @param unknown_type $menu
   * @param unknown_type $parentId
   */
      protected function _formatMenu($menu) {
          $out = $idMap = array();
          foreach ($menu as $item) {
              $item['children'] = array();
              $id = $item['id'];
              $parentId = $item['parent'];
              if (isset($idMap[$id]['children'])) {
                  $idMap[$id] = am($item, $idMap[$id]);
              } else {
                  $idMap[$id] = am($item, array('children' =&gt; array()));
              }
              if ($parentId) {
                  $idMap[$parentId]['children'][] =&amp; $idMap[$id];
              } else {
                  $out[] =&amp; $idMap[$id];
              }
          }
          usort($out, array(&amp;$this, '_sortMenu'));
          return $out;
      }
  
  /**
   * Sort the menu before returning it. Used with usort()
   *
   * @return int
   **/
      protected function _sortMenu($one, $two) {
          if ($one['weight'] == $two['weight']) {
              return 1;
          }
          return ($one['weight'] &lt; $two['weight']) ? -1 : 1;
      }
  /**
   * Merge the Cached menus with the Menus added in Controller::beforeFilter to ensure they are unique.
   *
   * @param array $cachedMenus
   * @return array Merged Menus
   */
      protected function _mergeMenuCache($cachedMenus) {
          $cacheCount = sizeOf($cachedMenus);
          $currentCount = sizeOf($this-&gt;rawMenus);
          $tmp = array();
          for ($i = 0; $i &lt; $currentCount; $i++) {
              $exist = false;
              $addedMenu = $this-&gt;rawMenus[$i];
              for ($j =0; $j &lt; $cacheCount; $j++) {
                  $cachedItem = $cachedMenus[$j];
                  if ($addedMenu['id'] == $cachedItem['id']) {
                      $exist = true;
                      break;
                  }
              }
              if ($exist) {
                  continue;
              }
              $tmp[] = $addedMenu;
          }
          if (!empty($tmp)) {
              $this-&gt;_rebuildMenus = true;
          }
          return array_merge($cachedMenus, $tmp);
      }
  
  }
  ?&gt;
  </code></pre>
  
  <p>Thank you in advance.</p>