- General Information
- Some Vantages
- Examples
- Init Options
- Logs
- Installation
- Requirements
- Resources
- Change Log
General Information ¶
YiiSmartMenu auto checks permissions to define visibility of any menu item. It can be used with any authManager component since that one has configured to use the Yii::app()->user->checkAccess() authorization method.
YiiSmartMenu can work out-of-the-box with the Rights.
This is how YiiSmartMenu works: it iterates through the received items and turns its visibility true or false depending on result of checkAccess() function to that specific menu item. If you prefer, you can define the auth item name to be checked for yourself but if you don't, YiiSmartMenu will auto compound it by concatenating the module (whether any), the controller and the action defined in url
or submit
options of your menu's items.
Some Vantages ¶
- Out-of-the-box use. You just need to extract and change the widgets that are using CMenu to YiiSmartMenu;
- Less database queries: if a menu item is not accessible due to checkAccess() restrictions, none of its children are checked. In some menus it could save dozens of queries;
- You can use YiiSmartMenu in the main menu, in the operations menu or any other menu where you would use CMenu;
- Very customizable. You can define your own auth params and your own auth item name or you can define init options to customize how YiiSmartMenu will generate them. You can even define your own "visible" rule to your menu item and YiiSmartMenu will not touch it;
Examples ¶
It could be use, for example, in //layouts/main or //layouts/column2 menus:
$this->widget('application.components.YiiSmartMenu',array(
//No required init options
'partItemSeparator'=>'.',
'upperCaseFirstLetter'=>true,
//Same options used in CMenu
'items'=>array(
array(
'label'=>'Home Page',
'url'=>array('/site/index',
),
array(
'label'=>'Other Page',
'url'=>array('something/other'),
//optional, if not set, YSM will controll visibility;
'visible'=>{your own rule, YSM will not touch this},
//optional, params to be sent to checkAccess() function;
//if not set, YSM will use params from url/submit options
//or $_GET.
'authParams'=>array('myParam'=>'myValue', ...),
//optional, auth item name to be used in checkAccess() function;
//if not set, YSM will auto generate this.
'authItemName'=>'myAuthItemName',
),
...
),
...
In the first menu item above, YiiSmartMeny would use the property url
('/site/index') to generate and execute the following checkAccess() function:
Yii::app()->user->checkAccess("Site.Index", $_GET)';
Setting 'partItemSeparator'=>''
(empty string) makes YSM generate:
Yii::app()->user->checkAccess("SiteIndex", $_GET)';
And if you also has set 'upperCaseFirstLetter'=>false
YSM would generate:
Yii::app()->user->checkAccess("siteindex", $_GET)';
This way, the menu item 'Home Page' would only be visible if the current logged user had access privilegies to an auth item named 'Site.Index' (or 'siteindex' if init options was set like in the example). Note that $_GET is passed as $params to checkAccess() if you do not define 'authParams' option of your menu item or if the "url" or "submit" options has no additional params.
Init Options ¶
- partItemSeparator string defines which char(s) separator to use when concatenating to generate auth item name. Defaults to '.'(dot);
- upperCaseFirstLetter boolean if true, the module, controller and action will be lcfirst() before to be concatened to generate the auth item name. Defaults to true;
Logs ¶
If you have trace logs enabled you can check traces messages to view why each menu item was turned visible or not. The template of the showed trace messages is:
Item {MenuItemName} is [*not*] visible. You have [no] permissions to [ModuleW.]ControllerX.ActionY with params:
paramX=valX ...
Installation ¶
- Put YiiSmartMenu.php in your application.components folder;
- Change your widget menus from
$this->widget('zii.widgets.CMenu', ...
to$this->widget('application.components.YiiSmartMenu',...
(Please, see the examples above);
Requirements ¶
- PHP 5.3+ (Due to use of the function ucfirst());
- Tested with Yii 1.1.7. Should work ok with earlier versions;
Resources ¶
Change Log ¶
- Version 0.3.1
- Turn visible menu items having url="#" without linkOptions=>submit [sidtj]
Version 0.3.0
- Looks for url/submit additional params to send to checkAccess() if 'authParams' is not set. $_GET will only be sent if 'authParams' is not set and url/submit has no additional params;
- Improoved trace messages. Now they show what params are being sent to checkAccess();
- The code has been refactored;
Version 0.2.1
- Allow to set the new option 'params'=>array(...) in a menu item to be sent to checkAccess() function instead of the default var $_GET;
- Allow to set the new option 'authItemName'=>'CanDoX' in a menu item to be used in checkAccess() function instead of auto generate it based in 'url' or 'submit' options;
Version 0.1.0 First Version;
Awesome man thanks!
Thanks, this is just what the doctor ordered, installed the file, called it, works perfectly. Literally 2 minutes to get it working.
dkrochmalny
Very happy that it was useful for you!
Thanks for let me know.
Doesn't work if item doesn't have a URL?
Hi
RE: v0.3.0 YiiSMartMenu
My menu starts like this:
$this->widget('application.components.YiiSmartMenu',array( 'partItemSeparator'=>'.', 'upperCaseFirstLetter'=>true, 'items'=>array( array('label'=>'Home', 'url'=>array('/site/index')), // Main Level array('label'=>'Incoming', 'items'=> array( // Main Level array('label'=>'Scan Pallet', 'url'=>array('/scandevices') ), array('label'=>'Upload IMEI File', 'url'=>array('/uploadimeis') ), array('label'=>'Upload Sims File', 'url'=>array('/uploadiccids')), array('label'=>'Spot Check', 'url'=>array('/spotcheck') ), array('label'=>'Returned Handset', 'url'=>array('/returnimei/create')), )),
The incoming menu has child items, but no URL.
So I'm getting the error: "Undefined index: url" on line 95 of YiiSmartMenu.php:
085 protected function generateAuthItemNameFromItem($item){ 086 if(isset($item['authItemName'])) 087 return $item['authItemName']; 088 else 089 { 090 if(isset($item['url']) && is_array($item['url'])) 091 $url=$item['url']; 092 elseif(isset($item['linkOptions']['submit']) && is_array($item['linkOptions']['submit'])) 093 $url=$item['linkOptions']['submit']; 094 else **095 return $item['url'];** 096 097 $templateParts=array();
Any ideas what I'm doing wrong?
Thanks in advance
Russell
RE: Doesn't work if item doesn't have a URL?
Yeah, it seems a bug. Thanks for let me know. Will be fixed in the next version.
Workaround: set 'url'=>'#'.
Thank you.
Good work
This is a good extension, easy to implement and pretty effective.
RE: Good work
Thank you, mhorrocks!
Thanks
Thanks sidtj
Get closer to having this working using the 'url'=>'#' workaround.
Additonally I'm close to having this working with the MbMenu extension (http://www.yiiframework.com/extension/mbmenu/ "(MbMenu)")...in YiiSmartMenu.php just extend as follows:
class YiiSmartMenu extends MbMenu
Why is it prepending Site. to the authName?
Hi
I had to change YiiSmartMenu.php as follows (code between RCH 20120130 comments)
protected function filterItems(array $items){ foreach($items as $pos=>$item) { if(!isset($item['visible'])) { $authItemName=$this->generateAuthItemNameFromItem($item); $params=$this->compoundParams($item); // RCH 20120130 $parts = explode('.',$authItemName); $authItemNameWithoutFirst = implode('.', array_slice($parts,1)); $allowedAccess = Yii::app()->user->checkAccess($authItemName, $params) || Yii::app()->user->checkAccess($authItemName.'.*', $params) || Yii::app()->user->checkAccess($authItemNameWithoutFirst, $params) || Yii::app()->user->checkAccess($authItemNameWithoutFirst.'.*', $params); // RCH 20120130 $item['visible'] = $allowedAccess; $this->trace($item, $authItemName, $params, $allowedAccess); } /** * If current item is visible and has sub items, loops recursively * on them. */ if(isset($item['items']) && $item['visible']) $item['items']=$this->filterItems($item['items']); $items[$pos]=$item; } return $items; }
RE: Why is it prepending Site. to the authName?
When your link has no controller (eg. 'url'=>'actionX'), ysm uses the current controller. Probably it was your case.
By the way, thanks for sharing this:
Yii::app()->user->checkAccess($authItemNameWithoutFirst.'.*', $params);
I had removed it when used YSM in a specific project cause I was having problemas with 'ControllerX.*' of Rights. I will put it again in the next version.
Integrate with other menu extensions?
Can I integrate this extension with emenu? yiismartmenu will check permissions and emenu will render it...
RE: Integrate with other menu extensions?
I dont know emenu, but I could see it extends CMenu. So, you could just change emenu class making it to extend YiiSmartMenu.
Change this...
class EMenu extends CMenu
to...
class EMenu extends YiiSmartMenu
Rights full compatibility workaround
Hello Sidtj,
Thank you for this extention! It has been a great start for what I need.
I'm working with Rights too, and I've been bothered by the fact that when a user has access to, let's say, User.Admin., the visibility check for User.Admin.View doesn't work.
And if the link is something like User/Admin and going for the default controller, then I wanted it to check User.Admin.
So here is it, very dirty and quick. Basically I create the .* item and check both.
What do you think? Any idea of how to make it better, if not irrelevant?
protected function filterItems(array $items){ foreach($items as $pos=>$item) { if(!isset($item['visible'])) { $allowedAccess = false; // Made it a bit dirty and quick $authItem=array(); $atemp = $this->generateAuthItemNameFromItem($item); $authItem = is_array($atemp) ? $atemp : array($atemp); $params=$this->compoundParams($item); if ($authItem[0] == '#') $allowedAccess = true; else foreach ($authItem as $i) { if (Yii::app()->user->checkAccess($i, $params)) { $allowedAccess = true; break; } $this->trace($item, $i, $params, $allowedAccess); } $item['visible'] = $allowedAccess; } /** * If current item is visible and has sub items, loops recursively * on them. */ if(isset($item['items']) && $item['visible']) $item['items']=$this->filterItems($item['items']); $items[$pos]=$item; } return $items; } protected function generateAuthItemNameFromItem($item){ [...] $templateParts['{controller}']=$controller; $templateParts['{action}']=$authItemName; } // Add the "parent" and the "childs" items to be checked if (isset($templateParts['{action}'])) { $templateParts2 = $templateParts3 = $templateParts; $templateParts2['{action}'] = '*'; $templateParts3['{joker}'] = '*'; return array( implode($this->partItemSeparator, $templateParts), implode($this->partItemSeparator, $templateParts2), implode($this->partItemSeparator, $templateParts3), ); } else { return implode($this->partItemSeparator, $templateParts); } }
Nice ,But i got its not default show ,when log in then show
hello,
i use this extension ,work nice
but facing problem in main layout,
means
$this->widget('application.components.YiiSmartMenu',array( 'partItemSeparator'=>'', 'upperCaseFirstLetter'=>false, 'items'=>array( array('label'=>'Home', 'url'=>array('/site/index')), array('label'=>'About', 'url'=>array('/site/page', 'view'=>'about')), array('label'=>'Contact', 'url'=>array('/site/contact')), array('label'=>'Rights', 'url'=>array('/rights')), array('url'=>Yii::app()->getModule('user')->profileUrl, 'label'=>Yii::app()->getModule('user')->t("Profile")), ), ) );
the menu is not showing without logout.
whats the problem is this
RE: mostofa62
Hi,
The docs says:
If you also has set 'upperCaseFirstLetter'=>false YSM would generate:
Yii::app()->user->checkAccess("siteindex", $_GET)';
So, check if you have an authItem named "siteindex" and if the logged user has access to that.
Hope it helps you.
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.