WordPressを高機能エディタとして利用し、Firebaseと連携させる

WordPressはそのお手軽さと拡張性の高さで、BlogやCMSとして重宝されていますが、単純に入力フォームとして見た場合も有用だったりします。WordPressを入力フォームとしてのみ利用し、FirebaseへPOST/連携させるという、少々ニッチな仕組みを作ったので、そのノウハウと手順を紹介します。

## 入力画面にWordPressを使う理由

### WordPressの選定ポイント
長期的に運用していくサービスへの導入を検討していく上で、他のCMSと比較して、以下のポイントが決め手となりました。

* 汎用性：LAMP環境下ならほとんどのサーバで動作する汎用性の高さ
* 信頼性：運用実績が多数あり、信頼性が高い
* 拡張性：拡張性が高く、カスタマイズが容易、膨大なプラグイン資産が使える
* 情報量：コミュニティが活発で情報量が多く、エラーが発生した際や開発時に解決できる可能性が高い

### Advanced Custom Field Pro(ACF Pro)
WordPressの入力画面としての優秀さを際立たせるのがカスタムフィールドを拡張する[Advanced Custom Field Pro](https://www.advancedcustomfields.com/pro/)です。有料ですがDeveloperライセンスを買えば制限なく他の案件に使えます。  
とくに優秀な機能として、Repeater Field（繰り返し入力フィールド）、Flexible Content（柔軟コンテンツ）は、他の入力システムと一線を画す優秀な機能です。このプラグインがあるからWordPressを使っているんだ！といっても過言ではありません。

[ACF Pro](https://www.advancedcustomfields.com/pro/)

### セキュリティリスクについて
WordPressのウィークポイントとして、オープンソースで普及率が高いため、利用にはある程度のセキュリティリスクが伴います。しかし、あくまで入力フォームとして使う構成とし、WordPressをインストールしているサーバとフロントエンドを表示するサーバとを分ければ問題ないと判断しました。ベーシック認証で担当者以外のアクセスを弾く、WAFを導入するなどの対策をした上で運用することを推奨します。

## 構成

![firebase-database-wordpress.png](https://qiita-image-store.s3.amazonaws.com/0/22445/c2835ff0-9cb6-6915-2449-3c3594761816.png)

構成は図にするとこんな感じです。WordPressで作成したデータ（商品データや記事データなどの不変データ）を一方通行でFirebaseに受け渡すフローとなります。ユーザデータやフロントエンドで生成されたデータはFireabaseと双方向で連携させています。

| 役割 |  ソフトウェア・サービス |
|:-----------------|:------------------|
| 記事入力画面       | WordPress / LAMP環境サーバ |
| 画像格納     　　　　　　　　　　　| Firebase Storage |
| データベース　　　　　     　| Firebase Realtime Database |
| フロントエンド        | Firebase Hosting（htmlが表示できるサーバならなんでも） |



## WordPress->Firebaseのためのコード作成
### 1.データ作成処理（WordPress）

WordPressのお作法に習い、themeフォルダー内またはwp-loadを使って記事のデータ生成を行います。今回はデータベースへの登録と同時に、記事内で登録された画像もStorageに登録させる必要があったので、Storageも対応している[kreait/firebase-php](https://github.com/kreait/firebase-php)ライブラリの利用を前提とした実装になります。Composer経由でダウンロードするか、gitから直接ダウンロードして読み込ませます。

#### セットアップ

```php:firebaseSync.php
equire '/xxx/autoload.php';
require_once '/xxx/wp-load.php';

// firebase-phpのnamespace設定
use Kreait\Firebase;
use Kreait\Firebase\Factory;
use Kreait\Firebase\ServiceAccount;

/* 
* Firebase Admin SDKのサービスアカウントを登録
* https://firebase.google.com/docs/admin/setup?hl=ja
*/
$serviceAccount = ServiceAccount::fromJsonFile(__DIR__.'/fireabase-admin-json/xxxxxxx-adminsdk-xxxxxxxxx.json');

```

#### ループ処理によるデータ生成
Databaseへ登録するデータの生成と同時にStorageアップロード用の画像を格納した配列を作成します。どちらの配列も連想配列にしているのは、重複データを弾くためです。

```php:firebaseSync.php
$partsQueryKey = array(
  'post_status'    => 'publish',
  'order'          => 'DESC',
  'post_type'      => 'xxxx',
  'posts_per_page' => 999
);

$query = new WP_Query($partsQueryKey);
$databaseArray = []; // Firebase Databaseへ登録する連想配列
$storageArray = []; // Firebase Storageへ登録する画像データ用連想配列
$num = 0;

/* 
* WordPressのループ処理でデータ作成
*/
while($query->have_posts()){
  $query->the_post();
  $id = get_the_ID();

  // Database登録用データ
  $databaseArray[$id]['id'] = $id;
  $databaseArray[$id]['name'] = get_the_title();
  $databaseArray[$id]['mainImage'] = get_field('mainImage')['filename'];

  // Storage登録用データ
  $storageArray[$brandsData[$id]['mainImage']] = array(
    'modified' => get_field('mainImage')['modified'], // 画像更新日付を取得
    'src' => get_field('mainImage')['url'] //　画像URLを取得
  );

  $num++;
}
```


### 2. データ加工・アップロード（Firebase）

データ生成が終わったら、データの加工処理（必要であれば）とfirebase-phpを使ったデータアップロード処理を行います。
Databaseへは``getReference``を使うことで簡単にPOSTできますが、Storageは**Flysystem**というComposerライブラリで定義されている、**Filesystem API**に則り操作する必要があります。ここでは``put``と``listContents``のみしか使っていませんので、より詳しく知りたい場合は以下のリンクから確認してください。

[Filesystem API | Flysystem](https://flysystem.thephpleague.com/docs/usage/filesystem-api/)

なにも工夫しないで実装した場合、投稿や同期時に、すべての画像がアップロードされてしまいますが、今回のコードではアップロードさせたい画像の連想配列に``modified（画像の更新日時）``を含めて、すでにFirebase StorageへPOSTしている画像の``modified``と比較して、差分配列を作成し、必要のない画像を投稿しないようにしています。
※このサンプルでは、Databaseは``item``配下に、Storageは``img``配下にPOSTされるように設定しています。


```php:firebaseSync.php

$firebase = (new Factory)
  ->withServiceAccount($serviceAccount)
  ->create(); // firebaseインスタンス作成

$database = $firebase->getDatabase();
$storage = $firebase->getStorage();
$filesystem = $storage->getFilesystem();
$firebaseImageList = $filesystem->listContents('img/'); //アップロードしたいfirebase storageのディレクトリ内画像一覧を取得

// アップロードする画像のみを抽出
foreach ($storageArray as $key => $value) {
  $fimename = $key;
  $timestamp = strtotime($value['modified']);

  foreach ($firebaseImageList as $value) {
    if($fimename == $value['basename'] && $timestamp <= $value['timestamp']) { // 差分抽出
      unset($storageArray[$fimename]);
    }
  }
}

// Storageへの画像アップロード処理（差分処理）
foreach ($storageArray as $key => $value) {
  if($key != null && $key != '') {
    $filename = $key;
    $src = file_get_contents($value['src'], false, stream_context_create($options));
    $filesystem->put('img/' . $filename, $src); 
  }
}

// Databaseへのアップロード処理
$database->getReference('items')->set($itemsData);
``` 
最終コードはgistにアップしています。
https://gist.github.com/kgsi/e86123a9e5c6d841ad7c0e153d6358de


## 所感　
あまり参考記事もなく、一風変わった構成となった気がしますが、WordPressを使い慣れている自分にとって、知識のない他のCMSを使うよりも実装がしやすく、お手軽感がありました。投稿毎にFirebaseへPOSTするのかどうか、全体の書き換えを行わないよう工夫をする必要があったりと、少々手間がかかる部分もありますが、WordPressのカスタム仕様を理解していれば実装は容易かと思います。 

自分の場合は、投稿の度にイベントフックして投稿させるよりも、一括してデータを同期させる方法を選びました。以下のようなWidgetを登録し、簡単に同期できるようにしています。

![wordpress-sync-widget.png](https://qiita-image-store.s3.amazonaws.com/0/22445/441b67a5-a87f-07e9-f36b-7ec9d18876bc.png)