ブログをやっているとよく遭遇するのがスパムコメントだ。

人類とスパムとの争いは永遠のテーマだろう。

スパムコメントを防ぐため、CAPTCHAなどの対策を講じているが、あまりユーザー側に負担を求めるとコメントする敷居が高くなる。

そこで、できるだけサーバ側で対処する方法を取っている。

これまではプラグインを使ってきたが、今回は functions.php でコメントを判別して拒否するための方法を調べてみた。

どこに関数をフックするか

WordPressにおいて、コメントはどの関数を使い、どのように記録されるのか。

コメントを送信する際にPOSTアクセスする先はドメイン直下の /wp-comments-post.php となっている。

このファイルのソースを見てみると、どうやら wp_handle_comment_submission() という関数を使ってコメントを処理しているようだ。

この関数のソースから、使えそうなフィルターもしくはアクションを探す。

しかしこの関数では主に、コメント先の投稿がコメントを受け付けているかどうかを判別しており、コメント自体の処理は wp_new_comment() が行なっているようだ。

次にこの関数を辿ってみる。

この関数ではコメントデータの整形、チェック、そしてデータベースへの記録をしており、そして以下の三つの関数が使われている。

  • wp_filter_comment() - コメントの各データに適用するフィルターがまとめてある。
  • wp_allow_comment() - コメントを承認するかどうかを決定する。
  • wp_insert_comment() - コメントをデータベースに記録する。

この中で、今回の目的に適いそうなのは wp_allow_comment() となる。

この関数では様々な方法でコメントが適切かどうかを判定しており、なかなか興味深いので一度は見ておいてもいいと思う。

“duplicate comment” とか “comment flood” とか。

コメントを却下する方法

wp_allow_comment() の返り値は 1 で承認、 2 で未承認、 'spam' でスパム扱い、となっているが、 WP_Error を返すことでエラーを表示させてコメントを却下することができる。

却下とはすなわち、コメントを受け取っていない扱いになる。まあエラーだし。

そしてこの関数にはフィルターやアクションがいくつかあり、その中で pre_comment_approved フィルターが今回の目的において最もふさわしい。

ということで、このフィルターに関数をフックすることにしよう。

function my_comment_approved( $approved, $commentdata ) {
    // $approved    フィルター適用前の返り値
    // $commentdata コメントデータ
}
add_filter( 'pre_comment_approved', 'my_comment_approved', 10, 2 );

エラーを吐かせるならこんな感じ。

function my_comment_approved( $approved, $commentdata ) {
    if ( [条件] )
        return new WP_Error( [エラーコード], [エラーメッセージ], [エラーデータ] );
}

これで、条件に当てはまるコメントが投稿された時、あのWordPress特有の真っ白なエラー画面に、設定したメッセージが表示され、コメントは却下される。

URLフィールドが空でない場合に拒否

このサイトでも二つほどスパム対策のプラグインを使用しているのだが、それでもなおスパムコメントが来ることがある。

手動なら仕方ないが、手動でもないようなパターンが。

ご覧の通り、このサイトのコメントフォームには「URL」の入力欄がないのだが、このサイトの従来のスパム対策をすり抜けてくるスパムコメントにはURLが設定されているのだ。

まあ、WordPressのコメントなんて、PHPを直接POSTアクセスで叩けば投稿できるというから、HTMLがどうなっているかに関わらず、スパムbotにとってURL欄は「あるもの」として扱われているのではなかろうか。

……よろしい、ならば戦争だURLを勝手に投稿しようとする輩は、問答無用でコメントを却下してやろうじゃない。

そこで俺が書いたのが以下のコード。

function kick_comment_with_url( $approved, $commentdata ) {
    if ( $approved !== 1 )
        return $approved;
    $user = wp_get_current_user();
    if ( $user->exists() )
        return 1;
    if ( empty($commentdata['comment_author_url']) )
        return 1;
    $url = $commentdata['comment_author_url'];
    if ( $url === 'https://' || $url === 'http://' )
        return 1;
    return new WP_Error( 'comment_url', __('<strong>ERROR</strong>: not accepted URL field on comments.'), 200 );
}
add_filter( 'pre_comment_approved', 'kick_comment_with_url', 10, 2 );

2〜3行目は、すでにコメントが「承認」以外で判定されている場合、そのままにする。

4〜6行目は、ログイン済みのユーザーによるコメントは「承認」にする。

ログイン済みのユーザーのコメントには自動的にプロフィールのURLが含まれてしまうため、こうしないと以下のコードにより却下されてしまう。

7〜8行目は、URL欄(として渡されるデータ)が空欄ならば「承認」にする。

9〜11行目は、URL欄が https:// または http:// のみだった場合、テーマによっては親切にもこういった“初期値”が用意されているかもしれないといった点を想定し、これに該当するコメントを「承認」にする。

そして12行目、上記に該当しない場合、URL欄は“不正に”入力されている状態だから、スパムとみなしてエラーを吐き出す。

なお、エラーコードとメッセージは俺のオリジナル。

混乱する人もいるかもしれないから念のために言うが、12行目の __() も関数だからね。

まとめ

今回は pre_comment_approved フィルターでエラーを吐くようにすれば、コメントを蹴ることができる、ということがわかった。

これを使えばスパムコメントを拒否することができる。

ところでこの記事を書いてて思ったことだが、俺ってさっきから、コメントを「蹴る」だの、「拒否する」だの、「却下する」だの、表現が全く一貫していないじゃないか。

まあ意味は似たようなものだが。

俺の中では「蹴る」が一番しっくりきている。短いし。

みなさんは蹴るのと蹴られるの、どちらがお好きですか?

俺は……蹴りたいほどSじゃないし、蹴られたいほどMでもないな。

「踏まれたい」なら、一部のキャラになら……

何言ってるんだろうね。

というわけで、今回紹介した方法を使って、スパムコメントは心置きなく蹴飛ばしてやろうね!