baserCMS + HangoutsChat の通知プラグイン

突然ですが、皆さんはこまめにメールをチェックしていますか？
私はプログラミングや作業に夢中になるとついついメールの確認をなおざりにしてしまいます。

でもプログラムのエラーって大体メールに通知が来るようにするもの。
昼間それに気付かず、**夜遅く帰ろうとした時にエラーを発見した日**にはもう……！
せめてチャットとかで通知が来てくれれば、ズボラな人や整理が苦手な人でも気付けるのに……

ということで、この記事では **[baserCMS](https://basercms.net/)** という **CakePHP** で作られているCMSにその機能を実装してみます！通知先のチャットツールはGoogleの **HangoutsChat** にしました。
今回はbaserCMSにプラグインとして実装する方法をご紹介しますが、CakePHPで作られたプログラムあればbaserCMSに限らず少し直すだけで取り入れられると思います。気になる方は是非お試しください。

## HangoutsChat のWebhook設定

### HangoutsChat で通知を受信する準備
はじめにHangoutsChatで通知を受け取る場所を用意しましょう。
イメージとしてはPHPからHangoutsChatのプログラムにPOST送信を行います。
このPOSTを受け取るものを **Webhook** といいます。チャットルームにWebhookを追加することで、そのチャットルームに対してPOSTを送信し通知を出します。
Webhookの設定は 『[GASからhangout chatにPostする](https://qiita.com/mistolteen/items/a67b131b31d5c5246a6c#webhook%E3%81%AEurl%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B)』 がわかりやすいのでご参照ください。

## baserCMS プラグインの開発

### baserCMS のプラグイン設置
baserCMSにプラグインを入れる時は `/app/Plugin/プラグイン名` のように設置します。
今回は `Chat` という名前のプラグインを開発してみます。Chatプラグインの中身は次のようになります。

```:プラグインの構成
/app/Plugin/Chat/
    Config/
        setting.php
    Controller/
        Component/
            ChatComponent.php
    Event/
        ChatControllerEventListener.php
    VERSION.txt
    config.php
```
この記事では中でも次の2ファイルをメインに説明していきます。
`/Chat/Controller/Component/ChatComponent.php`
`/Chat/Event/ChatControllerEventListener.php`
その他のファイルは一旦なくても動くので、気になる方は 『[第1回 プラグイン作成の準備・初期設定](http://magazine.basercms.net/article/archives/1)』 をご参照ください。

### HangoutsChat に通知を送信する処理
baserCMSからHangoutsChatに通知する為の処理を書きます。
今回は様々な場面で共通で使えるよう、Componentを使います。
`/Chat/Controller/Component/ChatComponent.php` というComponentを作りましょう。
通知の処理は『[HangoutsChatにPHPから投稿してみる](https://qiita.com/pickles/items/5d12a3e98386169d6ac4)』を参考にしています。

```php:/Chat/Controller/Component/ChatComponent.php
class ChatComponent extends Component
{

    public $controller = NULL;

    public function send($message)
    {   
        $webhookUrl = Configure::read('HangoutsChat.webhookUrl');
        if (empty($webhookUrl) || !preg_match('#\Ahttps://chat.googleapis.com/v1/spaces/#', $webhookUrl)) return FALSE;

        $data = json_encode(['text' => $message], JSON_UNESCAPED_UNICODE);

        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $webhookUrl);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST');
        curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
        curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
        curl_exec($curl);
        curl_close($curl);

        return TRUE;
    }

}
```
この中の次の箇所はHangoutsChatの通知先のWebhookを設定しています。

```php
$webhookUrl = Configure::read('Chat.webhookUrl');
if (empty($webhookUrl) || !preg_match('#\Ahttps://chat.googleapis.com/v1/spaces/#', $webhookUrl)) return FALSE;
```
```php:/Chat/Config/setting.php
$config = [];
$config['Chat'] = [
    'webhookUrl' => 'https://chat.googleapis.com/v1/spaces/XXXXXXXXXX'
];
```
`Configure::read('HangoutsChat.webhookUrl')` と書くことで プラグインの中で `Chat.webhookUrl` キーに設定した値を取得しています。ここで直接WebhookのURLを設定しても問題ありません。


### Component の読み込み
上記で作成したComponentの処理を好きなタイミングで呼べるように読み込みます。
CakePHPの便利な機能、**イベントシステム**を使ってみます。イベントシステムを使うことで、baserCMS本体をいじらずともプラグインから本体に対して処理の変更や追加を行うことができます。
`/Chat/Event/ChatControllerEventListener.php` というEventListnerを作りましょう。

```php:/Chat/Event/ChatControllerEventListener.php
class ChatControllerEventListener extends BcControllerEventListener
{

    public $events = [ 
        'initialize'
    ];  

    public function initialize(CakeEvent $event)
    {   
        $Controller               = $event->subject();
        $Controller->components[] = 'Chat.Chat';
        $Controller->Chat         = $Controller->Components->load('Chat.Chat');
        return TRUE;
    }

}
```
`initialize` は各actionの前に読み込まれ、 `beforeFilter` よりも先に呼ばれるイベントです。initializeのタイミングでComponentを読み込むことで、様々なシーンで通知処理を使えるようにしています。
`$Controller->components[]` には `プラグイン名.Component名` というルールでComponentを追加します。
これでChatプラグインのベースが完成しました。

## 通知タイミングの設定

### プラグインの有効化
baserCMSからHangoutsCahtに通知を送信する準備ができました。では実際に通知を送信してみましょう。
まずは開発したプラグインが動くようにbaserCMSでChatプラグインを有効化させます。
baserCMSの管理画面にログインして、プラグイン管理の画面に移動しましょう。一番下にChatプラグインが追加されています。 `/Chat/config.php` に設定を書いていない場合は情報が少ないかもしれませんが、動作に影響はありません。
それでは早速プラグインをインストールして有効化してみましょう。

### Controllerから通知
まずは任意のControllerから通知を送信する方法です。
新しく `/Chat/Controller/SampleController.php` を用意しましょう。

```php:/Caht/Controller/SampleController.php
class SampleController extends AppController
{

    public function index()
    {
        $this->Chat->send('Controllerから通知するテスト');
        $this->autoRender = FALSE;
        echo 'Chat通知テストページ';
        return;
    }

}
```
上記のControllerのURLは `baserCMS設置URL/chat/sample/` になります。
既にEventListenerでComponentを読み込んでくれているので `$this->Chat->send('メッセージ')` だけでHangoutsChatに通知を送信することができます。勿論これはChatプラグイン外のControllerでも有効です。

### 記事が投稿されたら通知
続いて、元々baserCMSに備わっている機能にHangoutsChatへの通知送信を追加してみましょう。
まずはブログに新規記事が投稿された時に通知を送信してみます。
既存の `/Chat/Event/ChatControllerEventListener.php` に手を加えます。
`$event` に `Blog.BlogPosts.afterAdd` を、 `function blogBlogPostsAfterAdd` という処理を新しく追加します。

```php:/Chat/Event/ChatControllerEventListener.php
    public $event = [
        'initialize',
        'Blog.BlogPosts.afterAdd'
    ];

    public function blogBlogPostsAfterAdd(CakeEvent $event)
    {
        $Controller = $event->subject();

        $data    = $Controller->request->data['BlogPost'];
        $message = sprintf('新着記事 『%s』 が投稿されました', $data['name']);

        $Controller->Chat->send($message);
        return TRUE;
    }
```
`Blog.BlogPosts.afterAdd` はBlogプラグインのBlogPostsController限定で `afterAdd` という新規記事登録後に動くイベントです。他のイベントにおいても `プラグイン名.Controller名.イベント名` とすることで対象を絞ることができます。
ここでは `$Controller->request->data['BlogPost']` で記事内容を取得し、通知の中に利用しています。

### お問い合わせを受け付けたら通知
ユーザーからのお問い合わせに機敏に対応することも大切ですね。お問い合わせを受けたらHangoutsChatに通知してみましょう。
今回も記事投稿時と同じように `/Chat/Event/ChatControllerEventListener.php` に手を加えます。
`$event` に `Mail.Mail.afterSendEmail` を、新たに `function mailMailAfterSendEmail` も追加します。

```php:/Chat/Event/ChatControllerEventListener.php
    public $event = [
        'initialize',
        'Blog.BlogPosts.afterAdd',
        'Mail.Mail.afterSendEmail'
    ];

    public function mailMailAfterSendEmail(CakeEvent $event)
    {
        $Controller = $event->subject();

        $data    = $Controller->request->data['MailMessage'];
        $message = sprintf('%s さんからお問い合わせがありました', $data['name_1']);

        $Controller->Chat->send($message);
        return TRUE;
    }
```
`Mail.Mail.afterSendEmail` はMailプラグインのMailController限定で `afterSendEmail` というメール送信直後に動くイベントです。 `$Controller->request->data['MailMessage']` でユーザーの入力内容を取得しています。
この他にもbaserCMSに用意されているイベントはbaserCMS公式ガイドの 『[ver4/プラグイン作成ガイド](http://wiki.basercms.net/ver4/%E3%83%97%E3%83%A9%E3%82%B0%E3%82%A4%E3%83%B3%E4%BD%9C%E6%88%90%E3%82%AC%E3%82%A4%E3%83%89#.E3.83.97.E3.83.A9.E3.82.B0.E3.82.A4.E3.83.B3.E3.82.A4.E3.83.99.E3.83.B3.E3.83.88)』 で紹介されています。

このようにイベントシステムを使うことで本体に手を加えずに処理を追加することができます。勿論デフォルトのイベントシステムでは対応できないシーンもありますが、1行書き足すだけで処理を追加できます。 ~~肝心のエラー時の通知は書いていないけど気にしない。~~

**とりあえず `$this->Chat->send('メッセージ')` って書けば好きな時に通知を送信できるよ！**
**皆さんもエラーに気を付けつつ、開発ライフを楽しみましょう！**

## 備考

### 開発環境
- PHP 7.1.23
- CakePHP 2.10.6
- baserCMS 4.1.4

### 参照
[GASからhangout chatにPostする](https://qiita.com/mistolteen/items/a67b131b31d5c5246a6c#webhook%E3%81%AEurl%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B)
[HangoutsChatにPHPから投稿してみる](https://qiita.com/pickles/items/5d12a3e98386169d6ac4)
[baserCMS ver4/プラグイン作成ガイド](http://wiki.basercms.net/ver4/%E3%83%97%E3%83%A9%E3%82%B0%E3%82%A4%E3%83%B3%E4%BD%9C%E6%88%90%E3%82%AC%E3%82%A4%E3%83%89#.E3.83.97.E3.83.A9.E3.82.B0.E3.82.A4.E3.83.B3.E3.82.A4.E3.83.99.E3.83.B3.E3.83.88)

---
[:christmas_tree: FORK Advent Calendar 2018](/advent-calendar/2018/fork)
:arrow_left: 15日目 [Photoshopで画像をスライスするJS](/re_sai/items/8eb316def2f85cda7f1b) @re_sai
:arrow_right: 17日目 [Vue.js スマホでコンテンツをスクロールさせたくないようなアプリをつくるコツ](/AsaToBan/items/986bbb5285e7a7275c5e) @AsaToBan

[:christmas_tree: baserCMS Advent Calendar 2018](https://adventar.org/calendars/3481)
:arrow_left: 15日目 [作成中のbaserCMS用メールプラグインの紹介](/seto1/items/353d4bc6830621a7d281) @seto1
:arrow_right: 17日目 [baserCMS 5で導入予定のCakePHP 3系は2系からどう変わった？](/tommy6073/items/a2b63c9c2fe6b15d1060)@tommy6073