[Drupal] Views Bulk Operationにカスタムのアクションを追加する方法

## Views Bulk Operation(VBO)って何
コンテンツ一覧やユーザー一覧はデフォルトで画像のように複数のエンティティに対して一括でアクション（操作）を行えるようになっていますが、
![views_action.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253501/a54df491-6153-fba4-db23-58884f9a71ad.png)

[Views Bulk Operation](https://www.drupal.org/project/views_bulk_operations)を使うと他のビューにもこのようなチェックボックスとアクション選択フォームを追加することができます。

使い方は簡単で、VBOをインストール→任意のビューのフィールド設定から`views bulk operations`を追加すれば完了なのですが...
![スクリーンショット 2021-03-23 23.12.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253501/6a078a3d-b76d-2c33-6172-e39ec04fb94b.png)

デフォルトで選べるアクションが10種類くらいしか無く。
![スクリーンショット 2021-03-23 23.13.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253501/d3a19138-0bfd-6bb7-1a13-389bf754a8c4.png)

これ以外のアクションを使えるようにする場合はカスタムアクションを作る必要があります。（例: 「選択されたコンテンツのフィールドAの値をXXにする」「選択されたコンテンツのモデレーション状態をXXにする」等）

というわけで今回はカスタムのアクションを追加する方法を紹介したいと思います。

## カスタムのアクションを追加する方法

今回は例として「選択されたコンテンツのフィールドAの値を◯◯にする」というアクションを追加してみます。

### プラグインファイルの作成
カスタムモジュールに`/src/Plugin/Action`というディレクトリを作成し、`MyAction.php`ファイルを配置します。必要なファイルはこれだけです。ファイル名（=クラス名）はControllerなどと同様に〇〇Actionで終わる方がわかりやすいですが、適当でも機能的には問題ありません。

### MyAction.phpを作成
プラグインクラスを書いていきます。

まず、通常のプラグインと同様にアノテーションを書きます。`type="node"`でノードのビューにしかこのアクションを適用できないようにしています。他には`requirements`というプロパティでこのアクションを実行できるユーザーを制限することができます。今回はこのビューにアクセスできる人は誰でもこのアクションを実行できるようにしたいので`requirements`は定義しません。詳しくは[公式ドキュメント](https://www.drupal.org/docs/contributed-modules/views-bulk-operations-vbo/creating-a-new-action)をご覧ください。

クラスは`Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionBase`を継承します。（`Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionInterface`を埋め込めば継承しなくてもOK）

```php
<?php

namespace Drupal\my_module\Plugin\Action;
use Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionBase;

/**
 * Myアクションを定義する.
 *
 * @Action(
 *   id = "my_module_my_action",
 *   label = "フィールドAの値をtestにする",
 *   type = "node",
 * )
 */
class MyAction extends ViewsBulkOperationsActionBase {
```
必須のメソッドを書いていきます。まずは`execute`メソッドです。引数の`$entity`でエンティティが渡されるので、このエンティティに対して好きな処理を施します。（エンティティはノードの一覧ならノード、タクソノミーの一覧ならタクソノミーが渡ってきます）

```php

 /**
   * {@inheritdoc}
   */
  public function execute($entity = NULL) {
    if ($entity->bundle() == 'article') {
      $entity->set('field_a', 'test')->save();
    }
    return 'フィールドAの値を更新しました。';
  }
```

時間記録などループの前後で処理を行いたい場合は`execute`の呼び出し元の`executeMultiple`を上書きします。

(`ViewsBulkOperationsActionBase`の`executeMultiple`)

```php
/**
   * {@inheritdoc}
   */
  public function executeMultiple(array $objects) {
    $results = [];
    foreach ($objects as $entity) {
      $results[] = $this->execute($entity);
    }

    return $results;
  }
```

次に`access`メソッドを定義します。ここで個々のエンティティに対するアクセスチェックを行ってると思われます。（間違ってたらすいません。）今回は特にエンティティごとの制御は行わないので`AccessResult::neutral()`（意見なし）を返しておきます。

```php
use Drupal\Core\Access\AccessResult;

/**
   * {@inheritdoc}
   */
  public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
    return AccessResult::neutral();
  }
```

全部つなげるとこんな感じです。(今回は`executeMultiple`は未使用)

```php
<?php

namespace Drupal\my_module\Plugin\Action;

use Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionBase;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Access\AccessResult;

/**
 * Myアクションを定義する.
 *
 * @Action(
 *   id = "my_module_my_action",
 *   label = "フィールドAの値を'test'にする",
 *   type = "node",
 * )
 */
class MyAction extends ViewsBulkOperationsActionBase {

  /**
   * {@inheritdoc}
   */
  public function execute($entity = NULL) {
    if ($entity->bundle() == 'article') {
      $entity->set('field_a', 'test')->save();
    }
    return 'フィールドAの値を更新しました。';
  }

  /**
   * {@inheritdoc}
   */
  public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
    return AccessResult::neutral();
  }

}

```
これでビューのVBOの設定にこのアクションが追加されます。
![スクリーンショット 2021-03-24 0.32.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253501/7e9bf73c-8333-1550-1be0-800cc260a670.png)

ビューの画面ではこんな感じに表示されます。

![スクリーンショット 2021-03-24 0.33.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253501/80d1e397-2154-5b42-999d-fef8584afc05.png)

一括処理終了後のステータスメッセージ。
![スクリーンショット 2021-03-24 0.43.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253501/60b9026f-454b-60b4-96ff-7db88426523a.png)

アクションを実行するとフィールド値もちゃんと更新されます :ok_hand:
![スクリーンショット 2021-03-24 0.46.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253501/8f32b4d4-5a34-0e0a-9fec-18312cf60cb6.png)

というわけでVBOにカスタムのアクションを追加する方法の紹介でした。