Add dropdown of authorized subwikis in header.navbar - Customize Flamingo Theme

Hi XWiki Community,

I am Luc, responsible of my company wiki (with XWiki SAS Pro Contract, instance in version 15.10.8) and IT project manager.

We are using some subwikis to separate the content in independant spaces. Each subwiki uses the customized global theme (same header.navbar, same company logo…), ensuring a style coherence but we want to facilitate the navigation between subwikis.

We want to have a dropdown (next to the company logo) with the name of current subwiki and a selector to move to another authorized subwiki. How to do it ?

Here is a mockup of the rendering :

Here is a snippet of the associated HTML code :

<!-- Company logo before ... -->
<div id="xwikimainmenu">
  <ul class='nav navbar-nav navbar-left'>
    <li class="dropdown subwiki-navigation" data-placement="bottom" data-trigger="manual" id="tmSubwikiNavigation">
      <a class="icon-navbar dropdown-toggle" data-toggle="dropdown" role="button" title="Sous-wiki">
        <!-- Name of current subwiki -->
        Main Wiki <span class="fa fa-caret-down" aria-hidden="true" style="margin-left: 0.5rem;"></span>
      </a>
      <ul class="dropdown-menu">
        <!-- List of authorized subwikis -->
        <li><a href="/xwiki/wiki/Main/">Main Wiki</a></li>
        <li><a href="/xwiki/wiki/SubwikiA/">Subwiki A</a></li>
        <li><a href="/xwiki/wiki/SubwikiB/">Subwiki B</a></li>
      </ul>
    </li>
  </ul>
<!-- ... .navbar-right after ... -->
</div>

Of course, only available and authorized (for the current user) subwikis should be listed.

Bonus : It would be perfect if the dropdown is disabled in case of single available wiki.

We are using the Flamingo Theme and I identified the file menus_macros.vm but I do not know if it is recommanded/possible to change it and to add some (velocity ?) code.

Thank you in advance for your precious help!

Best Regards,
Luc

Hi!

You could use a menu like this:

{{velocity}}
#set ($currentWiki = $services.wiki.getById($services.wiki.getCurrentWikiId()))
#set ($wikis = $services.wiki.getAll())
* $currentWiki.prettyName
#foreach ($wiki in $wikis)
  #if ($services.security.authorization.hasAccess('view', $wiki))
** [[$wiki.prettyName>>$wiki.mainPageReference]]
  #end
#end
{{/velocity}}

If you really want the selector to be at the right of the logo, you can create template overrides from the admin section: https://extensions.xwiki.org/xwiki/bin/view/Extension/Skin%20Application#HHowtooverrideatemplate
If you go this way, you might want to edit companylogo.vm instead, which should be easier since it doesn’t change often.

Hope this helps!

1 Like

Hi @pjeanjean,

A huge thanks for your advices, your post was very usefull and I am close to the target!

I managed to implement a modified menus_macros and adapt the code you provided.
Here is my new file:

## ---------------------------------------------------------------------------
## See the NOTICE file distributed with this work for additional
## information regarding copyright ownership.
##
## This is free software; you can redistribute it and/or modify it
## under the terms of the GNU Lesser General Public License as
## published by the Free Software Foundation; either version 2.1 of
## the License, or (at your option) any later version.
##
## This software is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
## Lesser General Public License for more details.
##
## You should have received a copy of the GNU Lesser General Public
## License along with this software; if not, write to the Free
## Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
## 02110-1301 USA, or see the FSF site: http://www.fsf.org.
## ---------------------------------------------------------------------------
### Ensure this template is only called once (for performance reasons), since it's called from several places
#if ($hasMenuMacrosBeenCalled)
  #break
#else
  #set ($hasMenuMacrosBeenCalled = true)
#end
###
###    GLOBALS
###
#set ($isAdminAppInstalled = $xwiki.exists($services.model.createDocumentReference('', 'XWiki', 'AdminSheet')))
#set ($currentWiki = $services.wiki.getById($services.wiki.getCurrentWikiId()))
#set ($wikis = $services.wiki.getAll())
#set ($nbAuthorizedWikis = 0)
#foreach ($wiki in $wikis)
  #if ($services.security.authorization.hasAccess('view', $wiki))
    #set ($nbAuthorizedWikis = $nbAuthorizedWikis + 1)
  #end
#end
###
###    Toplevel Menu
###
#macro(xwikitopmenustart)
  ## Set as an HTML header for accessibility best practices.
  <header class="navbar navbar-default actionmenu"
  aria-label="$escapetool.xml($services.localization.render('core.menu.navbar.label'))">
    <div class="container-fluid">
      ## Brand and toggle get grouped for better mobile display
      <div class="navbar-header">
        #if (!$displayPageHeader)
          #template("companylogo.vm")
        #end
      </div>
      ## Collect the nav links, forms, and other content for toggling
      <div id="xwikimainmenu">
#end

###
###    Toplevel Menu
###
#macro(xwikitopmenuend)
      </div>## /.navbar-collapse
    </div> ## / container-fluid
  </header>
#end

###
###    Toplevel Left Menu
###
#macro(xwikitopmenuleftstart)
  <ul class='nav navbar-nav navbar-left'>
  #if ($nbAuthorizedWikis <= 1)
    <li class="subwiki-navigation">
      <a class="icon-navbar" role="button" id="tmSubwikiNavigation" href="$xwiki.getURL($currentWiki.mainPageReference)">
        $currentWiki.prettyName
      </a>
    </li>
  #else
    <li class="dropdown subwiki-navigation" data-placement="bottom" data-trigger="manual" id="tmSubwikiNavigation">
      <a class="icon-navbar dropdown-toggle" data-toggle="dropdown" role="button" title="Changer de wiki">
        $currentWiki.prettyName
        <span class="fa fa-caret-down" aria-hidden="true" style="margin-left: 0.5rem;"></span>
      </a>
      <ul class="dropdown-menu">
    #foreach ($wiki in $wikis)
      #if ($services.security.authorization.hasAccess('view', $wiki))
        #if ($wiki.prettyName == $currentWiki.prettyName)
        <li class="active"><a href="$xwiki.getURL($wiki.mainPageReference)">$wiki.prettyName</a></li>
        #else
        <li><a href="$xwiki.getURL($wiki.mainPageReference)">$wiki.prettyName</a></li>
        #end
      #end
    #end
      </ul>
    </li>
  #end
#end

###
###    Toplevel Left Menu
###
#macro(xwikitopmenuleftend)
  </ul>
#end

###
###    Toplevel Right Menu
###
#macro(xwikitopmenurightstart)
  <ul class="nav navbar-nav navbar-right">
#end

###
###    Toplevel Right Menu
###
#macro(xwikitopmenurightend)
  </ul>
#end

###
###    Toplevel Menu entry with subitems
###
### Generate a menu entry and start the subitem section
###
### @param actionurl The URL this entry points to
### @param linktext The menu item text
###
#macro(xwikitopmenuentrystart $actionurl $linktext $id $class $icon $iconImg)
  #set($sep = '__SEPARATOR__')
  #set($linkName = $linktext)
  #set($linkTitle = $linktext) 
  #if ($linktext.contains($sep))
    #set($linkWords = $linktext.split($sep, 2))
    #set($linkName = $linkWords.get(1))
    #set($linkTitle = "${linkWords.get(0)}: ${linkWords.get(1)}")
  #end
  <dd class="dropdown dropdown-split text-left" #if(!$stringtool.isBlank($id))id="$id"#end>
    ## The menu label. On large screens (tablet and desktop) the label is a link that triggers the default menu action
    ## (e.g. navigation) and the menu is opened using a separated caret. On extra small screens (phones) the label opens
    ## the menu and the default menu action is the first item in the menu.
    <a href="$actionurl" class="dropdown-split-left dropdown-toggle $!class" data-toggle="dropdown" title="$linkTitle">
      ## This caret is displayed only on extra small screens to indicate that the label toggles the menu.
      #glyphicon($icon)$!{iconImg} $linkName <b class="caret"></b>
    ## Don't leave any space between the anchors because they have display:inline-block on large screens.
    </a><a href="#" class="dropdown-split-right dropdown-toggle hidden-xs $!class" data-toggle="dropdown">
      ## This caret is used to toggle the menu on large screens (tablet and desktop).
      <b class="caret"></b>
    </a>
    <ul class="dropdown-menu">
#end

###
###    Toplevel Menu entry without subitems
###
### Generate a menu entry withut subitems
###
### @param actionurl The URL this entry points to
### @param linktext The menu item text
###
#macro(xwikitopmenuentry $actionurl $linktext $id $class $icon $extraAttributes)
  #submenuitem($actionurl $linktext $id $class $icon $extraAttributes)
#end

###
###    Menu subitem entry
###
### Generate a submenu entry
###
### @param actionurl The URL this entry points to
### @param linktext The menu item text
###
#macro(submenuitem $actionurl $linktext $linkid $class $icon $extraAttributes)
  #set($sep = '__SEPARATOR__')
  #set($linkName = $linktext)
  #set($linkTitle = $linktext) 
  #if ($linktext.contains($sep))
    #set($linkWords = $linktext.split($sep))
    #set($linkName = $linkWords.get(1))
    #set($linkTitle = "${linkWords.get(0)}: ${linkWords.get(1)}")
  #end
  <li #if("$!class" != '')class="$!class"#end>
    <a href="$actionurl" #if(!$stringtool.isBlank($linkid))id="$linkid"#end title="$escapetool.xml($linkTitle)"
      $!extraAttributes>$services.icon.renderHTML($icon) $escapetool.xml($linkName)</a>
  </li>
#end

###
###    Menu disabled subitem entry
###
### Generate a disabled submenu entry
###
### @param text The menu item text
###
#macro(submenuitemdisabled $text)
<span class="submenuitem disable">$text</span>
#end

###
###    Toplevel Menu entry separator
###
#macro(xwikitopmenuseparator)
  <li class="divider" role="separator"></li>
#end

###
###    Menu submenu separator
###
#macro(submenuseparator)
  <li class="divider" role="separator"></li>
#end

###
### Action menu separator
###
#macro(xwikiactionmenuseparator)
    </ul>
  </dd>
  <dd>
    <ul>
#end

###
###    Toplevel Menu entry end
###
### End the subitem section
###
#macro(xwikitopmenuentryend)
    </ul>
  </li>
#end

#macro(xwikibutton $actionurl $linktext $id $class $icon)
  <a href="$actionurl" class="btn $!class" role="button" #if(!$stringtool.isBlank($id))id="$id"#end>#glyphicon($icon) $linktext</a>
#end

#macro(xwikibuttonentrystart $actionurl $linktext $id $class $icon)
  <div class="btn-group text-left" #if(!$stringtool.isBlank($id))id="$id"#end>
    <button type="button" class="btn $!class dropdown-toggle" data-toggle="dropdown">#glyphicon($icon) $linktext <span class="caret"></span>
    </button>
    <ul class="dropdown-menu" role="menu">
#end

#macro(xwikisplitbuttonentrystart $actionurl $linktext $id $class $icon)
  <div class="btn-group text-left" #if(!$stringtool.isBlank($id))id="$id"#end>
    <a href="$actionurl" class="btn $!class">#glyphicon($icon) $linktext</a>
    <button type="button" class="btn $!class dropdown-toggle" data-toggle="dropdown">
      <span class="caret"></span>
      <span class="sr-only">Toggle Dropdown</span>
    </button>
    <ul class="dropdown-menu" role="menu">
#end

#macro(xwikibuttonentryend $actionurl $linktext $id $class $icon)
    </ul>
  </div>
#end

#**
 * Display extension points only if the author of the UIX has the admin right on the wiki.
 *
 * @param $name name of the extension points to display
 * @since 7.3RC1
 *#
#macro(displaySimpleSecureUIX $name)
  #foreach($uix in $services.uix.getExtensions($name, {'sortByParameter' : 'order'}))
    #if ($services.security.authorization.hasAccess('admin', $uix.authorReference, $services.wiki.currentWikiDescriptor.reference))
      $services.rendering.render($uix.execute(), 'html/5.0')
    #end
  #end
#end

But I am now facing a problem: all users have the list of subwikis even they don’t have access to the target subwiki.
I tried these alternatives without success:

$services.security.authorization.hasAccess('view', $wiki)
$services.security.authorization.hasAccess('register', $wiki)
$services.security.authorization.hasAccess('view', $wiki.mainPageReference)

It returns always true for all users…

I register users to subwiki manually and deny all view rights to others users.
How to check the user membership for every target wiki.

Do you have an idea?

Best Regards,
Luc

Hi @pjeanjean,

Thank you for your help.
I finally managed with this check (a cache problem, why it did not work initially?):

$services.security.authorization.hasAccess('view', $wiki.mainPageReference)

Here is my optimized code:

## ---------------------------------------------------------------------------
## See the NOTICE file distributed with this work for additional
## information regarding copyright ownership.
##
## This is free software; you can redistribute it and/or modify it
## under the terms of the GNU Lesser General Public License as
## published by the Free Software Foundation; either version 2.1 of
## the License, or (at your option) any later version.
##
## This software is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
## Lesser General Public License for more details.
##
## You should have received a copy of the GNU Lesser General Public
## License along with this software; if not, write to the Free
## Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
## 02110-1301 USA, or see the FSF site: http://www.fsf.org.
## ---------------------------------------------------------------------------
### Ensure this template is only called once (for performance reasons), since it's called from several places
#if ($hasMenuMacrosBeenCalled)
  #break
#else
  #set ($hasMenuMacrosBeenCalled = true)
#end
###
###    GLOBALS
###
#set ($isAdminAppInstalled = $xwiki.exists($services.model.createDocumentReference('', 'XWiki', 'AdminSheet')))
#set ($currentWiki = $services.wiki.getById($services.wiki.getCurrentWikiId()))
#set ($authorizedWikis = [])
#foreach ($wiki in $services.wiki.getAll())
  #if ($services.security.authorization.hasAccess('view', $wiki.mainPageReference))
    #set ($mybool = $authorizedWikis.add($wiki))
  #end
#end
###
###    Toplevel Menu
###
#macro(xwikitopmenustart)
  ## Set as an HTML header for accessibility best practices.
  <header class="navbar navbar-default actionmenu"
  aria-label="$escapetool.xml($services.localization.render('core.menu.navbar.label'))">
    <div class="container-fluid">
      ## Brand and toggle get grouped for better mobile display
      <div class="navbar-header">
        #if (!$displayPageHeader)
          #template("companylogo.vm")
        #end
      </div>
      ## Collect the nav links, forms, and other content for toggling
      <div id="xwikimainmenu">
#end

###
###    Toplevel Menu
###
#macro(xwikitopmenuend)
      </div>## /.navbar-collapse
    </div> ## / container-fluid
  </header>
#end

###
###    Toplevel Left Menu
###
#macro(xwikitopmenuleftstart)
  <ul class='nav navbar-nav navbar-left'>
  #if ($authorizedWikis.size() <= 1)
    <li class="subwiki-navigation">
      <a class="icon-navbar" role="button" href="$xwiki.getURL($currentWiki.mainPageReference)">
        $currentWiki.prettyName
      </a>
    </li>
  #else
    <li class="dropdown subwiki-navigation" data-placement="bottom" data-trigger="manual" id="tmSubwikiNavigation">
      <a class="icon-navbar dropdown-toggle" data-toggle="dropdown" role="button" title="Changer de wiki">
        $currentWiki.prettyName
        <span class="fa fa-caret-down" aria-hidden="true" style="margin-left: 0.5rem;"></span>
      </a>
      <ul class="dropdown-menu">
    #foreach ($wiki in $authorizedWikis)
      #if ($wiki.prettyName == $currentWiki.prettyName)
      <li class="active"><a href="$xwiki.getURL($wiki.mainPageReference)">$wiki.prettyName</a></li>
      #else
      <li><a href="$xwiki.getURL($wiki.mainPageReference)">$wiki.prettyName</a></li>
      #end
    #end
      </ul>
    </li>
  #end
#end

###
###    Toplevel Left Menu
###
#macro(xwikitopmenuleftend)
  </ul>
#end

###
###    Toplevel Right Menu
###
#macro(xwikitopmenurightstart)
  <ul class="nav navbar-nav navbar-right">
#end

###
###    Toplevel Right Menu
###
#macro(xwikitopmenurightend)
  </ul>
#end

###
###    Toplevel Menu entry with subitems
###
### Generate a menu entry and start the subitem section
###
### @param actionurl The URL this entry points to
### @param linktext The menu item text
###
#macro(xwikitopmenuentrystart $actionurl $linktext $id $class $icon $iconImg)
  #set($sep = '__SEPARATOR__')
  #set($linkName = $linktext)
  #set($linkTitle = $linktext) 
  #if ($linktext.contains($sep))
    #set($linkWords = $linktext.split($sep, 2))
    #set($linkName = $linkWords.get(1))
    #set($linkTitle = "${linkWords.get(0)}: ${linkWords.get(1)}")
  #end
  <dd class="dropdown dropdown-split text-left" #if(!$stringtool.isBlank($id))id="$id"#end>
    ## The menu label. On large screens (tablet and desktop) the label is a link that triggers the default menu action
    ## (e.g. navigation) and the menu is opened using a separated caret. On extra small screens (phones) the label opens
    ## the menu and the default menu action is the first item in the menu.
    <a href="$actionurl" class="dropdown-split-left dropdown-toggle $!class" data-toggle="dropdown" title="$linkTitle">
      ## This caret is displayed only on extra small screens to indicate that the label toggles the menu.
      #glyphicon($icon)$!{iconImg} $linkName <b class="caret"></b>
    ## Don't leave any space between the anchors because they have display:inline-block on large screens.
    </a><a href="#" class="dropdown-split-right dropdown-toggle hidden-xs $!class" data-toggle="dropdown">
      ## This caret is used to toggle the menu on large screens (tablet and desktop).
      <b class="caret"></b>
    </a>
    <ul class="dropdown-menu">
#end

###
###    Toplevel Menu entry without subitems
###
### Generate a menu entry withut subitems
###
### @param actionurl The URL this entry points to
### @param linktext The menu item text
###
#macro(xwikitopmenuentry $actionurl $linktext $id $class $icon $extraAttributes)
  #submenuitem($actionurl $linktext $id $class $icon $extraAttributes)
#end

###
###    Menu subitem entry
###
### Generate a submenu entry
###
### @param actionurl The URL this entry points to
### @param linktext The menu item text
###
#macro(submenuitem $actionurl $linktext $linkid $class $icon $extraAttributes)
  #set($sep = '__SEPARATOR__')
  #set($linkName = $linktext)
  #set($linkTitle = $linktext) 
  #if ($linktext.contains($sep))
    #set($linkWords = $linktext.split($sep))
    #set($linkName = $linkWords.get(1))
    #set($linkTitle = "${linkWords.get(0)}: ${linkWords.get(1)}")
  #end
  <li #if("$!class" != '')class="$!class"#end>
    <a href="$actionurl" #if(!$stringtool.isBlank($linkid))id="$linkid"#end title="$escapetool.xml($linkTitle)"
      $!extraAttributes>$services.icon.renderHTML($icon) $escapetool.xml($linkName)</a>
  </li>
#end

###
###    Menu disabled subitem entry
###
### Generate a disabled submenu entry
###
### @param text The menu item text
###
#macro(submenuitemdisabled $text)
<span class="submenuitem disable">$text</span>
#end

###
###    Toplevel Menu entry separator
###
#macro(xwikitopmenuseparator)
  <li class="divider" role="separator"></li>
#end

###
###    Menu submenu separator
###
#macro(submenuseparator)
  <li class="divider" role="separator"></li>
#end

###
### Action menu separator
###
#macro(xwikiactionmenuseparator)
    </ul>
  </dd>
  <dd>
    <ul>
#end

###
###    Toplevel Menu entry end
###
### End the subitem section
###
#macro(xwikitopmenuentryend)
    </ul>
  </li>
#end

#macro(xwikibutton $actionurl $linktext $id $class $icon)
  <a href="$actionurl" class="btn $!class" role="button" #if(!$stringtool.isBlank($id))id="$id"#end>#glyphicon($icon) $linktext</a>
#end

#macro(xwikibuttonentrystart $actionurl $linktext $id $class $icon)
  <div class="btn-group text-left" #if(!$stringtool.isBlank($id))id="$id"#end>
    <button type="button" class="btn $!class dropdown-toggle" data-toggle="dropdown">#glyphicon($icon) $linktext <span class="caret"></span>
    </button>
    <ul class="dropdown-menu" role="menu">
#end

#macro(xwikisplitbuttonentrystart $actionurl $linktext $id $class $icon)
  <div class="btn-group text-left" #if(!$stringtool.isBlank($id))id="$id"#end>
    <a href="$actionurl" class="btn $!class">#glyphicon($icon) $linktext</a>
    <button type="button" class="btn $!class dropdown-toggle" data-toggle="dropdown">
      <span class="caret"></span>
      <span class="sr-only">Toggle Dropdown</span>
    </button>
    <ul class="dropdown-menu" role="menu">
#end

#macro(xwikibuttonentryend $actionurl $linktext $id $class $icon)
    </ul>
  </div>
#end

#**
 * Display extension points only if the author of the UIX has the admin right on the wiki.
 *
 * @param $name name of the extension points to display
 * @since 7.3RC1
 *#
#macro(displaySimpleSecureUIX $name)
  #foreach($uix in $services.uix.getExtensions($name, {'sortByParameter' : 'order'}))
    #if ($services.security.authorization.hasAccess('admin', $uix.authorReference, $services.wiki.currentWikiDescriptor.reference))
      $services.rendering.render($uix.execute(), 'html/5.0')
    #end
  #end
#end

Hope it could help other users!

Regards,
Luc

1 Like