Contact Form 7 のカスタムバリデーションの書き方（カスタマイズ方法）


Wordpressのコンタクトフォームと言えば、検索順位でも人気でも間違いなくNo.1なのが Contact Form 7。いまさら説明の必要もない、日本を代表するプラグイン。なのでドキュメントも充実！と思いきや、意外と欲しい情報に対するまとまったドキュメントがなかったので、せっかくなのでまとめさせていただきます。
テーマは、ひらがな判定、電話番号の桁数判定、はたまたAをチェックしたらB必須など、様々なカスタムバリデーションをどう書くか！？ その１つのご提案です。

# 公式ドキュメント

https://contactform7.com/2015/03/28/custom-validation/
え、英語だ！！

# 結論

使用しているテーマの、functions.php に書きます。下記がそのサンプルです。

```php:functions.php
/* ContactForm7 のカスタムバリデーション */
add_filter('wpcf7_validate', 'wpcf7_validate_customize', 11, 2);
 
function wpcf7_validate_customize($result,$tags){

  foreach( $tags as $tag ){
    $type = $tag['type'];
    $name = $tag['name'];
    $post = trim(strtr((string) $_POST[$name], "\n", ""));

    switch($type){
      case 'text':
      case 'text*':
        if(preg_match('/.*-kana$/', $name )){
          if(preg_match("/^[ぁ-ん]+$/u",$_POST[$name]) == false){
            $result->invalidate( $name,'全角ひらがなで入力してください。' );
          }
        }
        break;
      case 'tel':
      case 'tel*':
        if( preg_match( '/^0[0-9]{1,3}-[0-9]{2,4}-[0-9]{3,4}$/',$_POST[$name] ) == false){
          $result->invalidate( $name,'ハイフンは省略せず市外局番からご記入ください。' );
        }
        break;
    }

  }
  return $result;
}
```

# 解説

## ポイント１：フックは１つ

検索すると、例えば下記のような、フォームエレメントのTypeに応じたフックが紹介されることが多いです。この場合は、textタイプのエレメントです。

```php
add_filter('wpcf7_validate_text',  'wpcf7_validate_kana', 11, 2);
add_filter('wpcf7_validate_text*', 'wpcf7_validate_kana', 11, 2);
function wpcf7_validate_kana($result,$tag){
  $type = $tag['type'];
  $name = $tag['name'];
  /* ... 略 ... */
}
```

ただ、これだとエレメントの種類が増えるとそれだけフックの数も増やす必要があり、コード量が増えるし書き漏れのミスも増えます。必須かどうかで2つ書くのもナンセンス。なんとかならないのかなぁと思ってソースコードを見ていたら、ありました。

```php:wp-content\plugins\contact-form-7\includes\submission.php

class WPCF7_Submission {

  /* ... 略 ... */

  private function validate() {

    /* ... 略 ... */
  
    $result = new WPCF7_Validation();

    $tags = $this->contact_form->scan_form_tags();

    foreach ( $tags as $tag ) {
      $type = $tag['type'];
      $result = apply_filters( "wpcf7_validate_{$type}", $result, $tag );
    }

    $result = apply_filters( 'wpcf7_validate', $result, $tags );

    /* ... 略 ... */

  }

  /* ... 略 ... */
}
```

バリデーションの際のフックは、各エレメントに対して ``"wpcf7_validate_{$type}"`` が呼ばれた後に、 ``'wpcf7_validate'`` というフックが呼ばれているんですね。これを使わせていただきます。注意点は、 ``$tag`` が配列の ``$tags`` として与えられるので、中でループ展開をしているところです。

```php
add_filter('wpcf7_validate', 'wpcf7_validate_customize', 11, 2);

function wpcf7_validate_customize($result,$tags){
  foreach( $tags as $tag ){
    $type = $tag['type'];
    $name = $tag['name'];
  /* ... 略 ... */
  }
}
```

## ポイント２：switchで分けて、素直に if で判定

ここは好みが別れるところかもしれません。個人的には、switchで大きくタイプ別に分けて、ifで詳しい条件を書いていくのが好きです。

```php
    switch($type){
      case 'text':
      case 'text*':
        if(preg_match('/.*-kana$/', $name )){
          if(preg_match("/^[ぁ-ん]+$/u",$_POST[$name]) == false){
            /* ... 略 ... */
          }
        }
        break;
      case 'tel':
      case 'tel*':
        if( preg_match( '/^0[0-9]{1,3}-[0-9]{2,4}-[0-9]{3,4}$/',$_POST[$name] ) == false){
            /* ... 略 ... */
        }
        break;
    }
```

そもそも Type で条件をかき分ける必要はなく（たとえば「ひらがな判定」は input[type=text]だけでなく textarea などにも使いたい）、だったら case に条件式を書いてしまう、という手もあります。

```php
    switch(true){
      case preg_match('/.*-kana$/', $name ) && 
           preg_match("/^[ぁ-ん]+$/u",$_POST[$name]) == false :
           /* ... 略 ... */
           break;
      case preg_match('/^tel\*?$/', $type ) &&
           preg_match( '/^0[0-9]{1,3}-[0-9]{2,4}-[0-9]{3,4}$/',$_POST[$name] ) == false :
           /* ... 略 ... */
           break;
    }
```

## ポイント３：入力された値……それすなわち $_POST

正直、ちょっとどうにかならないのかなぁと思ったところ。あまり$_POSTといったグローバル変数を直接参照するのは良くないし、Wordpressにもそれなりの手段が用意されていますが、Contact Form 7のバリデーションでは常套手段のようなのでスルーします。こういうものだと思うことにします。

## ポイント４：結果は $result->invalidate を使って返す

> Contact Form 7のversionが 4.1 に上がったら、エラーチェックがスルーされるようになった
http://blog.valid-seo.biz/2015/02/1399.html

こちらに紹介されているように、Google先生が時々教えてくれる方法が、今はもう使えない、ということがあるようです。今も使える手段として、$result->invalidate を使います。

```php
     $result->invalidate( $name,'全角ひらがなで入力してください。' );
```

この１行だけ。これを１回でも実行すると、バリデーションは失敗し、$name に対する「全角ひらがなで入力してください。」というメッセージがフォームに表示されます。先のソースコードを見ると、WPCF7_Validation クラスのインスタンスですね。

# 感想

いまやノンプログラミングで、GUIでがしがしとバリデーションが組めるコンタクトフォームプラグインがある中、ほぼ100%ピュアなPHPでバリデーションを組まされることになるなんて（しかもキレイに書ける）、なかなか硬派なプラグインだと思いました。良くも悪くも上級者向け、なんでしょうか。逆に、複数の項目を横断的に判断する条件（AがチェックされているときだけBが必須…とか）もカンタンに書けるので、コーダーとしては腕が鳴る、いいプラグインだと思います。

あと、JavaScriptで画面遷移せずバリデーションエラーを出すので、バリデーションもJavaScriptなのかと思ったら、AjaxでサーバーサイドのPHPに判定させているっていうのも、PHPer想いの硬派な設計なんですね！(・∀・)