Drupal8のViewsでタクソノミーの階層を辿る検索

# 概要
Viewsの検索機能でタクソノミーを階層ごとにAjaxで呼び出せるようにする方法です
文字で説明するとよく分からないので画像貼り付けます

![image.png](https://qiita-image-store.s3.amazonaws.com/0/203759/f82de2df-d091-e4fb-1a3e-b82d65467646.png)

タグのような自由追加が可能なタクソノミーより、カテゴリーのようなサイトオーナー側が作成するタクソノミーを想定しています
基本的にはモジュールを入れればできるのですが、細かい調整が難しいので説明していきます

# 準備
## 必要なモジュール
- [Simple hierarchical selectモジュール](https://www.drupal.org/project/shs)

## インストール
```shell:CommandLine
composer require drupal/shs
drush en shs
```

# 基本設定の手順
## タクソノミーを作成
ボキャブラリーを用意します
今回は新しく作成しますが、用いたいボキャブラリーがあればそのまま利用しても大丈夫です
![image.png](https://qiita-image-store.s3.amazonaws.com/0/203759/f02fae7c-6825-ba4b-ecff-4973e3694b94.png)

階層化するタームを用意していきます
これも既に用意してあればそのまま利用して大丈夫です
![image.png](https://qiita-image-store.s3.amazonaws.com/0/203759/8f2475b6-76ce-268d-f8d3-47b146f231db.png)
~~野菜が食生活をよく表していますね~~

## ノードの設定
ノードに作成したタクソノミのフィールドを用意します
今回は「記事」コンテンツタイプにフィールドを追加しています
![image.png](https://qiita-image-store.s3.amazonaws.com/0/203759/5d5b3cd3-4ff1-4a50-33d9-da15e8f123d8.png)

参照タイプで作成したボキャブラリを指定します
![image.png](https://qiita-image-store.s3.amazonaws.com/0/203759/d5bfcb34-4831-2911-19ed-a4a367c00c3e.png)

フォームの管理でウィジェットを「Simple hierarchical select」を指定しておくと、フォームでも階層のAjaxで選択できるようになります
![image.png](https://qiita-image-store.s3.amazonaws.com/0/203759/eb133557-98c9-1b86-d829-1b701d24cb98.png)

ウィジェットの右側の設定から「Force selection of deepest level」をチェックしてください
これにより、子要素を選択しないと保存できないようになります
![image.png](https://qiita-image-store.s3.amazonaws.com/0/203759/e472d69d-f452-6c5b-a9bb-b2a34931c14c.png)

## Viewsを作成
Viewsを作成します
タクソノミを設定したタイプを指定し、ページを作成するにチェックします
![image.png](https://qiita-image-store.s3.amazonaws.com/0/203759/f7cc4afb-589e-e034-77dd-1bf379ffaceb.png)

今回は表示する内容は関係なく、検索できることが目的なので「フィルターの条件」以外はそのまま使用します
なので、好きなように設定してください
![image.png](https://qiita-image-store.s3.amazonaws.com/0/203759/bebfde6d-8478-5bc8-aba2-5fdb23e32193.png)

「フィルターの条件」に「タクソノミータームを含む」を追加します
![image.png](https://qiita-image-store.s3.amazonaws.com/0/203759/e1c2744f-4b79-01a4-f51e-17c80fbc7d80.png)

作成したボキャブラリーと「Simple hierarchical select」を指定します
![image.png](https://qiita-image-store.s3.amazonaws.com/0/203759/8f77a9fa-d376-cd2c-433c-21e952f632dd.png)

「このフィルターを訪問者へ表示し、変更できるようにする」にチェックして保存します
![image.png](https://qiita-image-store.s3.amazonaws.com/0/203759/6069e664-2778-afa2-89b3-41130aa7121f.png)

階層を辿った検索ができあがっています
![image.png](https://qiita-image-store.s3.amazonaws.com/0/203759/0857eda0-969c-4e24-1a47-27d793991116.png)

## 問題点
検索機能はできましたが、ここで問題が生じます
まず、「果物 > バナナ」で最終階層まで検索すると普通に検索できるのが分かると思います
![image.png](https://qiita-image-store.s3.amazonaws.com/0/203759/5609d42b-425c-708e-0b0b-92859f470312.png)

一つ上の階層、「果物」で検索するとコンテンツが見つかりません
![image.png](https://qiita-image-store.s3.amazonaws.com/0/203759/8563b074-2287-8587-2352-cb4cb6515579.png)

上記の問題を解決するため、更に対応していきます

# 親要素の検索を可能にする
## ノードの設定
ノードに親要素を保存するためのフィールドを追加します
先ほどと同じように作成したボキャブラリーを設定しましょう
![image.png](https://qiita-image-store.s3.amazonaws.com/0/203759/5f44c59f-5c1c-ff73-2527-e65cf9b59fec.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/203759/de6d8f98-f853-d1ff-a2aa-69321c57b81b.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/203759/c9d6dbdb-ed48-a240-17b5-b7704d434e2a.png)

フォームの設定でフォームから直接編集できないように設定しておきます
![image.png](https://qiita-image-store.s3.amazonaws.com/0/203759/37a42958-afff-fcbb-8b23-a78a75258a91.png)

## 親要素を自動設定
簡単なモジュールを作成して、親要素を新しく作成したフィールドに自動作成します
よく分からない場合は、書いてある通りに作成すれば大丈夫です

モジュール名はcategory parentになっていますが、何でもいいです

```shell:CommandLine
mkdir -p modules/custom/category_parent
cd modules/custom/category_parent

vim category_parent.info.yml
vim category_parent.module
drush en category_parent
```

vimの部分はファイルへの書き込みなので、下のファイル名とコードを参照して入力（コピペなり）してください
`i`で入力モード、`Esc`でモード離脱、`Shift + Insert`で貼り付け、`:wq`で保存して終了です

```yml:category_parent.info.yml
name: "Category Parent"
type: module
core: 8.x
```

`$type`, `$field`, `$parent`は使用するコンテンツタイプ、タクソノミーフィールド、親要素のタクソノミーフィールドのシステム名を指定してください

```php:category_parent.module
<?php

/**
 * Implements hook_node_presave().
 */
function category_parent_node_presave(\Drupal\Core\Entity\EntityInterface $node) {
  $type = 'article';
  $field = 'field_category';
  $parent = 'field_category_parent';

  if ($node->getType() === $type) {
    if (!empty($node->{$field}->target_id)) {
      $term = \Drupal::entityTypeManager()
        ->getStorage('taxonomy_term')
        ->loadParents($node->{$field}->target_id);

      $node->{$parent}->entity = reset($term);
    }
  }
}
```

## コンテンツに適用する
`hook_node_presave()`の設定はコンテンツを作成・編集する際に実行され、ノードを書き換えます
そのため、モジュールを有効化しただけでは、親タームの設定は行われていません

コンテンツページ（/admin/content）のアクションから「コンテンツの保存」で選択したアイテムに適用すれば、チェックした全てのコンテンツに実行できます
（このとき「コンテンツの削除」とかしないように注意しましょう）
![image.png](https://qiita-image-store.s3.amazonaws.com/0/203759/5b1e8f05-0ee6-9508-d7d4-7afeb8ce23d6.png)

## 検索の確認
作成した検索機能で親要素だけで検索してみましょう
親要素でも子要素でも検索できるようになっているのが確認できるかと思います
![image.png](https://qiita-image-store.s3.amazonaws.com/0/203759/8683fa76-5cc9-3c0e-c60b-65f3fccaa525.png)

# 検索機能をブロック化したい
番外編で検索フォームをブロックにする方法です
作成したViewsの設定ページの「高度」「外部設置フォーム」の「外部設置フォームをブロックで提供」を「はい」に設定します
![image.png](https://qiita-image-store.s3.amazonaws.com/0/203759/b5cc2e0c-bbd5-cd9a-ddec-a5eaa55b1a25.png)

ブロックで検索フォームが利用できるようになります！
![image.png](https://qiita-image-store.s3.amazonaws.com/0/203759/08c97201-e7a5-db77-83cf-12134919f341.png)