Yii 1.1: yiismartmenu

Hide any menu item (cmenu) that user has no access permissions to [based on checkAcess()].
27 followers

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

  1. Put YiiSmartMenu.php in your application.components folder;
  2. 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;

Total 14 comments

#15483 report it
sidtj at 2013/11/14 05:16am
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.

#15481 report it
mostofa62 at 2013/11/14 05:01am
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

#9934 report it
JB Renard at 2012/09/23 06:01pm
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);
            }
}
#8641 report it
sidtj at 2012/06/15 03:52am
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
#8605 report it
mohamadaliakbari at 2012/06/14 04:16am
Integrate with other menu extensions?

Can I integrate this extension with emenu? yiismartmenu will check permissions and emenu will render it...

#6719 report it
sidtj at 2012/01/31 03:52pm
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.

#6703 report it
russellfeeed at 2012/01/30 10:09am
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;
    }
#6702 report it
russellfeeed at 2012/01/30 09:15am
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
#6677 report it
sidtj at 2012/01/27 01:20pm
RE: Good work

Thank you, mhorrocks!

#6673 report it
Mike H at 2012/01/27 11:43am
Good work

This is a good extension, easy to implement and pretty effective.

#6671 report it
sidtj at 2012/01/27 10:22am
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.

#6665 report it
russellfeeed at 2012/01/27 05:10am
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

#6587 report it
sidtj at 2012/01/19 02:54pm
#dkrochmalny

Very happy that it was useful for you! Thanks for let me know.

#6586 report it
deez at 2012/01/19 01:41pm
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.

Leave a comment

Please to leave your comment.

Create extension