カスタムタクソノミーページの作成

賃貸物件の「エリアから探す」のページを作成します。ディレクトリ構成は、

※図が表示されない場合は、ブラウザを最新版にしてください。

となります。これらのページは全て1つのビュー(物件リストビュー)で作成します。但し、前の2つはカテゴリーリストなので、これらについては内容は別のビューで作成し、それをブロックとしてページに配置する形となります。

ここで使用するビューは、前回までに作成した「賃貸物件リスト」ビューに、ディスプレイを追加する形で作成します。ディスプレイとは、ビューで生成したコンテンツリストを、ページとして表示するのか、ブロックとして出力するのか、RSS フィードとして出力するのかといった、出力方式のことです。これらの出力方式は、それぞれ複数設置出来ます。

ディスプレイの追加

前回までに作成した「賃貸物件リスト」ビューの編集画面(「管理」 > 「サイト構築」 > 「Views」 > 「賃貸物件リスト」)にアクセスしてください。

左上「Page」の脇の「+Add」をクリックし、「ページ」を選択します。

「ページ」ディスプレイが追加されました。ページを2つ設定することになるので、分かりやすくするために、ディスプレイの名前を変更します。左上「Display name」をクリックして、「ページ - エリアから探す」に変更してください。ついでなので、元のページディスプレイ「Page」も、「ページ - 賃貸物件トップ」のように、名前を変更しておきます。

次に「ページ - エリアから探す」ディスプレイにパスを設定します。真ん中 PAGE SETTINGS の「パス」のところをクリックし、「forrent/area」と入力してください。とりあえずここまでで、ビューの編集画面右上「保存」ボタンをクリックして、ビューの設定を保存してください。

'/forrent/area' にアクセスすると、前回までに作成したページ '/forrent' と全く同じページが表示されます。これは、ディスプレイがビューの設定を継承しているためです(※1)

次に、メニューの設定をします。ビューの編集画面にアクセスし、PAGE SETTINGS の「メニュー」のところをクリックしてください。

メニューの設定画面です。タイプのところは「Menu tab」にチェックを入れ、タイトルに「エリアから探す」と入力、少しスクロールして、ウエイトに「1」を入力し、「Apply」ボタンをクリックします。ビューの設定画面に戻ったら、画面右上「保存」ボタンをクリックして、ビューの設定を保存してください。

ページにアクセスすると、「エリアから探す」というタブが表示されました。これは「ローカルメニュータブ」というもので、ページの階層構造を適切に判断し、アクセス性を高めるためのショートカットリンクです。ページの階層構造を表すものとしては、パンくずも適切に表示されています。

「エリアから探す」タブの隣には、「賃貸物件」タブも表示されています。これは以前作成したデフォルトメニュータブです。「エリアから探す」の上位のページ('/forrent')にリンクしています。(※2)

次に、ページのタイトルを設定します。ビューの編集画面、「ページ - エリアから探す」ディスプレイで、「タイトル」のところをクリックし、「エリアから探す」と入力します。そのままビューの設定を保存してください。

コンテキストフィルタの設定

コンテキストフィルタとは、URL からの引数の値によって、表示するリストに動的にフィルタリングを加えるものです。URL からの引数とは、ビューに設定した「パス」の後に続く文字列を、スラッシュ('/')で区切った値です。

例えば、このページの最初に示した「ディレクトリ構成」で、「{都市名}で探す ( /forrent/area/{県名}/{カテゴリID} )」のページの場合、このビューのパスは 'forrent/area' なので、URL からの引数は 1つ目の値が {県名} の部分、2つ目の値が {カテゴリID} の部分となります。

実際に追加してみましょう。

ビューの編集画面の右側「CONTEXTUAL FILTERS」のところの「追加」ボタンをクリック。

通常は、ここで追加するハンドラは、引数の値に応じて一覧リストに何らかのフィルタリングを行いますが、今回の 1つ目の引数(県名)に関しては特別に、何もフィルタリングを行わないハンドラを追加します。

ここで左上 For のセレクトボックスは「This page (override)」を選択してください。もとの「All display」のままだと、元のビュー(「ページ - 賃貸物件トップ」のディスプレイ)にも、変更が反映されてしまいます。フィルターには「Global」を選択し、「Global: Null」という項目にチェックを入れて「Apply (this display)」ボタンをクリック。

「Global: Null」の設定画面です。WHEN THE FILTER VALUE IS NOT IN THE URL(引数がない場合の設定)のところは「Display contents of "No results found"」にチェックを入れます。

スクロールして WHEN THE FILTER VALUE IS IN THE URL OR A DEFAULT IS PROVIDED のところでは、Override title にチェックを入れて、「%1 で探す」と入力します。

ここでいったん、ビューの設定を保存して、作成したページ(/forrent/area)にアクセスしてみましょう。

図のように、公開フィルターと「お探しの物件が見つかりませんでした。」のテキストが表示されました。この「お探しの ... 」のテキストは図10 の「引数がない場合の設定」で、設定画面の NO RESULTS BEHAVIOR のところに設定した内容が表示されているものです。前述したように、このページはカテゴリーリストにしたいので、カテゴリーリストのビューを別に作り、それをブロックとして配置するようにします。そのためこのビューは、このページには何も表示しないようにします。

設定画面に戻り、NO RESULTS BEHAVIOR の内容を編集します。

For のところを「This page (override)」を選択し、テキストフォーマットに「PHP code」を選択します。内容を以下のように編集してください。

<?php
$args = arg();
if (!empty($args[3])) {
  print 'お探しの物件は見つかりませんでした。';
}
?>

これで、物件リストを表示するページで物件が無いときだけ、「お探しの物件は ... 」のテキストが表示され、何も表示しないページでは何も表示しなくなります。

あと、公開フィルターもこのページには表示しないようにしたいので、これをブロック表示にします。

EXPOSED FORM の Exposed form in block のところの「いいえ」のところをクリックし、設定画面で「はい」に変更してください。これで公開フィルターがブロック表示となり、ブロックの設定でどのページに表示させるかを制御できるようになります。

ビューの設定を保存し、再び /forrent/area のページにアクセスすると、何も表示されなくなりました。ここにはあとで、カテゴリリストを別のビューで作成し、そのブロックを配置します。

ここまでが1つめの引数の、値が無い場合の設定です。それでは引数の値がある場合はどうでしょうか。/forrent/area/chiba のページにアクセスしてください。

URL からの引数の値は「chiba」ということになります。ページのタイトルが「chiba で探す」となっていますが、これは図11のところの設定で、「%1 で探す」としたのが、URL からの引数の値で置き換わったものです。これを「千葉県 で探す」のように URLからの引数そのものではなく、対応する文字列に置き換えるようにしたいと思います。

これはデフォルトの「Global: Null」のハンドラでは出来ないので、「Global: Null」のハンドラをもとにしたカスタムハンドラを作って適用させることにします。

カスタムモジュールのフォルダに、Views のカスタマイズ用フォルダ 'views' を作成してください('custom/views')。次に、その中に 'custom_handler_argument_null_taxonomy_field_path_name.inc' という名前のファイルを作成します。内容は以下の通りです。

<?php
/**
 *
 * 「null」のハンドラです。クエリには影響しません。
 * 引数に「パスに使用する名前」を受け取り、そのタームの名前をタイトル(ページタイトルや、パンくずに使用されます)に設定する。
 *
 */
class custom_handler_argument_null_taxonomy_field_path_name extends views_handler_argument_null {

  /**
   * Override the behavior of title().
   * 「パスに使用する名前」から、タームの名前を取得。
   */
  function title() {
    if ($this->argument) {
      $query = db_select('taxonomy_term_data', 'td');
      $query->join('field_data_field_path_name', 'fpn',
        'td.tid = fpn.entity_id AND (fpn.entity_type = :entity_type AND fpn.deleted = :deleted)',
        array(':entity_type' => 'taxonomy_term', ':deleted' => '0'));
      $term_name = $query
        ->fields('td', array('name'))
        ->condition('fpn.field_path_name_value', $this->argument)
        ->execute()
        ->fetchField();

      if (!empty($term_name)) {
        return check_plain($term_name);
      }
    }
    // TODO review text
    return t('No name');
  }
}

これを Views に認識させるよう、custom.views.inc という名前のファイルを views フォルダに作成し('custom/views/custom.views.inc')、以下のように記述します。

<?php

/**
 * Implementation of hook_views_data()
 */
function custom_views_data() {
  // グローバル設定を追加
  $data['custom']['table']['group'] = t('Global');
  $data['custom']['table']['join'] = array('#global' => array());

  // パンくずの生成で、「パスに使用する名前」を、タームの名称で表示させるため、特別なハンドラを設置します。
  $data['custom']['null_field_path_name_value'] = array(
    'title' => t('(null)パスに使用する名前 (field_path_name)'),
    'help' => t('null と同様、クエリに影響しません。パンくずの生成で、「パスに使用する名前」を、タームの名称で表示させるのに使用します。'),
    'argument' => array(
      'handler' => 'custom_handler_argument_null_taxonomy_field_path_name',
    ),
  );

  return $data;
}

{モジュール名}.views.inc は、Views で使用するカスタムハンドラや、カスタムプラグインなどを Views に読み込ませるためのフックを記述する場所です。

custom.views.inc の場所をViews に指示するため、カスタムモジュール('custom/custom.module')に以下のコードを追加します。

/**
 * Implementation of hook_views_api()
 */
function custom_views_api() {
  return array(
    'api' => 3.0,
    'path' => drupal_get_path('module', 'custom') .'/views',
    'template path' => drupal_get_path('module', 'custom') .'/views/templates',
  );
}

これは、hook_views_api() というフックです。Views の機能を拡張するのに必要なメソッドとなります。また、作成したカスタムハンドラの場所を指定するため、custom.info('custom/custom.info')に以下の記述を追加します。

files[] = views/custom_handler_argument_null_taxonomy_field_path_name.inc

ここまで設定したら、モジュール管理ページ(「管理」 > 「モジュール」)からモジュールの設定を再構築して、そのあとキャッシュをクリアしてください。

ビューの編集画面に戻り、「CONTEXTUAL FILTERS」のところの「追加」ボタンをクリック。追加画面に、今作成した「Global: (null)パスに使用する名前 (field_path_name)」という項目が入ってますので、これを追加し、最初に作成した「Global: Null」と入れ替えます。設定は「Global: Null」と同じようにしてください。追加したら、「Global: Null」は削除します。

タイトルが「千葉県 で探す」になりました。

続いて 2つめの引数の設定を行います。2つめの引数も、一部特殊な設定が必要なため、カスタムハンドラを作成します。カスタムモジュールの views フォルダ('custom/views')に、'custom_handler_argument_term_node_tid_depth.inc' という名前のファイルを作成し、内容を以下のように記述してください。

<?php
/**
 *
 * パンくずの生成は、最上位のタームはパンくずに追加しないようにしています
 *
 */
class custom_handler_argument_term_node_tid_depth extends views_handler_argument_term_node_tid_depth {

  /**
   * タクソノミータームの最上位のタームはパンくずに追加しないようにする。
   */
  function set_breadcrumb(&$breadcrumb) {
    if (empty($this->options['set_breadcrumb']) || !is_numeric($this->argument)) {
      return;
    }

    return custom_taxonomy_set_breadcrumb($breadcrumb, $this);
  }
}

これを Views に認識させるよう、custom.views.inc に、以下のコードを追加します。

/**
 * Implementation of hook_views_data_alter()
 */
function custom_views_data_alter(&$data) {
  // この時点ではまだ term_node_tid_depth は $data に追加されていないので、オリジナルを変更するのではなく、
  // カスタム版として設置します。
  $data['node']['custom_term_node_tid_depth'] = array(
    'help' => t('Display content if it has the selected taxonomy terms, or children of the selected terms. Due to additional complexity, this has fewer options than the versions without depth.(パンくずをカスタム調整。)'),
    'real field' => 'nid',
    'argument' => array(
      'title' => t('Has taxonomy term ID (with depth)(カスタム)'),
      'handler' => 'custom_handler_argument_term_node_tid_depth',
      'accept depth modifier' => TRUE,
    ),
  );
}

/**
 * タクソノミーのパンくず生成用のヘルパー関数。
 * デフォルトの views_taxonomy_set_breadcrumb() をカスタマイズ。
 * 最上位のタームはパンくずに追加しないようにする。
 */
function custom_taxonomy_set_breadcrumb(&$breadcrumb, &$argument) {
  if (empty($argument->options['set_breadcrumb'])) {
    return;
  }

  $args = $argument->view->args;
  $parents = taxonomy_get_parents_all($argument->argument);
  foreach (array_reverse($parents) as $parent) {
    // Unfortunately parents includes the current argument. Skip.
    // 最上位のタームはパンくずに追加しない。
    $parent_parent = taxonomy_get_parents($parent->tid);
    if ($parent->tid == $argument->argument || empty($parent_parent)) {
      continue;
    }
    if (!empty($argument->options['use_taxonomy_term_path'])) {
      $path = taxonomy_term_uri($parent);
      $path = $path['path'];
    }
    else {
      $args[$argument->position] = $parent->tid;
      $path = $argument->view->get_url($args);
    }
    $breadcrumb[$path] = check_plain($parent->name);
  }
}

custom.info に、以下の記述を追加します。

files[] = views/custom_handler_argument_term_node_tid_depth.inc

モジュールの設定を再構築し、キャッシュをクリアしてください。

ビューの編集画面に戻ります。右側「CONTEXTUAL FILTERS」のところの「追加」ボタンをクリック(図8)。

「CONTEXTUAL FILTERS」の設定画面です。For のセレクトボックスに、「This page (override)」が選択されているのを確認、フィルターは「コンテンツ」を選択し、「コンテンツ: Has taxonomy term ID (with depth)(カスタム)」という項目を探してチェックを入れます。これが今作成したカスタムフィルターです。

「コンテンツ: Has taxonomy term ID (with depth)(カスタム)」の設定画面です。For のセレクトボックスに「This page (override)」が選択されていることを確認、Depth は「1」を選択します。Allow multiple values と、少しスクロールして Set the breadcrumb for the term parents のところにチェックを入れ、Use Drupal's taxonomy term path to create breadcrumb links にはチェックを入れないようにします。

さらにスクロールして、WHEN THE FILTER VALUE IS NOT IN THE URL(引数が無い場合の設定)のところは「Display contents of "No results found"」にチェックを入れます。EXCEPTIONS のところは「%」と入力します。

さらにスクロールし、WHEN THE FILTER VALUE IS IN THE URL OR A DEFAULT IS PROVIDED(引数がある場合の設定)では、Override title にチェック、「%2 の物件一覧」と入力、Override breadcrumb にチェック、「%1で探す」と入力します。

さらにスクロールし、Specify validation criteria にチェックを入れ、Validator は「タクソノミーターム」を選択、ボキャブラリは「エリアから探す」にチェックを入れます。Filter value type は「Term IDs separated by , or + 」を選択し、Action to take if filter value does not validate(検証にパスしなかった場合の処置)には「Show "Page not found"」を選択します。

「Apply (this display)」ボタンをクリックしてください。

{県名} で探す('forrent/area/{県名}')のページにアクセスすると、ここも何も表示されないページとなりました。このビュー(パスは 'forrent/area')に渡される、2つめの引数が無い場合ということになります。1つめの引数のときと同様、ここにはあとで、カテゴリリストを別のビューで作成し、そのブロックを配置します。ディレクトリ構成を表す「パンくず」も、適切に構築されています。

引数がある場合を見てみましょう。{都市名} で探す('forrent/area/{県名}/{カテゴリ ID}')にアクセスすると、{カテゴリ ID}(タクソノミーターム ID)のすべての子タームに登録された物件が表示され、{地域名}で探す('forrent/area/{県名}/{カテゴリ ID}')にアクセスすると、{カテゴリ ID}(タクソノミーターム ID)のタームに登録された物件が表示されます。パンくずも適切に構築されています。

物件一覧の「所在地」のリンクをクリックすると、{地域名}で探す('forrent/area/{県名}/{カテゴリ ID}')のページにアクセスすることが出来ます。


※1 ビューにはそれぞれ元となるマスターディスプレイがあり、マスターディスプレイの設定は基本的に、そのビューの全てのディスプレイに継承します。マスターディスプレイは、デフォルトではビューの編集画面にありませんが、Views の設定を変更することで確認出来ます。

※2 ローカルメニュータブには2つのルールがあります。ひとつは「デフォルトメニュータブ(上位のページにアクセスしたときにアクティブとなるページ)を1つ設置する」、もうひとつは「ローカルメニュータブは、少なくとも2つ以上設置する」というものです。この2つの条件が揃わないと、ローカルメニュータブは表示されません。

コメント

素晴らしい、チュートリアルをありがとうございます。
ものすご~く勉強になります。

こんにちは!

初歩レベルの素人ですが、このチュートリアルでものすごく勉強になっております。
ありがとうございます。

このページを参考に「エリアから探す」ページを作成し、記載通りの動作までは
確認できました。更に試しに「沿線から探す」のページを作ってみているのですが、
「CONTEXTUAL FILTERS」の設定項目「コンテンツ: Has taxonomy term ID
(with depth)(カスタム)」の中のボキャブラリ設定を単純に「沿線から探す」へ
変更しただけでは'forrent/area/{鉄道分類}/{カテゴリ ID}'でアクセスした場合に
タームに登録されている物件が見つかりません。

ここの処理はどのようにすればいいのでしょうか?

field_stationはfield_collection内で定義されているため、直接絞り込みできないから
ではないかと思っているのですが、具体的な方法が思いつきません。
(思いついた理由が全然見当違いかもしれませんが....)

宜しくお願いします。

チュートリアルを参考にして頂きまして、ありがとうございます。

「沿線から探す」のページですが、お察しの通り、

> field_stationはfield_collection内で定義されているため、直接絞り込みできないから
> ではないかと思っているのですが

そのとおりです。

「エリアから探す」のビューでは、CONTEXTUAL FILTERS の第2引数に「コンテンツ: Has taxonomy term ID (with depth)(カスタム)」を置いていますが、これはコンテンツ(ノード)を絞り込むハンドラなので、「沿線から探す」の場合は、これのフィールドコレクション版を、代わりに置きます。

フィールドコレクション版のハンドラは、自作するしかないのですが、Views モジュールの中の、/modules/taxonomy/views_handler_argument_term_node_tid_depth.inc を参考にすれば良いと思います。

少し敷居が高いですが、これまでの応用ですので、頑張ってください。

※ コメントの受け付けは終了しました。