Liferay 6.2 のWebコンテンツテンプレートのサンプル

#LiferayのWebコンテンツ
Liferayでサイトを構築する際に、必ず使うのがこのWebコンテンツです。LiferayのWebコンテンツは、ユーザーがプログラムの知識がなくとも、構造化されたデータ構造を利用してデータを登録し、構造化されつつも、表示は自由な方法でデータ整形できます。それを実現しているのが、データ構造を定義する`ストラクチャー`と、表示フォーマットを規定する`テンプレート`です。

今回、事情があって6.2を利用することになったのでそれを記述しておきます。すぐに7.1対応が入るので、その際には7.1バージョンの記事も書く予定です。

AssetPublisherから利用するADTに関しましては、[こちらに](https://qiita.com/yasuflatland-lf/items/024e45b85ff7e800a18d)記事を書きましたので合わせて参照ください。

##推奨テンプレート
バージョン7.1現在、使用可能なテンプレートのフォーマットは`Velocity`と`Freemarker`がありますが、組み込み機能の充実と、表記の柔軟性、また近年のバージョンのアップデート状況を鑑みて、`Freemarker`の利用を推奨します。

##利用可能な全組み込み変数/クラスの出力方法
LiferayのWebコンテンツ、テンプレート内では、Liferay側が提供している変数やクラスが利用できます。利用できる変数やクラス・メソッドを一覧で確認するためのテンプレートをこの[Gist](https://gist.github.com/yasuflatland-lf/06d1ac281e9c8e66b290be2cebaa1176)に置いてあります。使い方は、

1. ダミーのストラクチャーを作成する。仮に`Dump Structure`とします。
1. この[コード](https://gist.github.com/yasuflatland-lf/06d1ac281e9c8e66b290be2cebaa1176)をWebコンテンツのテンプレートとして登録し、`Dump Structure`をストラクチャーとして指定します。
1. `Dump Structure`を指定してWebコンテンツを作成し、任意のページにWebコンテンツディスプレイポートレットを配置、`Dump Strucuter`を指定して作成したWebコンテンツを選択します。
1. 該当のページに配置された、Webコンテンツテンプレートで利用できる変数やクラスの一覧が表示されます。

##組み込みサービスの取得方法
たとえば、よく利用するJournalArticleLocalServiceを使う方法を紹介します。まずは`portal-ext.properties`に以下のプロパティを設定します。

```properties:portal-ext.properties
freemarker.engine.restricted.variables=
```

次に、テンプレートに以下の設定を配置します。

```text
<#assign journalArticleLocalService = serviceLocator.findService("com.liferay.portlet.journal.service.JournalArticleLocalService")>
```
これで`journalArticleLocalService`という変数で`JournalArticleLocalService`が利用可能になりました。

##よく使う値の取得方法
####Articleの取得

```text
<#assign journalArticleLocalService = serviceLocator.findService("com.liferay.portlet.journal.service.JournalArticleLocalService")>
<#assign article = journalArticleLocalService.getArticle( groupId, .vars["reserved-article-id"].data )>
```

####該当Articleのタグの取得

```text
<#assign journalArticleLocalService = serviceLocator.findService("com.liferay.portlet.journal.service.JournalArticleLocalService")>
<#assign article = journalArticleLocalService.getArticle( groupId, .vars["reserved-article-id"].data )>
<#assign resourceprimKey = article.getResourcePrimKey()>
<#assign assetEntryLocalService = serviceLocator.findService("com.liferay.portlet.asset.service.AssetEntryLocalService")>
<#assign assetEntry = assetEntryLocalService.getEntry("com.liferay.portlet.journal.model.JournalArticle", resourceprimKey)>
<#assign tagUtil = serviceLocator.findService("com.liferay.portlet.asset.service.AssetTagLocalService")>
<#assign tags = tagUtil.getAssetEntryAssetTags(assetEntry.getEntryId())>
```

####コンテンツ作成者名
```text
<#assign author_name = .vars['reserved-article-author-name'].data>
```

####作成日付
```text
<#setting locale="en_US">
<#assign morg_date = .vars['reserved-article-modified-date'].data>
<#assign morg_date = morg_date?date("EEE, d MMM yyyy HH:mm:ss Z")>
<#assign modified_date = morg_date?string("yyyy-MM-dd")>
<#assign modified_date_print = morg_date?string("yyyy.MM.dd")>
<#setting locale="ja_JP">
```
`setting`で`locale`を変えているのは、日本語で表示させる場合に、Liferay側の初期化設定によって、日付が日本語設定で返ってくる場合、EEEの部分が水曜なら水、というような感じで、比較元がWedなどの文字列の場合、正しく比較できないことがあります。そのため、一時的に強制的に英語ロケールに変換し、比較が終わったところで日本語（もとの言語）に戻しています。これは複数言語でのポータル運用の場合、対応が難しいケースがあるかとは思います。

####サービスコンテクスト(ServiceContext)
```text
<#assign serviceContext = staticUtil["com.liferay.portal.service.ServiceContextThreadLocal"].getServiceContext()>
```

####リクエスト(HttpServletRequest)
```text
<#assign serviceContext = staticUtil["com.liferay.portal.service.ServiceContextThreadLocal"].getServiceContext()>
<#assign httpServletRequest = serviceContext.getRequest()>
```

####テーマディスプレイ(ThemeDisplay)
```text
<#assign serviceContext = staticUtil["com.liferay.portal.service.ServiceContextThreadLocal"].getServiceContext()>
<#assign themeDisplay = serviceContext.getThemeDisplay()>
```
####ルートポートレットID
```text
<#assign rootPortletId = request["theme-display"]["portlet-display"]["root-portlet-id"]>
```
これはポートレットIDにはよく、INSTANCEとか、ネームスペースのランダム文字列とかが付与されますが、それらが無い、プレーンなポートレットID(56とか101とか）が入ります。

####各種パス
```text
<#assign
	currentPath = themeDisplay.getURLPortal()
	currentParam = themeDisplay.getURLCurrent()
	currentHome = themeDisplay.getURLHome()
	friendlyURL = layoutLocalService.fetchLayout(themeDisplay.getPlid()).getFriendlyURL(locale)
>
```

|変数|説明|
|:--|:--|
|currentPath|現在のポートレットのパス`http://localhost:8080`などが入ります。|
|currentParam|`/web/guest/about`などが入ります。|
|currentHome|`http://localhost:8080/web/guest`などが入ります。|
|friendlyURL|現在`About`という名前のページにいる場合、`/about`などが入ります。|

##利用方法サンプル
####タグのループ
上記のタグの取得方法で取得した`AssetTag`のリストが`tags`に入っている、とした場合、

```text
<#list tags as tag>
    <#assign portletURL = portletURLFactory.create(httpServletRequest,rootPortletId,themeDisplay.getPlid(),themeDisplay.getLifecycle())>
    <#assign VOID = portletURL.setParameter("tag", tag.getName()) />
    <#assign VOID = portletURL.setParameter("p_p_lifecycle", "0") />
    <#assign VOID = portletURL.setParameter("p_p_state", "maximized") />
    <li><a href="${portletURL}"><i class="fas fa-hashtag" aria-hidden="true"></i>${tag.getName()}</a></li>
</#list>
```
となります。利用している変数(themeDisplayなど)は、このページの他の部分を参照ください。

####繰り返しコンテンツ(repeatable)の表示方法
Webコンテンツでは繰り返し(repetable)なコンテンツを作成できます。例えば以下のような構造のストラクチャーを持つWebコンテンツを表示させたい場合、

```text
root
├── blog_sec_rp
│      ├── blog_sec_ttl
│      └── blog_sec_contents
├── blog_sec_rp
│      ├── blog_sec_ttl
│      └── blog_sec_contents
├── blog_sec_rp
│      ├── blog_sec_ttl
│      └── blog_sec_contents
.
.
.
```

```text
<#if blog_sec_rp.getSiblings()?has_content>
    <#list blog_sec_rp.getSiblings() as blog>
        ${blog.getChild("blog_sec_ttl").getData()}
    </#list>
</#if>
```
のような表示方法になります。最初に要素があるか確認し、ある場合はループさせて内容を表示させていきます。