TypeCodes

博客优化第二波(三):wordpress评论添加算术验证码

虽然说前面通过前面通过采用《屏蔽wordpress垃圾评论的方法(插件和非插件)》的方法来拦截那些spam,效果非常不错。但是博主对这些垃圾评论不会直接删除,而是先经过非插件方法:Anti-Spam程序的处理,自动把他们列入黑名单,然后在wp后台手动删除。每天都要删半箩筐的垃圾评论,蛋疼不已。

于是博主做了一个艰难的决定,那就是填写验证码后才能发表评论,幸苦发表评论的筒子们了。之前写过一篇关于PHP验证码的文章——《PHP中验证码的设计》,主要是JS弹窗进行验证。这样的好处是整个过程在客户端进行,不用提交到服务器,减轻了服务器的负担;缺点是安全性不佳,要是用户禁用了浏览器的javascript,那么弹窗是不起作用的。那么除了图片验证码之外,我们也可以使用简单的数字验证码,即简单的算术运算来完成验证工作。

效果如下图示:

wordpress算术验证码验证码

不罗嗦了,直接贴出代码:

(一)在主题目录的functions.php添加如下代码:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
//算术验证码by vfhky
function spam_provent_math(){
  $a=rand(5,15);
  $b=rand(5,15);
  echo "<input type='text' name='sum' id='sum'  size='22' tabindex='3' value='动手又动脑,哦也 !' onfocus='if (this.value != \"\") {this.value = \"\";}' onblur='if (this.value == \"\") {this.value = \"动手又动脑,哦也 !\";}' /> = $a + $b (<font color='#0088DD'>防止机器人评论</font>)" ."<input type='hidden' name='a' value='$a'/>" ."<input type='hidden' name='b' value='$b'/>";
}
function spam_provent_pre($spam_result){
  $sum=$_POST['sum'];
  switch($sum){
    case $_POST['a']+$_POST['b']:break;
    case null:wp_die('亲,算个结果撒');break;
    default:wp_die('算错啦⊙﹏⊙b汗');
  }
  return $spam_result;
}
//注册用户or管理员则不需要验证
if(!is_user_logged_in() && $comment_data['comment_type']==''){
  add_filter('preprocess_comment','spam_provent_pre');
}
(二)在主题目录下的comments.php(不同的主题可能评论框的位置不同,有的主题可能在functions.php里面)中调用上述代码:
//根据是否是管理员来决定是否显示验证码
<php if(!is_user_logged_in())spam_provent_math();?>
Update 2013.03.09 00:26

感谢 @Teddysun 指出本代码的BUG——普通评论者在初次留言后,如果继续留言的话就需要点击“更改”按钮,否则直接留言的话会被程序视为未登录者而调用spam_provent_math()函数,从而造成BUG。问题出在is_user_logged_in()函数身上,先给出解决方案:

在wp官方论坛也有人遇到了类似问题:is_user_logged_in not working as expected。其中,有两位大牛的回答我觉得还是有见地的。

Asbj?rn Ulsberg的解释是:

他在查看了is_user_logged_in()的底层函数之后发觉内部调用wp_parse_auth_cookie()会返回false。因为根据var_dump($_COOKIE)打印输出的变量$_COOKIE的值可以揭示wp_parse_auth_cookie()正在查找的cookies是完全不存在的。

I've dug into the underlying code of is_user_logged_in() and found that the inner call to wp_parse_auth_cookie() returns false. A var_dump($_COOKIE) reveals that there's actually no cookies named anything like what wp_parse_auth_cookie() is looking for.
rhysgoodwin的个人博客解释是:

wp_signon()函数可能出于某些原因使得$current_user之类的全局变量在页面刷新前是无法存储的,而且调用get_currentuserinfo()也无法产生。而is_user_logged_in()也是返回false,因此认为是cookie的认证过程出了什么问题引起的。

The wp_signon() function logs a user in but for some reason after doing so the global user variables such as $current_user and $user_ID are not populated until the page is refreshed and calling get_currentuserinfo() doesnt populate them. The is_user_logged_in() function also returns false.  I think the problem has something to do with the cookie authentication process.
(三)我们也不去深入考究上面两位大牛的具体,结合博客先前评论遇到的实际BUG,有一点是可以确定的:is_user_logged_in()只能“一次性”使用,一旦页面刷新就无法起作用了。所以我把步骤(二)中的调用代码改成
<?php if(!isset($_COOKIE['comment_author_email_'.COOKIEHASH]))spam_provent_math();?>

经博主的验证测试木有出现上面的BUG了,问题解决!!!

Update 2013.04.12 22:10

感谢 @来哥 指出本代码的BUG——用户在错误输入验证码结果后会导致博客布局错乱、页面变形。解决方案见《再谈wordpress评论验证码——Willin Kan大神开的一个“玩笑”》

打赏支持

Comments »