Liferay 7 / DXPで上部の管理ナビゲーションバー(Control menu)の表示を制御したい

## 対応バージョン
Liferay DXP SP1以降 / Liferay 7 CE


## はじめに
プロジェクトの要件で、Liferayの上部に表示される管理ナビゲーションバー(Control Menu)の表示を制御したいという要件は多くある。その場合の対応方法を説明する。

[公式ドキュメント:THEME CONTRIBUTORS](https://dev.liferay.com/develop/tutorials/-/knowledge_base/7-0/theme-contributors)
[公式ドキュメント:CONTEXT CONTRIBUTORS](https://dev.liferay.com/develop/tutorials/-/knowledge_base/7-0/context-contributors)
に対応方法の説明が載っているが、実際のポートレットの作成方法をここに説明する。

## 前提条件
Liferay workspaceは作成されていて、bladeツール、Theme作成ツール各種はインストールされている前提とする。以下の資料でセットアップ方法は説明している。

- [Liferay DXP / 7におけるサービスビルダーポートレットの作成方法](http://qiita.com/yasuflatland-lf/items/df60ef27b7710bca1607)
- [Liferay 7 / DXPでのテーマの作り方](http://qiita.com/yasuflatland-lf/items/857b009ccbc9f9e3ae59)

テストシナリオとして、Liferayの標準ロール、Administrator権限があるユーザーにのみControl Menuを表示させる、という仕様で実装する。

## Template context contributorの作成
$(liferay_workspace)/modulesの下に移動。

そこでControl Menuのバンドルを置換するためのTheme contributerを作成する必要がある。Liferay Developer Studio / IDEの File -> New -> Liferay Module Projectを選択、Project Template Nameでtemplatecontextcontributorを選択。

もしくはコマンドラインで

```bash
blade create -t template-context-contributor -p com.liferay.product.navigation.control.menu.theme.contributor -c SampleProductNavigationControlMenu sample-product-navigation-control-menu-theme-contributor
```

でbladeツールを使ってモジュールプロジェクトを作成する。

### SampleProductNavigationControlMenuTemplateContextContributorの作成

Control Menuのベースとなるファイルを開く

```bash:ProductNavigationControlMenuTemplateContextContributor.java
vi /liferay-portal/modules/apps/web-experience/product-navigation/product-navigation-control-menu-theme-contributor/src/main/java/com/liferay/product/navigation/control/menu/theme/contributor/internal/ProductNavigationControlMenuTemplateContextContributor.java
```

以下の部分をSampleProductNavigationControlMenuTemplateContextContributor.javaにコピーする。

```java
	@Override
	public void prepare(
		Map<String, Object> contextObjects, HttpServletRequest request) {

		boolean isShowControlMenu = isShowControlMenu(request);

		contextObjects.put("isShowControlMenu", String.valueOf(isShowControlMenuFlg));
	}

	protected boolean isShowControlMenu(HttpServletRequest request) {
		ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
			WebKeys.THEME_DISPLAY);

		if (themeDisplay.isImpersonated()) {
			return true;
		}

		if (!themeDisplay.isSignedIn()) {
			return false;
		}

		User user = themeDisplay.getUser();

		if (!user.isSetupComplete()) {
			return false;
		}

		return true;
	}
```

次に、標準ロールのAdministrator権限がある場合にのみ表示を絞る、またその表示条件をTheme側に出力する変数をセットする処理を追加すると、以下のようになる。

```java
    @Override
    public void prepare(
        Map<String, Object> contextObjects, HttpServletRequest request) {

        //Theme側でメニューの表示非表示を判断できるようにするための変数を書き込む。
        boolean isShowControlMenu = isShowControlMenu(request);
        contextObjects.put("isShowControlMenu", String.valueOf(isShowControlMenu));
    }

    protected boolean isShowControlMenu(HttpServletRequest request) {
        ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
            WebKeys.THEME_DISPLAY);

        if(!isAdmin(request)) {
            return false;
        }

        if (themeDisplay.isImpersonated()) {
            return true;
        }

        if (!themeDisplay.isSignedIn()) {
            return false;
        }

        User user = themeDisplay.getUser();

        if (!user.isSetupComplete()) {
            return false;
        }

        return true;
    }

    /**
     * Extra check for test
     * 
     * @param request
     * @return
     */
    protected boolean isAdmin(HttpServletRequest request) {
        ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
                WebKeys.THEME_DISPLAY);
        boolean bRet = false;

        try {

            //管理者権限がある時のみ表示したい。
            //ここでは説明的にわかりやすように、文字列でロールを取得しているが、実プロジェクトではロールIDを使うようにする方がよいかもしれない。
            //（実プロジェクトでは管理者のロール名が変更される可能性もあるので。ロールIDがDB毎に変わる可能性がある場合は、Administratorなどの文字列をキーにする方がいいケースもある。)
            Role adminRole = RoleLocalServiceUtil.getRole(themeDisplay.getCompanyId(), "Administrator");

            List<Role> roles = themeDisplay.getUser().getRoles();

            for(Role role : roles) {
                if(role.getName().equals(adminRole.getName())) {
                    bRet = true;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bRet;
    }	
```

次に、bnd.bndファイルを以下のように変更する。

```bash:bnd.bnd
Bundle-SymbolicName: sampleproductnavigationcontrolmenutemplatecontextcontributor
Bundle-Version: 2.0.6
Liferay-Releng-Module-Group-Description:
Liferay-Releng-Module-Group-Title: Product Navigation
Liferay-Theme-Contributor-Type: product-navigation-control-menu
Web-ContextPath: /product-navigation-control-menu-theme-contributor
```

Bundle-Version、Liferay-Theme-Contributor-Type、Web-ContextPathは現行のDXPに含まれているProductNavigationControlMenuTemplateContextContributor.javaのbnd.bndをコピーする。

これでTheme Contributorの準備ができたので、

```bash
blade deploy
```

でデプロイ、続いてThemeの作成を行う。

## Themeの作成
$(liferay_workspace)/themesディレクトリに移動し、Liferay CEのクラシックテーマを持つテーマを作成する。([Liferay 7 / DXPでのテーマの作り方](http://qiita.com/yasuflatland-lf/items/857b009ccbc9f9e3ae59)を参照)

以下を変更

```freemarker:init-custom.ftl
<#if isShowControlMenuFlg>
    css_class = css_class?replace("has-control-menu", "")
    css_class = css_class?replace("open", "")
</#if>
```
のようにCSSのクラスを変更した上で、

```freemarker:portal_normal.ftl
<@liferay.control_menu />
```

を

```freemarker:portal_normal.ftl
<#assign isShowControlMenu = htmlUtil.escape(isShowControlMenu!) />
<#if "true" == isShowControlMenu >
	<@liferay.control_menu />
</#if>
```

に変更、

```bash
gulp deploy
```

でデプロイ。

上記のContributor / Themeをデプロイした場合、Administrator権限をもつユーザーにはControl Menuが表示され、それ以外のユーザーにほ表示されなくなるはず。