Monthly Archives: May 2014

PHPでRespect/Validationを使って値の検証をするときの例外文字複数指定

PHPのRespect/Validationは、入力値の検証に使えるライブラリです。GitHubのREADME.mdを読めば、だいたいの使い方がわかります。

class Validator extends \Respect\Validation\Validator {

    static function validate($param){

        if(!static::notEmpty()->alnum()->validate($param)){
            echo "Error!!";
        }
}

みたいにして使います。

alnum()は「alphanumeric characters from a-Z and 0-9」を検証してくれます。空白も許容。noWhitespace()と組み合わせると空白は非許容となります。例外文字の設定が出来るのですが、README.mdだと例外1文字の例しかなくて、複数の例外文字指定方法がわかりませんが、GitHubのWikiに整理されています。

class Validator extends \Respect\Validation\Validator {

    static function validate($param){

        $additionalChars = "#_-";

        if(!static::notEmpty()->alnum($additionalChars)->validate($param)){
            echo "Error!!";
        }
}

これで「#_-」を許容してくれます。

ご参考まで。

Twilioコールセンターアプリ「Runa-CCA」バージョン1.2リリース(ソフトフォンへ機能追加)

Twilioコールセンターアプリ「Runa-CCA」バージョン1.2リリースしました。

ソフトフォンにミュート/ミュート解除・DTMF信号送信用ダイヤルパッド機能を搭載しました。
以下のイメージになります。

Runa-CCA_Softphone1

Runa-CCA_Softphone2-Mute

Runa-CCA_Softphone3-Dialpad

コールセンターには他にも、保留・転送・会議・モニタ機能が必要になります。
JavaScriptライブラリ「twilio.js」の充実に期待!というところでしょうか。

README.mdにも書いてありますが、他にも変更していて、変更点をまとめると以下の通りです。

  • ソフトフォン
    • ミュート/ミュート解除、ダイヤルパッド(DTMF送信用)ボタンを追加。
    • ボタンの表示・非表示を追加(通話設立前はHangUpボタン非表示等)。
    • ソフトフォン小型化。
    • ソフトフォン起動前に、オペレータが持っているキュー情報を更新する仕様に変更。
  • Webクライアント
    • キューID(TwilioのFriendly Name)の空白を非許容に変更(CSSと相性悪いので)。a-Z0-9に加えて#_-を許容。
    • キューを持っていないオペレータの表示がエラーになっていたので、エラーにならないように修正。
  • その他
    • サンプルSQLのキュー作成部分を削除(Webクライアントからキューを作成してください)。

ソースコードは以下から。

PHPマイクロフレームワークSlimで作るTwilioコールセンターシステム(4.コールフロー(1.コールフロー起動まで編))

PHPマイクロフレームワークSlimで作るTwilioコールセンターシステム
1.アプリ紹介編
2.環境設定
 1.ライブラリインストール編
 2.Apache mod_rewrite設定編
 3.CSSとJavaScript編
3.“Twilioコールフローフレームワーク”編

の続きです。

コールフローを作ります。
Twilioからリクエストされることで、コールフローが起動します。以下のような流れ。

  1. お客様が050番号に電話する。
  2. Twilioによって、050番号の「音声通話」に登録されているRequest URLが、サーバーアプリ(Runa-CCA)にリクエストされる。
  3. Runa-CCAでリクエストを処理する。

「050番号の「音声通話」に登録されているRequest URL」ですが、Runa-CCAでは新サービス・製品窓口のコールフロー(NewService)をサンプルとして用意していて、「http://ドメイン名/runa-cca/twilio/callflow/newservice/main」になります。このURLは、Apache mod_rewrite設定の通り、index.phpで処理されることになります。

“Twilioコールフローフレームワーク”編」に書いた通り、index.phpは、Slimのオブジェクトを作って、ルータ(\Runa_CCA\Route.php)に処理を渡す役割を負います。具体的にはindex.phpの中の、

\Runa_CCA\Route::registration($app);

によって、ルータに処理が渡されます。
なお、index.phpでsession_start()とかしているのはWebアプリケーション機能用でして、コールフローのみであれば不要になります。

ルータ(\Runa_CCA\Route.php)のregistrationメソッドには以下のように書いてあります。
Request URLに従って、コントローラ(\Runa_CCA\Controller\CallFlow\NewService.php)のstart(“main”)メソッドを呼びます。
GETとPOSTの両方に対応するようにしているのはテスト用です。テスト終わったらPOSTで統一するべきだと思います。
main以外は別途Twilioからリクエストされるものになります。

        // CallFlow
        // "NewService"
        $app->map ('/twilio/callflow/newservice/main', 
                    function () {
                        \Runa_CCA\Controller\CallFlow\NewService::start("main");
                    })->via('GET', 'POST');
        $app->map ('/twilio/callflow/newservice/wait', 
                    function () {
                        \Runa_CCA\Controller\CallFlow\NewService::start("wait");
                    })->via('GET', 'POST');
        $app->map ('/twilio/callflow/newservice/info', 
                    function () {
                        \Runa_CCA\Controller\CallFlow\NewService::start("info");
                    })->via('GET', 'POST');
        $app->map ('/twilio/callflow/newservice/enqueaction', 
                    function () {
                        \Runa_CCA\Controller\CallFlow\NewService::start("enqueaction");
                    })->via('GET', 'POST');
        $app->map ('/twilio/callflow/newservice/guidance', 
                    function () {
                        \Runa_CCA\Controller\CallFlow\NewService::start("guidance");
                    })->via('GET', 'POST');
        $app->map ('/twilio/callflow/newservice/statuscallback', 
                    function () {
                        \Runa_CCA\Controller\CallFlow\NewService::start("statuscallback");
                    })->via('GET', 'POST');

コントローラ(\Runa_CCA\Controller\CallFlow\NewService.php)では、まず、Slimのインスタンスを取得し、Requestで送られてきたパラメータを全て取得します。

        // Get Parameters
        $app = \Slim\Slim::getInstance();
        $params = $app->request->params();

続いて、RequestがTwilioから来たものなのかどうかを検証します。
検証処理については、別記事「Twilioにおける認証(TwilioからのRequestデータ検証)について」に記載の通りです。

if((new \Runa_CCA\Model\Twilio())->validateTwilioRequest($app, $params)){

Trueが返ってきたら、Switch文で判定して、Modelクラスを呼びます。ここからがコールフローの本体です。コールフローでの処理が終わって、ModelクラスからTwiMLオブジェクト(\Services_Twilio_Twimlオブジェクト)が返ってきたら、それをTwiMLを作るVewクラスに渡します。

                    // Go to Main CallFlow
                    case "main" :
                    
                        $response = (new \Runa_CCA\Model\CallFlow\NewService\Main())->main($params);
                        (new \Runa_CCA\View\Twiml($app))->createTwiml($response);
                        break;

実は、ここのエラー処理には問題ありなのです。
Webアプリケーション機能とエラー処理を共有しておりまして、日本語でエラーページを返す仕様にしています。
しかしそうすると、Twilioホームページでログ確認するときに文字化けしてしまうんです。コールフロー用のエラーページを英語で作らないといけません。この点は別記事に書いておきました。
別途修正予定です。

TwiMLを作るTwiMLクラスですが、以下のようにTemplateを呼び出し、Twigでレンダリングさせます。

class Twiml {

    public function __construct($app){
        
        $this->app = $app;
        
    }

    public function createTwiml($twilioObj){
        
        $this->app->render(
            'TwiML/twiml.twig', 
            [
                'twiml'         => $twilioObj,
            ]
        ); 
        
    } 
}

Twigを使用している場合は、

{% autoescape true %}
ほげほげ
{% endautoescape %}

でエスケープされます。
しかし、TwiMLを出力させるので、オートエスケープをオンにしてしまうと、TwiMLのタグがすべてエスケープされてしまいます。そのため、

{% autoescape false %}
{{ twiml }}
{% endautoescape %}

として、エスケープしないようにしています。

しかし、例えばお客様から電話機で番号入力させて、それをTwilioで読み上げたりするような場合は、エスケープする必要があります。
twilio-phpで自動でエスケープしてくれるのですが、\Services_Twilio_Twimlクラスの__callマジックメソッドのコメントを読むと、「経験ない開発者の中にはアンパサンドをエスケープしない人がいる」といった記載がありました。twilio-phpには頼らずエスケープ処理せい、ということなんでしょう。

Runa-CCAでは、\Base\Conf内にエスケープするメソッドを用意しておりまして、TwiMLを作成するときにはそのメソッドを実行してエスケープするようにしています。

次回はコールフローの中身を見ていく予定です。

Twilioコールセンターアプリ「Runa-CCA」バージョン1.1.1リリース(ウォールボード機能搭載)

Twilioコールセンターアプリ「Runa-CCA」バージョン1.1.1リリースしました。

ウォールボード機能を搭載しました。
以下のイメージになります。

Runa-CCA_1_1_1_Wallboard

管理画面(Configurator)に「ウォールボード」リンクがあります。
そこをクリックすると、自分が所属しているキューのリアルタイム情報が表示されます。
表示項目は画像をご覧の通り。
画面は10秒更新にしました。

あと、TwiML出力方法の変更。Webクライアント側と合わせてTwigを利用するようにしました。不要ファイルの削除と、細かなバグ修正を実施しています。
アップしたとたんにバグを見つけたので、最新リリースは1.1.1になります。
もっと試験しないとな。

GitHubにアップしております。ソースコードは以下から。

Twilioのエラーページで日本語を使ってはいけません

Twilioのホームページでログを確認出来ます。

しかし、日本語の表示ができません。日本語のエラーページを返すようにしておいてからログ確認すると、以下のように文字化けしてしまいます。

<!DOCTYPE html>
<html lang="ja-JP">
    <head>
        <meta charset="utf-8">
            <meta http-equiv="X-UA-Compatible" content="IE=edge">
                <meta name="viewport" content="width=device-width, initial-scale=1">
                    <title>Runa-CCA</title>
                </head>
                <body>
                    <h1>?????????</h1>
      ???????????????????????????????????????????????????
  
                </body>
            </html>

Twilioでエラーページを返したい場合は英語にしておかないと、あとでデバッグしたときに何がなんだかわからなくなります。
Runa-CCAのバージョン1.0は日本語のエラーページを返すので、上記のような文字化けをしてしまうんですよね。直さなければ。

ちょっとした情報共有でした。

MacBook Pro (Retina, 13-inch, Late 2013) でAir用の45W電源を使う

MacBook Pro with Retina Display ME865J/Aを使っています。前に使っていたMacbook Airは売ってしまったのですが、電源が余っていました。前に勉強会を開催したときに、電源を持って行き忘れて、急遽電源を購入したことがあって、余っていたのです。

しかしながら、Macbook Air用は45W。Proは60W。
コネクタ形状も違います。私の古いAirはMagSafeです。今のProはMagSafe2。

コネクタに関しては、純正のコンバーターが売っていました。

コンバータを付けて、旧Airの45W電源を、Proにつないだところ、普通に使うことが出来ました。
45Wはコンパクトだし、持ち運びに便利そうです。

そもそもバッテリー容量が多いので、電源を持ち運ぶ必要はあまり無いのですが、プログラミングしているときなどは予想外に早く電源を消費します(サーバソフトや、仮想環境や、IDEを動かしてますからねえ)。そんなときに使おうかと。

やる場合はもちろん自己責任でお願いします。

SlimフレームワークでLaravel’s Eloquent ORMを用いたデータベーストランザクション処理

SlimフレームワークでORマッパーLaravel’s Eloquent ORMを使う方法は「How to use Laravel’s Eloquent ORM with the Slim Framework」に書いてあります。
しかしながら、トランザクションの利用については書いてません。試行錯誤しました。NetBeansの補完機能に助けられつつ、実現できました。

以下のようなクラスを作ります。ちなみに、Runa-CCAではDBクラスになります。説明にあたって、クラス名を変更しました。

クラスを利用するときは、クラスのオブジェクトを作って、getIlluminateConnection()メソッドを呼んで、コネクションを取得します。
コネクションから”getPdo()->beginTransaction()”を呼ぶと、トランザクションが開始されます。コミットしたいときは”getPdo()->commit()”、ロールバックしたいときは”getPdo()->rollback()”を呼べばOKです。

以下のような感じ。

$dbConn = (new \Runa_CCA\Model\IlluminateDB())->getIlluminateConnection();

try{
    // Begin Transaction.
    $dbConn->getPdo()->beginTransaction();

    // Commit.
    $dbConn->getPdo()->commit();

}catch(\Exception $e){

    // Rollback.
    $dbConn->getPdo()->rollback();

}

以上、ご参考まで。

Twilioにおける認証(TwilioからのRequestデータ検証)について

Twilioは、

電話する→Twilioが電話番号にひも付けられたURLをリクエストする

という仕組みになっています。
Twilioはインターネット経由でリクエストしてきますので、リクエストを受けるアプリケーション側からすると、セキュリティ的にかなりの脅威です。対処しなければなりません。

対処の方法として以下のものがあります。

    1. Basic(基本)認証を使う
    2. アプリ側で対処する

前に作ったTwilio-MiniCCでは、Basic(基本)認証を前提にしていました。Twilio設定画面の電話番号のURL登録のところで、

http://A:B@C/D/index.php
A: Basic認証のユーザ名
B: Basic認証のパスワード
C: Webサーバのドメイン名
D: アプリを置いた場所

としてあげれば、基本認証がかかります。これでも十分かもしれません。

が、Runa-CCAはWebクライアント機能と、Twilioから呼び出されるコールフロー機能の2つを持っています。そして、PHPマイクロフレームワークSlimを使って、URLをリライトしてルーティングしていて、URLをWebクライアント機能とコールフロー機能とで共用していたため、Basic認証は使いづらいものでした(コールフロー側のみならず、Webクライアント側にもBasic認証がかかってしまう)。そこで、暫定策として、Runa-CCAのバージョン0.8では、Requestで送られてくるAccountSidを使って認証をかけていました。

見落とししていたのですが、Twilioにはもう一つの方法があります。「Validating Requests are coming from Twilio」です(日本語版もあります)。Twilioのダッシュボードに行くと得られるAUTH TOKENと、Requestで送られる値とをハッシュした値を、Twilioがヘッダーに付けてくれます。Requestを受けるアプリ側でも同じように値をハッシュして、それがTwilioの送るハッシュと値が合っていれば、そのRequestはTwilioから送られたものだと判定できます。

具体的なコーディング方法はこちらに書いてありますが、誤解を招く記載です。TwilioはPOSTデータ全てを使ってハッシュ値を算出するので、検証する側もPOSTデータ全てを使わなければなりません。$validator->validate()の3つ目の引数には、POSTデータをそのまま入れればよろしい。

Slimを使ってサンプルを作ってみましたので、公開します。

これを、

<?php
namespace Sample;

require '../vendor/autoload.php'; 

$app = \Slim\Slim::getInstance();
$params = $app->request->params();

$result = validateTwilioRequest($app, $params);

とすればTrue/Falseが返ってくるはずです。
ちなみに、Runa-CCAのVersion 1.0では、\Runa_CCA\Model\Twilioクラスに実装されています。コメントにミスがあったのと、ちょっと余計な処理しているので、別途直さなくては。あうあう。

ご参考まで。

Twilioコールセンターアプリ「Runa-CCA」バージョン1.0リリース

Twilioコールセンター(コンタクトセンター)用アプリケーションを作っていて、機能や画面イメージはこちらで紹介しています。
修正を行い、GitHubタグを1.0にしました。
0.8から以下を更新しています。

  • Webクライアントのキュー管理機能をTwilioと統合。Twilio REST APIを使用し、WebクライアントからTwilioのQueue追加・変更・削除が可能になりました。
  • Webクライアントのオペレータ管理・キュー管理において、トランザクション管理を実装。TwilioのREST APIが動作しなかった場合にもDBロールバックを行うようにしました。
  • Webクライアントの管理画面(Configurator)権限を以下の通りに整理しました。
    • Operator … 何も出来ない(自分のパスワード変更のみ)
    • Supervisor … オペレータ管理
    • SystemAdmin … キュー管理+オペレータ管理
  • Twilioからリクエストされる部分について、Twilio推奨の認証(検証)方法を導入しました。
  • 上記実現のため、ソースコードならびにDB構造の変更を行っています。

ソフトフォン機能の充実に進みたいところなのですが、JQueryとか学ばないといかんかなあ。
まずはその前に今までやってきたことのまとめだ。

PHPマイクロフレームワークSlimで作るTwilioコールセンターシステム(3.”Twilioコールフローフレームワーク”編)

PHPマイクロフレームワークSlimで作るTwilioコールセンターシステム
 1.アプリ紹介編
 2.環境設定
  1.ライブラリインストール編
  2.Apache mod_rewrite設定編
  3.CSSとJavaScript編

の続きです。

前回までで設置と設定が終わったはず。
今回は、Runa-CCAの「Webクライアント」機能と「コールフロー」機能のうちの、「コールフロー」機能の作りについて書きます。

Twilioのコールフロー機能を考えるにあたって、フレームワーク化したいなあと思いました。

お客様がコールセンターの電話番号に電話すると、Twilioの050の電話番号に登録されている「音声通話」のURLに対して、Twilioがリクエストを投げます。そのリクエストを受けて、コールフローが起動します。
Twilioの設定で、ある050番号の音声通話のURLに、例えば「http://Runa-CCAのドメイン名/runa-cca/twilio/callflow/newservice/main」を登録しておきます。そうすると、お客様がその050番号に電話してきたら、Twilioによって音声通話のURLがリクエストされるわけです。「Apache mod_rewrite設定編」で設定した通り、/runa-ccaに来たURLは、Apacheの機能で/runa-cca/index.phpにリライトされ、処理されることになります。

ここからが”Twilioコールフローフレームワーク”になります。
以下のような構成にしました。

  • 前提
    1. コールセンターには、コールフローがいくつもある。
    2. コールセンターにおいて、コールフローはビジネスロジックである。
    3.  コールセンターによってはコールフローはたくさんあります。数十とかあります。そしてコールフローは、コールセンター独自の仕組みであり、電話インタフェースと統計データベースとのやり取りを制御するものでもありますので、ビジネスロジックであろうと思います。ま、ビジネスロジックの定義は色々のようですが。

  • “Twilioコールフローフレームワーク”
    1. index.phpは、Slimのオブジェクトを作って、ルータ(\Runa_CCA\Route.php)に登録して、Slimを実行させる。(それだけしかしない)
    2. Route.phpは、URLに応じ、適切なController(コントローラ)にルーティングする。(それだけしかしない)前提1の通り、コールセンターにはコールフローがいくつもある。そのため、Controllerフォルダ配下にコールフロー毎にフォルダを作って、コールフロー毎のコントローラを一括管理する。言うまでもなくnamespaceも別にする。コールフローの場合、Route.phpは\Runa_CCA\Controller\CallFlow配下の適切なコールフローを呼び出す。
    3. コールフロー毎のコントローラが行うのは、Model配下のコールフロービジネスロジックの呼び出しと、Twilio用のTwiML作成Viewクラスの呼び出しだけ。(それだけしかしない)
    4. 前提2の通り、コールフローはビジネスロジック。そのため、コールフロー本体はModel(モデル)として整理。前提1の通り、コールフローはたくさんあるので、Model配下にコールフロー毎のフォルダを作って、そこに一括管理し、コールフローのIVR処理や統計データベースへのデータ保存といった処理を実行する。(ここでがっつりと処理を実施する)
    5. Modelのコールフローが処理を終えたら、Controllerに一度戻し、ControllerからViewが呼ばれて、TwiMLが作成され、TwilioにResponseとして返される。(それだけしかしない)

まあ大した話ではないのですが、そんな構造にしてみたわけです。
フォルダ構造は以下の通りになります。

/var/www/html/runa-cca/
 |-htdocs ... 公開用ディレクトリ
 |   |-index.php ... すべての入り口
 |
 |-lib ... 作成したクラスライブラリ
 |   |-Base ... 設定クラス置き場
 |   |-Runa_CCA ... Runa-CCAクラス置き場
 |       |-Route.php ... いわゆるルータ
 |       |
 |       |-Controller ... コントローラクラス置き場
 |       |   |-CallFlow ... コールフロー毎のコントローラクラス置き場
 |       |       |-NewService ... コールフロー「NewService」のコントローラクラス置き場
 |       |
 |       |-Model ... モデルクラス置き場
 |       |   |-CallFlow ... コールフロー毎のモデルクラス置き場
 |       |       |-NewService ... コールフロー「NewService」のモデルクラス置き場
 |       |
 |       |-Model ... ビュークラス置き場
 |
 |-templates ... テンプレートファイル

次回からはソースを追いかけながらコールフローの流れを書きます。