beginner
beginner

Reputation: 2032

How to build dynamic menu in YII2

I want to have a dynamic menu base on the user's permission. For example there is a menu for the admin account and for the basic account.

I am currently doing it like the following.

          <?php

            if((yii::$app->user->can('admin')) || (yii::$app->user->can('blah')) || (yii::$app->user->can('blahblah'))) ..and so on
            {
                //..print the menu 
            }

            //another condition for menu here.
            ?>

Is there a shortcut in that if condition? say for example I declare an array with the permissions inside then i will just use in_array method to check if the user has the right permission to see the links.

Upvotes: 1

Views: 6096

Answers (4)

Davron Achilov
Davron Achilov

Reputation: 621

I use a simple over it.
DB: menu and childmenu
Join: menu_id (childmenu) to id (menu)

``

<ul>
      $mainmenu = \common\models\Menu::find()->all();
                foreach ($mainmenu as $data)
      {
          echo "<li><a  href='$data->url'>$data->name_en</a><ul>";
          $sub_menu = \common\models\Childmenu::find()->where(['menu_id'=>$data->id])->all();
          foreach ($sub_menu as $sub_value)
          {
              echo "<li><a  href='$sub_value->url'>$sub_value->name_en</a></li>";
          }
          echo "</ul></li>";
      }
?></ul>

```

Upvotes: -1

Rafal
Rafal

Reputation: 573

I sugested

composer requiere pceuropa/yii2-menu dev-master

Upvotes: 0

Rahul Pawar
Rahul Pawar

Reputation: 1036

Use this: dynamic 2 level menuitem yii2

$menuItems = array();
                $pages_record = Pages::find()->all();
                $i = 0;
                foreach ($pages_record as $data) 
                {
                    if($data->select_parent == 0)
                    {
                        $path = \Yii::$app->request->BaseUrl."/index.php?r=".$data->seo_url;
                        $menuItems[$i] = array('label' => $data->page_title, 'url' => $path);
                        $pages_sub_rec = Pages::findAll(['select_parent' => $data['pid']]);
                        foreach ($pages_sub_rec as $sub_value)
                        {
                            $menuItems[$i]['items'][] = array('label' => $sub_value->page_title, 'url' => '#');
                        }
                        $i++;
                    }
                }

Upvotes: 0

arogachev
arogachev

Reputation: 33538

Yii2 templates ships with Bootstrap 3, and Nav widget is most often used for building menus.

Here is an example of declaring visibility of menu items dynamically:

echo Nav::widget([
    'options' => ['class' => 'navbar-nav navbar-right'],
    'items' => [
        [
            'label' => 'Users',
            'url' => ['/users'],
            'visible' => Yii::$app->user->can('users.manage'),
        ],

        ...
    ],
]);

And you don't need to check every possible role, you need to check permission for accessing / managing specific section.

For example in the provided example every user with assigned permission users.manage can view that menu item. So, add permssion and assign it to desired user roles.

Don't forget to check permissions in controller too, hiding it in layout doesn't make sense if the link is still accessible.

Sometimes you have the nested submenu with grouped items and you want to show it conditionally too, depending on the rights. You can do it like that:

$menuItems = [
    [
        'label' => 'Section',
        'items' => [
            [
                'label' => 'Subsection One',
                'url' => ['/sub-section-one/index']],
                'visible' => Yii::$app->user->can('sub-section-one.manage'),
            ],
            [
                'label' => 'Subsection One',
                'url' => ['/sub-section-one/index']],
                'visible' => Yii::$app->user->can('sub-section-two.manage'),
            ],
        ],
    ],
],

echo Nav::widget([
    'options' => ['class' => 'navbar-nav navbar-right'],
    'items' => $menuItems,
]);

The problem is if user didn't have permission for managing both subsections, the root section used for grouping is still visible. We obviously can add

'visible' => Yii::$app->user->can('sub-section-one.manage') || Yii::$app->user->can('sub-section-two.manage'),

But in case of RBAC authManager being DbManager and turned off caching additional queries will be performed and we kind of duplicating code.

So you can do something like this:

$sectionItems => [
    [
        'label' => 'Subsection One',
        'url' => ['/sub-section-one/index']],
        'visible' => Yii::$app->user->can('sub-section-one.manage'),
    ],
    [
        'label' => 'Subsection One',
        'url' => ['/sub-section-one/index']],
        'visible' => Yii::$app->user->can('sub-section-two.manage'),
    ],
],

$isSectionVisible = false;

foreach ($sectionItems as $sectionItem) {
    if ($sectionItem['visible']) {
        $isSectionVisible = true;

        break;
    }
}

And then:

$menuItems = [
    [
        'label' => 'Section',
        'visible' => $isSectionVisible,
        'items' => $sectionItems,
    ],
],

echo Nav::widget([
    'options' => ['class' => 'navbar-nav navbar-right'],
    'items' => $menuItems,
]);

Or you can extend Nav widget and implement this logic.

Also take a look at advanced template layout, there is also an example for building dynamic menu:

$menuItems = [
    ['label' => 'Home', 'url' => ['/site/index']],
];
if (Yii::$app->user->isGuest) {
    $menuItems[] = ['label' => 'Login', 'url' => ['/site/login']];
} else {
    $menuItems[] = '<li>'
        . Html::beginForm(['/site/logout'], 'post')
        . Html::submitButton(
            'Logout (' . Yii::$app->user->identity->username . ')',
            ['class' => 'btn btn-link']
        )
        . Html::endForm()
        . '</li>';
}
echo Nav::widget([
    'options' => ['class' => 'navbar-nav navbar-right'],
    'items' => $menuItems,
]);

Upvotes: 3

Related Questions