Acl Menu Component

By now you’ve got an awesome Acl and Auth controlled app running. However, making navigation menus is a pain with dynamic, and variable permissions. Outside of making menu elements for each type of Aro and including them in your layout, there currently aren’t many options (at least none that I’m aware of). I was faced with this exact problem a while back, and couldn’t find a suitable solution, so I made one. I called it the MenuComponent

What does it do?

This menu component will scan through your application, and generate a list of menu-able actions to be used in menus. When a request is made for a MenuComponent using page, a nested menu array will be created based on the current User’s access permissions. This menu is set to the View and available in your layouts / views. To top it all off, this is done automagically!

What doesn’t it do?

It doesn’t kill the performance of your site for one. It uses a lot of caching to help minimize the hit your application will endure when using dynamically generated menus. It also doesn’t generate menu items for actions that involve id’s, or information from the session. These can be added into the menu’s structure, but it is not done automatically. It also doesn’t make any HTML, how you render these menus is totally up to you.

How do I use it?

At its most basic use, download the MenuComponent and test case and add them to your app. Include the MenuComponent in your AppController or wherever you want to use it and you’re ready to go.

Show Plain Text
  1. var $components = array('Acl', 'Auth', 'Menu');

It can obviously get far more complicated. MenuComponent has a number of settings that can be set using “alternate component declarations”/posts/view/cakephp-easter-eggs . Any of the class variables can be set in this manner. Additional configuration related to specific controllers is done in the respective controller. This seems a bit odd, but I chose to do it this way, as it keeps each controller’s contribution to the menu inside that controller. This is in contrast to setting every option for every controller in AppController or wherever you are including the MenuComponent. As this would end up bloated and ugly very quickly.

Using controller options for the MenuComponent

Controller options for the MenuComponent control how the menu is generated for that controller, and nothing else. Each controller’s menuOptions are separate and affect nothing outside of its own actions. $meunOptions provide facilities for aliasing actions, excluding actions and setting the parent menu item of the controllers actions.

Renaming action titles in the menus

Often you don’t want the action name to be the display title in your menu. Setting an alias lets you control the menu’s title.

Show Plain Text
  1. var $menuOptions = array(
  2.     'alias' => array(
  3.         'admin_index' => 'View all Posts',
  4.     ),
  5. );

You basically map the action name to the title text you want to display. Pretty straightforward, right now due to the way the caching is done, you would have to do any i18n work in the views.

Excluding actions from the menu

Excluding actions is done by creating an array of non-private actions, you want to exclude from the menus. You don’t need to specify the following either ‘view’, ‘edit’, ‘delete’, ‘admin_edit’, ‘admin_delete’, ‘admin_edit’, or ‘admin_view’. If you want to change the global list of excluded actions modify MenuComponent::excludeActions. You can also set exclude to ‘‘. Setting exclude to ‘‘ will remove an entire controller, and all of its actions from the generated menus.

Show Plain Text
  1. var $menuOptions = array(
  2.     'exclude' => array('secretStuff');
  3. );
  4. //Or
  5. var $menuOptions = array(
  6.     'exclude' => array('*');
  7. );

Changing the parent element

You can also set the parent menu element for all actions in a controller. This lets you nest a set of controller/actions under an artificial or generated menu.

Show Plain Text
  1. var $menuOptions = array(
  2.     'parent' => 'my-menu-item'
  3. );

This would place all the menus generated for this controller under the menu element with an id of ‘my-menu-item’. Using parent gives you a powerful, and easy way to create a hierarchy in your application’s menu without affecting the actual structure of the application. Menu ids are generated automatically and take the form of $controllerName-$actionName.

Removing the controller element

There are times when you don’t want a menu element for the controller. Normally these occur when using manually added menu elements or specifying $menuOptions['parent'] in these circumstances you can:

Show Plain Text
  1. var $menuOptions = array(
  2.     'controllerButton' => false,
  3. );

This will remove the controller button, and all the actions will become children of whatever $menuOptions['parent'] is set to.

Manually creating menu elements

In addition to automagic generation of menu elements you can manually create menu elements. I’ve used this when, I want to promote a single action to somewhere it normally wouldn’t be able to get to in the menu structure. Or to create menu elements for actions that need an id parameter to work. Creating menu elements looks a bit like this.

Show Plain Text
  1. $this->Menu->addMenu(array(
  2.     'title' => 'My custom menu',
  3.     'parent' => 'my-menu-tree',
  4.     'url' => array(
  5.         'controller' => 'posts',
  6.         'action' => 'new_action',
  7.         'id' => 25
  8.     ),
  9. ));

By using addMenu() you can create menu elements for any path or url on your application. However, the big catch is that all your addMenu() calls must occur before the menus are constructed. This normally occurs automatcally in startup() but can be manually controlled. See the doc blocks for more information regarding that. It is also important to note that if two menu elements exist for the same id, and a user has access to both only the first will show up in the final menu.

Well I hope you find this Component useful and I’d love to hear on how to improve it. You can download the menu component and its related test from my gitHub repository

Comments

hey, looks cool – nice you included the manual option…I always end up doign this whenever I try and do a dyanmic menu, and it used to begin with an extra if else, then a case then … serenity etc :)

anonymous user on 9/15/08

Hi,
it’s very usefull component, but for me it’s lack of some features. I wrote some mods for ex. possibility to set up custom controller’s title by setting $menuOptions[‘ctrlTitle’], because generic just takes ctrl’s name. I think it also needs automagic support for acl public functions to avoid warning if there is no aco’s for them while they are in allowedActions.

anonymous user on 10/28/08

When it comes about comment from your code “no active session, no menu can be generated”, i think support for for “no session menu” would be not bad, like i said last time we can check up just Auth’s allowedActions and generate menu for public functions.

anonymous user on 10/28/08

Natan: If check the gitHub, I’ve been working on improvements to the component as I come across the need for them. If you’ve added features or fixed things and would like to share send me an email through my contact form, or fork the code on gitHub, and make a pull request.

mark story on 10/29/08

Hei! I find this component very useful and promising! Good job! As a suggestion I would suggest to add someway to order the result of array, don’t know what ways would be the best, supose each item would need to have order number ( spacing of 10 ) and then it would be possible to manually tell menu->item(value, ordre). Creating database table would allow more possibilities for creating interface and adding translations more easily.

Tomppa on 1/7/09

Tomppa: If you check out a recent version from Github you’ll see that I’ve added a ‘‘weight” to the menu items. This allows manually added elements to be weighted. There is no ability to set weights for auto-generated menu items though. Perhaps I can add that in the future.

mark story on 1/8/09

Many thanks for the ACL code and the Menu Component. I have a couple of queries regarding the menu component.
1.The load cache routine doesn’t return the current users cache, but does return an empty array therefore successfully enters the if statement. As the writeCache sets the cachekey as User?_menu_storage, shouldn’t load cache also look for this?
2.A minor point I dont use the standard admin_index for the admin pages, as the generateRawMenu, sets $cakeAdmin = Configure::read(‘Routing.admin’), see line 300, I have replaced ‘admin_index’ with $cakeAdmin.’index’. These also appear on Line 87 & 360. This confused me for a while, as I searched for any references to admin? in my controllers.

Jonathan Jones on 1/26/09

Jonathan Jones: Thanks for pointing out those hardcoded values. I’ve updated them now.

mark story on 1/27/09

This component is really good. I’m just a n00b with Cake so I might be wrong but I think MenuComponent doesn’t support automagic when component’s name contains underscope (ex. good_pies). Is that hard to code ?

OstReach on 2/25/09

I deleted “if ($this->Acl->check($aro, $aco))” in constructMenu() and it kinda works for me.

OstReach on 2/26/09

Deleting the Acl->check() basically neuters the Acl controlled aspect of the menu. Which was the primary feature of this menu component. As for supporting underscores, its not hard, but wasn’t part of the original design that I built the component for. Controller names in CakePHP by convention don’t have underscores in them, so its not really necessary.

mark story on 2/26/09

Remove $aco = Inflector::underscore($item[‘url’][‘controller’]);

Add
$aco = $item[‘url’][‘controller’];

instead to use controller names with underscopes

OstReach on 2/26/09

Great Component. One thing I noticed that if I don’t have an index action set up for a controller, it does not render the actions for that controller at all.
Is it me or any one else noticed this too ? – Anyhow, I’d post a fix here I get about doing it as for now I’m just adding / index action in ACOs and assigning permissions.

SayB on 3/24/09

To add weight to your menu:

1. Add the following code after the “$ctrlHuman = …” line inside the generateRawMenus function:

if (empty($menuOptions[‘weight’]) || !isset($menuOptions[‘weight’]) || !is_numeric($menuOptions[‘weight’])) { $ctrlWeight = 999;
} else { $ctrlWeight = $menuOptions[‘weight’];
}

2. Replace the line “‘weight’ => 0” with “‘weight’ => $ctrlWeight” in the generateRawMenus function

Now you can add the weight option inside the $menuOptions

Riaan on 7/1/09

Riaan: You could always fork the code on github and apply the patch too :)

mark story on 7/1/09

when cache in out of date i get this warning (debug set at 2):

Warning (512): DbAcl::check() – Failed ARO/ACO node lookup in permissions check. Node references:
Aro: Array
( [User] => Array ( [id] => 1 [username] => pollution [group_id] => 1 [active] => 1 [created] => 0000-00-00 00:00:00 [modified] => 2009-09-09 11:30:47 )

)

Aco: controllers/pages/display [CORE/cake/libs/controller/components/acl.php, line 239]

same thing for all the other action out aco table

With updated cache all works!

congratulations for this blog … the best place to improve my cake skills!

pollution on 9/16/09

When I loaded this I had an error on line 201 with $aro[key($aro)][‘id’]. My user table’s primaryKey is user_id, so I updated it to match.

Now I load the page correctly, but I don’t see any menu. I’m not sure what I’m missing.

Ryan on 9/29/09

Obviously (now) I was missing that the menu options are now stored in the view array.

Has anyone written a helper to display the menu?

Ryan on 9/29/09

I like the idea and got it to work. However my ACOS table contains records with controller and action names. Should I just query the ACOS table instead to generate raw menus?

Doctor Who on 10/6/09

I can’t get it to work. Could you explain using the example of “Acl and Auth controlled app” from previous posts where and how create Menu and how display it?
Thank’s

noob on 10/16/09

Comments are not open at this time.