連想配列(ハッシュ)や条件分岐を駆使して同じタグの付いたエントリーをリストアップ

relatedtags.gif
(完成図)

MTSetVar(Block)/MTIfタグの具体的な利用例。 (Junnama Online (Mirror))さんの記事を参考に、現に表示されているエントリーに付いているタグと同じタグが付いているエントリーをリストアップするテンプレートを書いてみました。

ただ同じタグが付いているエントリーを並べただけでは面白くないので、「関連性の高さで2つにグルーピングして表示」するようにしてみました。

ソースコード

まずは完成したソースコードを紹介します。

<mt:EntryIfTagged>

<mt:SetVarBlock name="allTags"><mt:EntryTags glue=" AND "><$mt:TagName$></mt:EntryTags></mt:SetVarBlock>
<mt:SetVarBlock name="anyTags"><mt:EntryTags glue=" OR "><$mt:TagName$></mt:EntryTags></mt:SetVarBlock>
<mt:SetVarBlock name="thisTags"><mt:EntryTags glue=", "><$mt:TagName$></mt:EntryTags></mt:SetVarBlock>
<mt:SetVarBlock name="thisID"><$mt:EntryID pad="1"$></mt:SetVarBlock>

<mt:Entries tag="$allTags" lastn="11">
 <mt:SetVarBlock name="listID"><$mt:EntryID pad="1"$></mt:SetVarBlock>
 <mt:SetVarBlock name="listCount"><$mt:EntriesCount$></mt:SetVarBlock>
 <mt:If name="listCount" gt="1">
 <mt:Unless name="listID" eq="$thisID">
 <mt:SetVarBlock name="listDate" key="$listID"><a href="<$mt:EntryPermalink$>"><$mt:EntryTitle$></a></mt:SetVarBlock>
 </mt:Unless>
 </mt:If>
</mt:Entries>

<mt:Entries tag="$anyTags" lastn="11">
 <mt:SetVarBlock name="listID_any"><$mt:EntryID pad="1"$></mt:SetVarBlock>
 <mt:SetVarBlock name="listCount_any"><$mt:EntriesCount$></mt:SetVarBlock>
 <$mt:SetVar name="already" value="0"$>
 <mt:If name="listCount_any" gt="$listCount">
 <mt:If name="listCount_any" gt="1">
 <mt:Unless name="listID_any" eq="$thisID">
 <mt:Loop name="listDate">
 <mt:If name="listID_any" eq="$__key__">
 <$mt:SetVar name="already" value="1"$>
 </mt:If>
 </mt:Loop>
 <mt:Unless name="already">
 <mt:SetVarBlock name="listDate_any" key="$listID_any"><a href="<$mt:EntryPermalink$>"><$mt:EntryTitle$></a></mt:SetVarBlock>
 </mt:Unless>
 </mt:Unless>
 </mt:If>
 </mt:If>
</mt:Entries>

<mt:SetVarBlock name="listDateCount"><$mt:GetVar name="count(listDate)"$></mt:SetVarBlock>
<mt:SetVarBlock name="listDateCount_any"><$mt:GetVar name="count(listDate_any)"$></mt:SetVarBlock>

<mt:If name="listDateCount">
 <div class="textBody relatedEntry">
 <dl class="relatedEntry">
 <dt>[関連エントリー]
 <mt:If name="listDateCount_any">
 <br />タグ:<$mt:GetVar name="thisTags"$> をすべて含む
 </mt:If>
 </dt>
 <dd>
 <ul>
 <mt:Loop name="listDate" sort_by="key reverse">
 <li><$mt:GetVar name="__value__"$></li>
 </mt:Loop>
 </ul>
 </dd>
 <mt:If name="listDateCount_any">
 <dt>タグ:<$mt:GetVar name="thisTags"$> のいずれかを含む</dt>
 <dd>
 <ul>
 <mt:Loop name="listDate_any">
 <li><$mt:GetVar name="__value__"$></li>
 </mt:Loop>
 </ul>
 </dd>
 </mt:If>
 </dl>
 </div>
<mt:Else>
 <mt:If name="listDateCount_any">
 <div class="textBody relatedEntry">
 <dl class="relatedEntry">
 <dt>[関連エントリー]</dt>
 <dd>
 <ul>
 <mt:Loop name="listDate_any">
 <li><$mt:GetVar name="__value__"$></li>
 </mt:Loop>
 </ul>
 </dd>
 </dl>
 </div>
 </mt:If>
</mt:If>

</mt:EntryIfTagged>

Movable Type の変数、配列、条件分岐を駆使しました。結果、参考にしたJunnama Online (Mirror) さんのソースとはちょっと変わっていますし、長くなってしまいました。

以下で順を追って解説してみます。

解説

全体的な設定

<mt:EntryIfTagged>

<mt:SetVarBlock name="allTags"><mt:EntryTags glue=" AND "><$mt:TagName$></mt:EntryTags></mt:SetVarBlock>
<mt:SetVarBlock name="anyTags"><mt:EntryTags glue=" OR "><$mt:TagName$></mt:EntryTags></mt:SetVarBlock>
<mt:SetVarBlock name="thisTags"><mt:EntryTags glue=", "><$mt:TagName$></mt:EntryTags></mt:SetVarBlock>
<mt:SetVarBlock name="thisID"><$mt:EntryID pad="1"$></mt:SetVarBlock>

MTEntryIfTagged は、記事にタグを付けているときだけ囲まれた内容を処理します。

MTSetVarBlock によって変数を定義しています。定義している内容はそれぞれ次のとおりです。

allTags

現在表示しているエントリーに付いているすべてのタグを、「AND」でつなげて変数 allTags に代入。
例:タグ1 AND タグ2

anyTags

現在表示しているエントリーに付いているすべてのタグを、「OR」でつなげて変数 anyTagsに代入。
例:タグ1 OR タグ2

thisTags

現在表示しているエントリーに付いているすべてのタグを、「,」でつなげて変数 thisTagsに代入。
例:タグ1 , タグ2

thisID

現在表示しているエントリーの ID を pad モディファイアを指定して常に6桁で変数 thisID に代入。6桁にした方が、ソートがうまくいったので。

現在表示されているエントリーに付いている「すべてのタグ」を含むエントリーのリストアップ

<mt:Entries tag="$allTags" lastn="11">
 <mt:SetVarBlock name="listID"><$mt:EntryID pad="1"$></mt:SetVarBlock>
 <mt:SetVarBlock name="listCount"><$mt:EntriesCount$></mt:SetVarBlock>
 <mt:If name="listCount" gt="1">
 <mt:Unless name="listID" eq="$thisID">
 <mt:SetVarBlock name="listDate" key="$listID"><a href="<$mt:EntryPermalink$>"><$mt:EntryTitle$></a></mt:SetVarBlock>
 </mt:Unless>
 </mt:If>
</mt:Entries>

まず <mt:Entries tag="$allTags" lastn="11"> で、現在表示しているエントリーに付いている「すべてのタグ」を含むエントリーを、最大11件リストアップします。

最大10件ではなく最大11件となっているのは、リストアップされるエントリーには必ず現在表示されているエントリーを含むので、そのエントリーをリストから除いたときにリストアップされるエントリー数が一つ減ってしまうからです。

その次の SetVarBlock タグで、以下の二つの変数を定義しています。

listID
リストアップされるたびにそのエントリーの ID を代入。
listCount
リストアップされるエントリーの件数を代入。

その後、もしリストアップされるエントリーの件数( listCount )が1より大きい場合( if タグ)で、かつ、リストアップされるエントリーの ID が、現在表示されているエントリーの ID と異なる場合( Unless タグ)に次の内容を処理します。

ここが今回のテンプレートの肝で、上記の条件を満たしたエントリーを変数 listDate に次のような連想配列(ハッシュ)として代入します。

連想配列 listDate
  • キー: リストアップされるエントリー ID
  • 値: リストアップされるエントリーへのリンク

ここで直接エントリーへのリンクを出力せずに連想配列に代入する形をとったのは、後ほど (X)HTML を出力するときに、ここでリストアップされた件数で条件分岐を行いたかったためです。

現在表示されているエントリーに付いている「いずれかのタグ」を含むエントリーのリストアップ

<mt:Entries tag="$anyTags" lastn="11">
 <mt:SetVarBlock name="listID_any"><$mt:EntryID pad="1"$></mt:SetVarBlock>
 <mt:SetVarBlock name="listCount_any"><$mt:EntriesCount$></mt:SetVarBlock>
 <$mt:SetVar name="already" value="0"$>
 <mt:If name="listCount_any" gt="$listCount">
 <mt:If name="listCount_any" gt="1">
 <mt:Unless name="listID_any" eq="$thisID">
 <mt:Loop name="listDate">
 <mt:If name="listID_any" eq="$__key__">
 <$mt:SetVar name="already" value="1"$>
 </mt:If>
 </mt:Loop>
 <mt:Unless name="already">
 <mt:SetVarBlock name="listDate_any" key="$listID_any"><a href="<$mt:EntryPermalink$>"><$mt:EntryTitle$></a></mt:SetVarBlock>
 </mt:Unless>
 </mt:Unless>
 </mt:If>
 </mt:If>
</mt:Entries>

まず、現在表示しているエントリーに付いている「いずれかのタグ」を含むエントリーを、最大11件リストアップする設定にし、変数を三つ定義します。

listID_any
リストアップされるたびにそのエントリーの ID を代入。
listCount_any
リストアップされるエントリーの件数を代入。
already
0 を代入(初期化)。

続いて、三つの条件分岐が入れ子になります。

  • 「いずれかのタグ」を含むエントリー数( listCount_any )が、「すべてのタグ」を含むエントリー数( listCount )より多い場合( if )で、
  • かつ、「いずれかのタグ」を含むエントリー数( listCount_any )が 1 よりも大きくて( if )、
  • かつ、リストアップされるエントリーの ID が、現在表示されているエントリーの ID と異なる場合( Unless )

この三つの条件分岐を満たす場合のみ、次の内容を処理します。

<mt:Loop name="listDate">
 <mt:If name="listID_any" eq="$__key__">
 <$mt:SetVar name="already" value="1"$>
 </mt:If>
</mt:Loop>
<mt:Unless name="already">
 <mt:SetVarBlock name="listDate_any" key="$listID_any"><a href="<$mt:EntryPermalink$>"><$mt:EntryTitle$></a></mt:SetVarBlock>
</mt:Unless>

ここでは、まず Loop タグで配列変数 listDate に格納されたデータについて繰り返し処理をします。

処理する内容は、ここでリストアップされる「いずれかのタグ」を含むエントリーの ID ( listDate_any )が、「すべてのタグ」を含むエントリーとしてリストアップされた ID ( listDate の $__key__ )の中にすでに含まれているかどうかをチェックし、含まれているならば変数 already に 1 を代入します。

ループで、ID のチェックが終わり、「すべてのタグ」を含むとしてリストアップされていなければ 変数 already は初期化した 0 のままです。

したがって、次のUnless タグで、「もし変数 already が 1 でなければ」、つまり「いずれかのタグ」を含むとして初めてリストアップされたエントリーについてのデータのみ、変数 listDate_any に以下のような連想配列(ハッシュ)として代入します。

連想配列 listDate_any
  • キー: リストアップされるエントリー ID
  • 値: リストアップされるエントリーへのリンク

(x)HTML を出力する

<mt:SetVarBlock name="listDateCount"><$mt:GetVar name="count(listDate)"$></mt:SetVarBlock>
<mt:SetVarBlock name="listDateCount_any"><$mt:GetVar name="count(listDate_any)"$></mt:SetVarBlock>

まずは、次の二つの変数を定義します。

listDateCount
「すべてのタグ」を含むとしてリストアップされたエントリーの数を代入。
listDateCount_any
「いずれかのタグ」を含むとしてリストアップされたエントリーの数を代入。

続いて、(x)HTML を出力します。

<mt:If name="listDateCount">
 <div class="textBody relatedEntry">
 <dl class="relatedEntry">
 <dt>[関連エントリー]
 <mt:If name="listDateCount_any">
 <br />タグ:<$mt:GetVar name="thisTags"$> をすべて含む
 </mt:If>
 </dt>
 <dd>
 <ul>
 <mt:Loop name="listDate" sort_by="key reverse">
 <li><$mt:GetVar name="__value__"$></li>
 </mt:Loop>
 </ul>
 </dd>
 <mt:If name="listDateCount_any">
 <dt>タグ:<$mt:GetVar name="thisTags"$> のいずれかを含む</dt>
 <dd>
 <ul>
 <mt:Loop name="listDate_any">
 <li><$mt:GetVar name="__value__"$></li>
 </mt:Loop>
 </ul>
 </dd>
 </mt:If>
 </dl>
 </div>
<mt:Else>
 <mt:If name="listDateCount_any">
 <div class="textBody relatedEntry">
 <dl class="relatedEntry">
 <dt>[関連エントリー]</dt>
 <dd>
 <ul>
 <mt:Loop name="listDate_any">
 <li><$mt:GetVar name="__value__"$></li>
 </mt:Loop>
 </ul>
 </dd>
 </dl>
 </div>
 </mt:If>
</mt:If>

全体的な条件分岐イメージは次のようになります。

[すべてのタグを含む・いずれかのタグを含むエントリーが両方ある場合]

<div class="textBody relatedEntry">
 <dl class="relatedEntry">
 <dt>[関連エントリー]
 <br />タグ:タグ1,タグ2 をすべて含む
 </dt>
 <dd>
 <ul>
 <li>エントリーへのリンク</li>
 </ul>
 </dd>
 <dt>タグ:タグ1,タグ2 のいずれかを含む</dt>
 <dd>
 <ul>
 <li>エントリーへのリンク</li>
 </ul>
 </dd>
 </dl>
</div>
[すべてのタグを含む・いずれかのタグを含むエントリーのどちらかがある場合]

<div class="textBody relatedEntry">
 <dl class="relatedEntry">
 <dt>[関連エントリー]</dt>
 <dd>
 <ul>
 <li>エントリーへのリンク</li>
 </ul>
 </dd>
 </dl>
</div>

このように、エントリーがリストアップされたか否かを条件として分岐するために、先ほどエントリーをリストアップさせた際に、直接エントリーへのリンクを出力せずに連想配列に代入する形をとったわけです。

エントリーへのリンクは、次のように Loop タグを使って、その配列の値を取り出しています。

<mt:Loop name="listDate">
 <li><$mt:GetVar name="__value__"$></li>
</mt:Loop>

説明をコメントとして入れたソースコード

最後に、簡単な説明をコメントとして入れたソースコードを載せておきます。

<mt:EntryIfTagged>

<!-- 設定 -->

<!-- 変数 allTags : 現在表示されているエントリーについているすべてのタグを AND でつなげる -->
<mt:SetVarBlock name="allTags"><mt:EntryTags glue=" AND "><$mt:TagName$></mt:EntryTags></mt:SetVarBlock>

<!-- 変数 anyTags : 現在表示されているエントリーについているすべてのタグを OR でつなげる -->
<mt:SetVarBlock name="anyTags"><mt:EntryTags glue=" OR "><$mt:TagName$></mt:EntryTags></mt:SetVarBlock>

<!-- 変数 thisTags : 現在表示されているエントリーについているすべてのタグを , でつなげる -->
<mt:SetVarBlock name="thisTags"><mt:EntryTags glue=", "><$mt:TagName$></mt:EntryTags></mt:SetVarBlock>

<!-- 変数 thisID : 現在表示されているエントリーの ID -->
<mt:SetVarBlock name="thisID"><$mt:EntryID pad="1"$></mt:SetVarBlock>

<!-- / 設定 -->

<!-- 現在表示されているエントリーについているすべてのタグを含むエントリーをリストアップ -->
<mt:Entries tag="$allTags" lastn="11">

 <!-- 変数 listID : リストアップされるエントリーの ID -->
 <mt:SetVarBlock name="listID"><$mt:EntryID pad="1"$></mt:SetVarBlock>

 <!-- 変数 listCount : 現在表示されているエントリーについているすべてのタグを含むエントリー数 -->
 <mt:SetVarBlock name="listCount"><$mt:EntriesCount$></mt:SetVarBlock>

 <!-- 変数 listCount が1以上の場合に以下を実行 -->
 <mt:If name="listCount" gt="1">

 <!-- リストアップされるエントリーID と今表示されているエントリーID が同じでない場合 -->
 <mt:Unless name="listID" eq="$thisID">
 <!-- 配列変数 listDate : key = リストアップされるエントリーの ID、value = エントリーへのリンク -->
 <mt:SetVarBlock name="listDate" key="$listID"><a href="<$mt:EntryPermalink$>"><$mt:EntryTitle$></a></mt:SetVarBlock>
 </mt:Unless>

 </mt:If>

</mt:Entries>
<!-- / 現在表示されているエントリーについているすべてのタグを含むエントリーをリストアップ -->


<!-- 現在表示されているエントリーについているいずれかのタグを含むエントリーをリストアップ -->
<mt:Entries tag="$anyTags" lastn="11">

 <!-- 変数 listID_any : リストアップされるエントリーの ID -->
 <mt:SetVarBlock name="listID_any"><$mt:EntryID pad="1"$></mt:SetVarBlock>

 <!-- 変数 listCount_any : 現在表示されているエントリーについているいずれかのタグを含むエントリー数 -->
 <mt:SetVarBlock name="listCount_any"><$mt:EntriesCount$></mt:SetVarBlock>

 <!-- 変数 already : 0 に初期化-->
 <$mt:SetVar name="already" value="0"$>

 <!-- 変数 listCount_any が、変数 listCount より大きい場合に以下を実行 -->
 <mt:If name="listCount_any" gt="$listCount">

 <!-- 変数 listCount_any が1以上の場合に以下を実行 -->
 <mt:If name="listCount_any" gt="1">

 <!-- リストアップされるエントリーID と今表示されているエントリーID が同じでない場合 -->
 <mt:Unless name="listID_any" eq="$thisID">

 <!-- リストアップされるエントリーID がすでに上でリストアップされていないかチェック -->
 <mt:Loop name="listDate">
 <mt:If name="listID_any" eq="$__key__">
 <!-- 変数 already : すでに上でリストアップされていたら 1 を記憶-->
 <$mt:SetVar name="already" value="1"$>
 </mt:If>
 </mt:Loop>

 <!-- まだ上でリストアップされていなかった場合-->
 <mt:Unless name="already">
 <!-- 配列変数 listDate_any : key = リストアップされるエントリーの ID、value = エントリーへのリンク -->
 <mt:SetVarBlock name="listDate_any" key="$listID_any"><a href="<$mt:EntryPermalink$>"><$mt:EntryTitle$></a></mt:SetVarBlock>
 </mt:Unless>

 </mt:Unless>

 </mt:If>

 </mt:If>

</mt:Entries>
<!-- / 現在表示されているエントリーについているいずれかのタグを含むエントリーをリストアップ -->

<!-- 以下で出力する -->

<!-- 設定 -->
<mt:SetVarBlock name="listDateCount"><$mt:GetVar name="count(listDate)"$></mt:SetVarBlock>
<mt:SetVarBlock name="listDateCount_any"><$mt:GetVar name="count(listDate_any)"$></mt:SetVarBlock>
<!-- / 設定 -->

<!-- 配列変数 listDate の値の数が 0 でない場合 -->
<mt:If name="listDateCount">
<div class="textBody relatedEntry">
 <dl class="relatedEntry">
 <dt>[関連エントリー]
 <!-- 配列変数 listDateCount_any の値の数が 0 でない場合 -->
 <mt:If name="listDateCount_any">
 <br />タグ:<$mt:GetVar name="thisTags"$> をすべて含む
 </mt:If>
 </dt>
 <dd>
 <ul>
 <mt:Loop name="listDate" sort_by="key reverse">
 <li><$mt:GetVar name="__value__"$></li>
 </mt:Loop>
 </ul>
 </dd>
 <!-- 配列変数 listDateCount_any の値の数が 0 でない場合 -->
 <mt:If name="listDateCount_any">
 <dt>タグ:<$mt:GetVar name="thisTags"$> のいずれかを含む</dt>
 <dd>
 <ul>
 <mt:Loop name="listDate_any">
 <li><$mt:GetVar name="__value__"$></li>
 </mt:Loop>
 </ul>
 </dd>
 </mt:If>
 </dl>
</div>
<!-- 配列変数 listDate の値の数が 0 の場合 -->
<mt:Else>
 <!-- 配列変数 listDateCount_any の値の数が 0 でない場合 -->
 <mt:If name="listDateCount_any">
 <div class="textBody relatedEntry">
 <dl class="relatedEntry">
 <dt>[関連エントリー]</dt>
 <dd>
 <ul>
 <mt:Loop name="listDate_any">
 <li><$mt:GetVar name="__value__"$></li>
 </mt:Loop>
 </ul>
 </dd>
 </dl>
 </div>
 </mt:If>
</mt:If>

</mt:EntryIfTagged>

もし間違っている箇所があればご指摘いただけると嬉しいです。

万が一、「おーコレいいじゃん、コピペして使おう」って思う方がいたときのために、タブでインデントしたファイルも置いておきます。

Movable Type の公式サイトには、テンプレートタグリファレンスが掲載されていますが、使い勝手がいいとは言えません。同じような感想をお持ちの方は以下の書籍がお勧めです。

そのうち感想をアップしますが、かなり便利です。