a-blog cms のカスタムフィールド検索を理解する

公開日:

目次

このエントリーは「a-blog cms Advent Calendar」の19日目の記事です。

今年は、a-blog cms の次期バージョンである Ver. 3.2 でエントリー管理画面にカスタムフィールド検索機能を実装した経験から、カスタムフィールド検索機能についての知見が深まったので共有していこうと思います。

まず、a-blog cms のカスタムフィールド検索について知らない方は、前提情報として次のドキュメントを御覧ください。

この記事では既存のドキュメントの内容で分かりづらいところや、不足しているところを補足し、カスタムフィールド検索の理解をより深めることを目的とします。

URLコンテキストでカスタムフィールドの条件式を表現する

a-blog cmsでは、いま表示しているページがWebサイトのどこにいるか、どのような状態であるかをURL上のパスとして表現しています。これをURLコンテキストと呼びます。

URLコンテキストは、カスタムフィールドの条件を表現することが可能です。例えば、以下のURLは「priceというフィールドの値が100または300」という意味をもちます。

https://example.com/field/price/100/300/

このURLコンテキストの特性を利用し、ユーザーが検索フォームで入力した情報からURLコンテキストの仕様に従ったURLを組み立て、リダイレクトすることでサイト内に検索機能を実装することができます。

カスタムフィールドのURLコンテキストをもっと理解する

カスタムフィールドのURLコンテキストについてより深ぼっていきます。

カスタムフィールドのURLコンテキストはフィールド名・検索条件・結合子(セパレーター)の3つの構成要素からできています。

また、検索条件は演算子(オペレーター)・値・結合子(コネクター)の3つの要素で構成されており、1つのカスタムフィールドに対して複数設定することが可能です。

上記の内容を図に表すと次のようになります。

カスタムフィールドのURLコンテキストはフィールド名・検索条件・結合子(セパレーター)の3つの構成要素からできています。さらに、検索条件は演算子(オペレーター)・値・結合子(コネクター)の3つの要素で構成されています。

ここまでで出てきた「演算子(オペレーター)」、「結合子(コネクター)」、「結合子(セパレーター)」といった用語について、それぞれ詳しく説明します。

演算子(オペレーター)

検索条件の比較方法を指定する記号やキーワードです。以下はカスタムフィールド検索で利用できる演算子の一覧です

演算子記号意味
eq=等しい
neq!=等しくない
gt>より大きい
gte>=以上
lt<より小さい
lte<=以下
lkLIKE含まれる
nlkNOT LIKE含まれない
reREGEXXP指定した表記表現に合致する
nreNOT REGEXP指定した表記表現に合致しない
emEMPTY値が空(何も入ってこない)
nemNOT EMPTY値が空(何も入ってこない)ではない

結合子(コネクター)

同じフィールド内で複数の条件を組み合わせるための論理演算子です。 and か or で指定します。

例えば、「価格が300より大きい、または150以下」という条件を表現する場合、URLコンテキストは以下のようになります。

field/price/gte/300/or/lte/150/

次に、このURLコンテキストを分解して、構造化してみます。構造化することで、URLコンテキストがどのような意味を持つのかがわかりやすくなるはずです。

[
  {
    "key": "price",
    "filters": [
      {
        "operator": "gte",
        "value": "300",
        "connector": "and"
      },
      {
        "operator": "lte",
        "value": "150",
        "connector": "or"
      }
    ],
    "separator": "_and_"
  }
]

URLコンテキストを構造化したことで、「price というカスタムフィールドの値が300より大きい、または150以下」という意味を持つことがわかりやすくなりました。

なお、結合子(コネクター)が指定されなかった場合は、eq 以外の演算子(オペレーター)が指定されていれば、and となり、そうでなければ or として扱われます。

結合子(セパレーター)

異なるフィールドの条件を組み合わせる際に使用する論理演算子です。 _and_ か _or_ で指定します。

例えば、「価格が100より大きい、かつ在庫数が0より大きい」という条件を表現する場合、URLコンテキストは以下のようになります。

field/price/gte/100/_and_/stock/gt/0

こちらも、URLコンテキストを分解して、構造化してみます。

[
  {
    "key": "price",
    "filters": [
      {
        "operator": "gte",
        "value": "100",
        "connector": "and"
      }
    ],
    "separator": "_and_"
  },
  {
    "key": "stock",
    "filters": [
      {
        "operator": "gt",
        "value": "0",
        "connector": "and"
      }
    ],
    "separator": "_and_"
  }
]

こちらも、構造化することで意味がわかりやすくなりました。

URLコンテキストで指定されているフィールド情報を表示する Field_Searchモジュール

カスタムフィールドの検索を実装する場合には、現在絞り込まれている検索条件は何なのかということをユーザーに表示したい場合があります。

そのような場合、Field_Searchモジュール を利用することで、現在絞り込まれている検索条件をテンプレート上で変数として扱うことができるようになります。

<!-- BEGIN_MODULE Field_Search -->
    <form
      action=""
      method="post"
      name="searchForm"
      class="acms-form search-form"
      role="search"
    >
      <div class="acms-form-action">
        <input type="hidden" name="field[]" value="color" />
        <select name="color">
          <option value="">全て</option>
          <option value="red" {color:selected#red}>赤色</option>
          <option value="yellow" {color:selected#yellow}>黄色</option>
          <option value="blue" {color:selected#blue}>青色</option>
        </select>
      </div>
    </form>
<!-- END_MODULE Field_Search -->

上記のように、Field_Searchモジュール を利用すると、URLコンテキストに field/color/red が指定されている場合、セレクトボックスの value属性が red となっている項目が選択された状態で表示することが可能です。

後ほど、カスタムフィールド検索フォームの実装方法を紹介しますが、そこでも現在絞り込まれている検索条件を取得・表示するためにField_Searchモジュール を利用しています。

カスタムフィールド検索フォームを実装する

a-blog cms では POST_2GET モジュールの仕様に従ってフォームのHTMLを記述することで、サイト内にカスタムフィールドのデータを利用して検索するためのフォームを実装することができます。

POST_2GET モジュールを利用しているため、カテゴリーやタグのデータと組み合わせてデータを検索することができます。

カスタムフィールドを利用した検索方法は以下のような方法があります。

  • 単一の値で検索する
  • 複数の値のいずれかに一致する条件で検索する
  • 値と演算子を組み合わせて検索する
  • 複数の値と演算子を組み合わせて検索する
  • 複数のフィールドから検索する

それぞれの検索方法について詳細を解説していきます。

単一の値で検索する

特定のカスタムフィールドのデータを、指定した単一の値で検索します。

URLコンテキストの例

https://example.com/field/price/100

カスタムフィールドのURLコンテキストを構造化して表現すると、次のようになります。

[
  {
    "key": "price",
    "filters": [
      {
        "operator": "eq",
        "value": "100",
        "connector": "or"
      }
    ],
    "separator": "_and_"
  }
]

検索フォームのサンプル

単一の値で検索する場合、演算子や結合子は不要なため、通常のカスタムフィールドと同じルールでフォームのHTMLを記述することができます。

<!-- BEGIN_MODULE Field_Search -->
<form action="" method="post" class="acms-form">
  <table>
    <tr>
      <th>価格</th>
      <th>検索</th>
    </tr>
    <tr>
      <td>
        <label class="acms-form-radio">
        <input type="radio" name="price" value="100"{price:checked#100} /><i class="acms-ico-radio"></i>100</label>
        <label class="acms-form-radio">
        <input type="radio" name="price" value="200"{price:checked#200} /><i class="acms-ico-radio"></i>200</label>
        <label class="acms-form-radio">
        <input type="radio" name="price" value="300"{price:checked#300} /><i class="acms-ico-radio"></i>300</label>
        <input type="hidden" name="field[]" value="price" />
      </td>
      <td>
        <input type="hidden" name="cid" value="%{CID}" />
        <input type="hidden" name="bid" value="%{BID}" />
        <input type="submit" name="ACMS_POST_2GET" value="検索" class="acms-btn-admin" />
      </td>
    </tr>
  </table>
</form>
<!-- END_MODULE Field_Search -->

複数の値のいずれかに一致する条件で検索する

特定のカスタムフィールドのデータを、複数の指定した値のいずれかに一致する条件で検索します。

URLコンテキストの例

https://example.com/field/price/200/300/

カスタムフィールドのURLコンテキストを構造化して表現すると、次のようになります。

[
  {
    "key": "price",
    "filters": [
      {
        "operator": "eq",
        "value": "200",
        "connector": "or"
      },
      {
        "operator": "eq",
        "value": "300",
        "connector": "or"
      }
    ],
    "separator": "_and_"
  }
]

検索フォームのサンプル

複数の値のいずれかに一致する条件で検索する場合も、単一の値で検索する場合と同じく特別な記述は必要ありません。

<!-- BEGIN_MODULE Field_Search -->
<form action="" method="post" class="acms-form">
  <table>
      <tr>
        <th>価格</th>
        <th>検索</th>
      </tr>
      <tr>
        <td>
          <label class="acms-form-checkbox" for="input-price-100">
            <input type="checkbox" name="price[]" value="100" id="input-price-100"{price:checked#100} />
            <i class="acms-ico-checkbox"></i>100
          </label>
          <label class="acms-form-checkbox" for="input-price-200">
            <input type="checkbox" name="price[]" value="200" id="input-price-200"{price:checked#200} />
            <i class="acms-ico-checkbox"></i>200
          </label>
          <label class="acms-form-checkbox" for="input-price-300">
            <input type="checkbox" name="price[]" value="300" id="input-price-300"{price:checked#300} />
            <i class="acms-ico-checkbox"></i>300
          </label>
          <input type="hidden" name="field[]" value="price" />
        </td>
        <td>
          <input type="hidden" name="cid" value="%{CID}" />
          <input type="hidden" name="bid" value="%{BID}" />
          <input type="submit" name="ACMS_POST_2GET" value="検索" class="acms-btn-admin" />
        </td>
      </tr>
  </table>
</form>
<!-- END_MODULE Field_Search -->

値と演算子を組み合わせて検索する

特定のカスタムフィールドのデータを、指定した値と演算子(例: 等しい、より大きいなど)を組み合わせて検索します。

URLコンテキストの例

https://example.com/field/price/lte/300/

カスタムフィールドのURLコンテキストを構造化して表現すると、次のようになります。

[
  {
    "key": "price",
    "filters": [
      {
        "operator": "lte",
        "value": "300",
        "connector": "and"
      }
    ],
    "separator": "_and_"
  }
]

検索フォームのサンプル

演算子(オペレーター)を利用して検索する場合、どの演算子(オペレーター)を利用するのかという情報をフォームで送信するデータ内に含める必要があります。

次のように、name 属性に カスタムフィールド名@operator とし、値として演算子(オペレーター)を指定することができます。

<input type="hidden" name="カスタムフィールド名@operator" value="演算子" />

検索フォームのサンプルコード全体は次のようになります。

<!-- BEGIN_MODULE Field_Search -->
<form action="" method="post" class="acms-form">
  <!-- 宣言 -->
  <input type="hidden" name="field[]" value="price" />
  <!-- 演算子 -->
  <input type="hidden" name="price@operator" value="lte" />
  <table>
    <tr>
      <th>価格</th>
      <th>検索</th>
    </tr>
    <tr>
      <td>
        <select name="price">
          <option value="">上限なし</option>
          <option value="300"{price:selected#300}>300円以下</option>
          <option value="200"{price:selected#200}>200円以下</option>
          <option value="100"{price:selected#100}>100円以下</option>
        </select>
      </td>
      <td>
        <input type="hidden" name="cid" value="%{CID}" />
        <input type="hidden" name="bid" value="%{BID}" />
        <input type="submit" name="ACMS_POST_2GET" value="検索" class="acms-btn-admin" />
      </td>
    </tr>
  </table>
</form>
<!-- END_MODULE Field_Search -->

演算子(オペレーター)をユーザーに選ばせたい場合は、selectタグやラジオボタンで選択させることも可能です。

<select name="price@operator">
          <option value="eq"{price@operator:selected#eq}>=</option>
          <option value="neq"{price@operator:selected#neq}>!=</option>
          <option value="lt"{price@operator:selected#lt}>&lt;</option>
          <option value="lte"{price@operator:selected#lte}>&lt;=</option>
          <option value="gt"{price@operator:selected#gt}>&gt;</option>
          <option value="gte"{price@operator:selected#gte}>&gt;=</option>
          <option value="lk"{price@operator:selected#lk}>LIKE</option>
          <option value="nlk"{price@operator:selected#nlk}>NOT LIKE</option>
          <option value="re"{price@operator:selected#re}>REGEXP</option>
          <option value="nre"{price@operator:selected#nre}>NOT REGEXP</option>
          <option value="em"{price@operator:selected#em}>EMPTY</option>
          <option value="nem"{price@operator:selected#nem}>NOT EMPTY</option>
        </select>

複数の値と演算子を組み合わせて検索する

特定のカスタムフィールドのデータを、複数の値と演算子を組み合わせて複数の条件で検索します。

URLコンテキストの例

https://example.com/field/price/gte/300/or/lte/150/

カスタムフィールドのURLコンテキストを構造化して表現すると、次のようになります。

[
  {
    "key": "price",
    "filters": [
      {
        "operator": "gte",
        "value": "300",
        "connector": "and"
      },
      {
        "operator": "lte",
        "value": "150",
        "connector": "or"
      }
    ],
    "separator": "_and_"
  }
]

検索フォームのサンプル

単一フィールド内で、複数の値と演算子を組み合わせて複数の条件で検索するためには、値と演算子(オペレーター)、結合子(コネクター)をそれぞれ複数指定する必要があります。

まず、値を複数設定する方法ですが、name属性に カスタムフィールド名[] を記述することで、複数の値を指定することができます。

<input type="text" name="price[]" value=""  />
<input type="text" name="price[]" value=""  />

添字を指定して、何個目の条件に対応する値なのかを明示的に設定することも可能です。添字がない場合は、上から順番に数えられます。

<input type="text" name="price[1]" value=""  />
<input type="text" name="price[0]" value=""  />

次に、演算子(オペレーター)を複数設定する方法です。

演算子(オペレーター)も値と同じく、name属性に カスタムフィールド名@operator[] とすることで、複数指定することが可能です。

<input type="hidden" name="カスタムフィールド名@operator[]" value="演算子" />
<input type="hidden" name="カスタムフィールド名@operator[]" value="演算子" />

<!-- 添字を指定して、何個目の条件に対応する演算子なのかを明示的に設定することも可能 -->
<input type="hidden" name="カスタムフィールド名@operator[1]" value="演算子" />
<input type="hidden" name="カスタムフィールド名@operator[0]" value="演算子" />

最後に、結合子(コネクター)を複数設定する方法です。

結合子(コネクター)は、name 属性に カスタムフィールド名@connector とした input タグの値として指定することができます。

指定できる値は and または or になります。

<input type="hidden" name="カスタムフィールド名@connector[添字]" value="結合子(and または or)" />

添字の部分には、複数条件ある中の何個目の条件に対する結合子(コネクター)なのかを 0 から数えて指定します。また、1つ前の条件との論理関係を指定するため、添字に 0 を指定する意味はありません。

検索フォームのサンプルコード全体は次のようになります。

<!-- BEGIN_MODULE Field_Search -->
<form action="" method="post" class="acms-form">
  <!-- 宣言 -->
  <input type="hidden" name="field[]" value="price" />

  <table>
    <tr>
      <th>価格</th>
      <th>AND OR</th>
      <th>価格</th>
      <th>検索</th>
    </tr>
    <tr>
      <td>
        <select name="price@operator[0]">
          <option value="">選択しない</option>
          <option value="eq"{price@operator[0]:selected#eq}>=</option>
          <option value="neq"{price@operator[0]:selected#neq}>!=</option>
          <option value="lt"{price@operator[0]:selected#lt}>&lt;</option>
          <option value="lte"{price@operator[0]:selected#lte}>&lt;=</option>
          <option value="gt"{price@operator[0]:selected#gt}>&gt;</option>
          <option value="gte"{price@operator[0]:selected#gte}>&gt;=</option>
          <option value="lk"{price@operator[0]:selected#lk}>LIKE</option>
          <option value="nlk"{price@operator[0]:selected#nlk}>NOT LIKE</option>
          <option value="re"{price@operator[0]:selected#re}>REGEXP</option>
          <option value="nre"{price@operator[0]:selected#nre}>NOT REGEXP</option>
          <option value="em"{price@operator[0]:selected#em}>EMPTY</option>
          <option value="nem"{price@operator[0]:selected#nem}>NOT EMPTY</option>
        </select><br/>
        <input type="text" name="price[0]" value="{price[0]}" size="10" />
        円
      </td>
      <td>
        <label class="acms-form-radio">
        <input type="radio" name="price@connector[1]" value="and"{price@connector[1]:checked#and} /><i class="acms-ico-radio"></i>and</label>
        <label class="acms-form-radio">
        <input type="radio" name="price@connector[1]" value="or"{price@connector[1]:checked#or} /><i class="acms-ico-radio"></i>or</label>
        <input type="hidden" name="field[]" value="price@connector[1]" />
      </td>
      <td>
        <select name="price@operator[1]">
          <option value="">選択しない</option>
          <option value="eq"{price@operator[1]:selected#eq}>=</option>
          <option value="neq"{price@operator[1]:selected#neq}>!=</option>
          <option value="lt"{price@operator[1]:selected#lt}>&lt;</option>
          <option value="lte"{price@operator[1]:selected#lte}>&lt;=</option>
          <option value="gt"{price@operator[1]:selected#gt}>&gt;</option>
          <option value="gte"{price@operator[1]:selected#gte}>&gt;=</option>
          <option value="lk"{price@operator[1]:selected#lk}>LIKE</option>
          <option value="nlk"{price@operator[1]:selected#nlk}>NOT LIKE</option>
          <option value="re"{price@operator[1]:selected#re}>REGEXP</option>
          <option value="nre"{price@operator[1]:selected#nre}>NOT REGEXP</option>
          <option value="em"{price@operator[1]:selected#em}>EMPTY</option>
          <option value="nem"{price@operator[1]:selected#nem}>NOT EMPTY</option>
        </select><br/>
        <input type="text" name="price[1]" value="{price[1]}" size="10" />
        円
      </td>
      <td>
        <input type="hidden" name="cid" value="%{CID}" />
        <input type="hidden" name="bid" value="%{BID}" />
        <input type="submit" name="ACMS_POST_2GET" value="検索" class="acms-btn-admin" />
      </td>
    </tr>
  </table>
</form>
<!-- END_MODULE Field_Search -->

このコードでは、結合子(コネクター)のフォームをラジオボタンで実装することで、ユーザーが複数条件の論理関係を設定できるようにしています。

複数のフィールドから検索する

複数のカスタムフィールドのデータを複合して検索する方法です。

URLコンテキストの例

https://example.com/field/price/gte/1000/_and_/color/red/_and_/type/stationery

このURLコンテキストは、price というカスタムフィールドの値が1000以上かつ、color というカスタムフィールドの値が red かつ、type というカスタムフィールドの値が stationery という意味になります。

カスタムフィールドのURLコンテキストを構造化して表現すると、次のようになります。

[
  {
    "key": "price",
    "filters": [
      {
        "operator": "gte",
        "value": "1000",
        "connector": "and"
      }
    ],
    "separator": "_and_"
  },
  {
    "key": "color",
    "filters": [
      {
        "operator": "eq",
        "value": "red",
        "connector": "or"
      }
    ],
    "separator": "_and_"
  },
  {
    "key": "type",
    "filters": [
      {
        "operator": "eq",
        "value": "stationery",
        "connector": "or"
      }
    ],
    "separator": "_and_"
  }
]

また、「_or_」で繋げる事により、複数のフィールドのOR検索をすることができます。

https://example.com/field/price/gte/300/or/lte/150/_or_/color/lk/red/lk/blue/_or_/type/stationery

このURLコンテキストは、price というカスタムフィールドの値が300以上、または150以下であるまたは、color というカスタムフィールドの値が red を含む、かつ blue を含む、または、type というカスタムフィールドの値が stationery という意味になります。

このURLコンテキストを構造化して表現すると、次のようになります。

[
{
 "key": "price",
 "filters": [
   {
     "operator": "gte",
     "value": "300",
     "connector": "and"
   },
   {
     "operator": "lte",
     "value": "150",
     "connector": "or"
   }
 ],
 "separator": "_and_"
},
{
 "key": "color",
 "filters": [
   {
     "operator": "lk",
     "value": "red",
     "connector": "and"
   },
   {
     "operator": "lk",
     "value": "blue",
     "connector": "and"
   }
 ],
 "separator": "_or_"
},
{
 "key": "type",
 "filters": [
   {
     "operator": "eq",
     "value": "stationery",
     "connector": "or"
   }
 ],
 "separator": "_or_"
}
]

注意点として、AND検索とOR検索を混ぜて利用する場合、AND → OR の順番でのみ可能です。

検索フォームのサンプル

複数のカスタムフィールドのデータを複合して検索する場合、カスタムフィールド同士の論理関係を結合子(セパレーター)として指定する必要があります。

結合子(セパレーター)は、name 属性に カスタムフィールド名@separator とすることで、値として指定することができます。

<input type="hidden" name="カスタムフィールド名@separator" value="結合子" />
<!-- BEGIN_MODULE Field_Search -->
<form action="" method="post" class="acms-form">
  <table class="realestateSearch">
    <tr class="acms-hide-sp">
      <th>最寄り駅</th>
      <th>家賃</th>
      <th>検索</th>
    </tr>
    <tr>
      <td>
      <span class="acms-hide-md acms-hide-pc">最寄り駅</span>
      <select name="station">
        <option value="">未設定</option>
        <option value="あの駅"{station:selected#あの駅}>あの駅</option>
        <option value="この駅"{station:selected#この駅}>この駅</option>
        <option value="その駅"{station:selected#その駅}>その駅</option>
      </select>
      <input type="hidden" name="field[]" value="station" />
      </td>
      <td class="tableNowrap">
        <span class="acms-hide-md acms-hide-pc acms-show-sp">家賃</span>
        <select name="price[]">
          <option value="">下限なし</option>
          <option value="30000"{price[0]:selected#30000}>30,000円以上</option>
          <option value="50000"{price[0]:selected#50000}>50,000円以上</option>
          <option value="80000"{price[0]:selected#80000}>80,000円以上</option>
        </select>
       - 
        <select name="price[]">
          <option value="">上限なし</option>
          <option value="80000"{price[1]:selected#80000}>80,000円以下</option>
          <option value="50000"{price[1]:selected#50000}>50,000円以下</option>
          <option value="30000"{price[1]:selected#30000}>30,000円以下</option>
        </select>
        
        <input type="hidden" name="field[]" value="price" />
        <input type="hidden" name="price@operator[0]" value="gte" />
        <input type="hidden" name="price@operator[1]" value="lte" />
      </td>

      <td>
        <input type="hidden" name="cid" value="%{CID}" />
        <input type="hidden" name="bid" value="%{BID}" />
        <input type="hidden" name="price@separator" value="or" />
        <input type="submit" name="ACMS_POST_2GET" value="検索" class="btn btnSearchBlock" />
      </td>
    </tr>
  </table>
</form>
<!-- END_MODULE Field_Search -->

サンプルでは、カスタムフィールド station と price を複合して検索可能なフォームとなっています。

結合子(セパレーター)は結合子(コネクター)と同様に、1つ前の条件との論理関係を指定するため、station@separator ではなく、price@separator を指定しています。

まとめ

今回は、カスタムフィールド検索の仕様について改めて整理してみました。僕自身、既存のドキュメントだと分かりづらいと感じるところや、説明不足だと感じる点がありましたので改めて整理してみました。

カスタムフィールドのURLコンテキストがどのような意味を持つのかや、URLコンテキストを組み立てるためのフォームの仕様について細かく解説したので、カスタムフィールドによる検索フォームを実装する場合にはぜひ思い出していただけると幸いです。

余談ですが、a-blog cms のカスタムフィールド検索機能はデータを柔軟に検索でき便利ですが、現状のカスタムフィールド検索クエリをURLのパスで表現している都合上、検索したい値に 「/」が含まれていると、「\」によるエスケープが必要となるため、不便を感じる部分があります。

「/」が含まれている場合は自動で「\」によるエスケープを追加するほか、クエリパラメーターで検索できるようにしたりするようにして改善できないかななど考えています。

明日は株式会社 HAMWORKS(ハムワークス)の (´ ºムº `)さんの投稿です!お楽しみに!