Typecho不使用插件实现Ajax评论功能

Typecho教程 typecho 模板 插件 2019-04-28

原文出自绛木子博客https://lixianhua.com/te_ajax_comment_without_pluign.html

为了不使用插件实现Ajax评论功能需要实现:

1,监听评论表单,改用ajax方式提交
2,创建新的评论表单提交地址(用Typecho主题提供的系统方法themeInit实现)

当访问文章加载主题时,themeInit方法首先被加载,可在此方法中判断是否为添加评论的操作,即新的评论表单地址为文章的链接(permalink).具体判断方法如下

// 主题初始化
function themeInit($archive){
    // 判断是否是添加评论的操作
    // 为文章或页面、post操作,且包含参数`themeAction=comment`(自定义)
    if($archive->is('single') && $archive->request->isPost() && $archive->request->is('themeAction=comment')){
        // 为添加评论的操作时
        ajaxComment($archive);
    }
}

要实现ajax评论,则无法使用系统默认的feedback,这里我们将复制feedback功能并改造为我们需要的方法

/**
 * ajaxComment
 * 实现Ajax评论的方法(实现feedback中的comment功能)
 * @param Widget_Archive $archive
 * @return void
 */
function ajaxComment($archive){
    $options = Helper::options();
    $user = Typecho_Widget::widget('Widget_User');
    $db = Typecho_Db::get();
    // Security 验证不通过时会直接跳转,所以需要自己进行判断
    // 需要开启反垃圾保护,此时将不验证来源
    if($archive->request->get('_') != Helper::security()->getToken($archive->request->getReferer())){
        $archive->response->throwJson(array('status'=>0,'msg'=>_t('非法请求')));
    }
    /** 评论关闭 */
    if(!$archive->allow('comment')){
        $archive->response->throwJson(array('status'=>0,'msg'=>_t('评论已关闭')));
    }
    /** 检查ip评论间隔 */
    if (!$user->pass('editor', true) && $archive->authorId != $user->uid &&
    $options->commentsPostIntervalEnable){
        $latestComment = $db->fetchRow($db->select('created')->from('table.comments')
                    ->where('cid = ?', $archive->cid)
                    ->where('ip = ?', $archive->request->getIp())
                    ->order('created', Typecho_Db::SORT_DESC)
                    ->limit(1));

        if ($latestComment && ($options->gmtTime - $latestComment['created'] > 0 &&
        $options->gmtTime - $latestComment['created'] < $options->commentsPostInterval)) {
            $archive->response->throwJson(array('status'=>0,'msg'=>_t('对不起, 您的发言过于频繁, 请稍侯再次发布')));
        }        
    }

    $comment = array(
        'cid'       =>  $archive->cid,
        'created'   =>  $options->gmtTime,
        'agent'     =>  $archive->request->getAgent(),
        'ip'        =>  $archive->request->getIp(),
        'ownerId'   =>  $archive->author->uid,
        'type'      =>  'comment',
        'status'    =>  !$archive->allow('edit') && $options->commentsRequireModeration ? 'waiting' : 'approved'
    );

    /** 判断父节点 */
    if ($parentId = $archive->request->filter('int')->get('parent')) {
        if ($options->commentsThreaded && ($parent = $db->fetchRow($db->select('coid', 'cid')->from('table.comments')
        ->where('coid = ?', $parentId))) && $archive->cid == $parent['cid']) {
            $comment['parent'] = $parentId;
        } else {
            $archive->response->throwJson(array('status'=>0,'msg'=>_t('父级评论不存在')));
        }
    }
    $feedback = Typecho_Widget::widget('Widget_Feedback');
    //检验格式
    $validator = new Typecho_Validate();
    $validator->addRule('author', 'required', _t('必须填写用户名'));
    $validator->addRule('author', 'xssCheck', _t('请不要在用户名中使用特殊字符'));
    $validator->addRule('author', array($feedback, 'requireUserLogin'), _t('您所使用的用户名已经被注册,请登录后再次提交'));
    $validator->addRule('author', 'maxLength', _t('用户名最多包含200个字符'), 200);

    if ($options->commentsRequireMail && !$user->hasLogin()) {
        $validator->addRule('mail', 'required', _t('必须填写电子邮箱地址'));
    }

    $validator->addRule('mail', 'email', _t('邮箱地址不合法'));
    $validator->addRule('mail', 'maxLength', _t('电子邮箱最多包含200个字符'), 200);

    if ($options->commentsRequireUrl && !$user->hasLogin()) {
        $validator->addRule('url', 'required', _t('必须填写个人主页'));
    }
    $validator->addRule('url', 'url', _t('个人主页地址格式错误'));
    $validator->addRule('url', 'maxLength', _t('个人主页地址最多包含200个字符'), 200);

    $validator->addRule('text', 'required', _t('必须填写评论内容'));

    $comment['text'] = $archive->request->text;

    /** 对一般匿名访问者,将用户数据保存一个月 */
    if (!$user->hasLogin()) {
        /** Anti-XSS */
        $comment['author'] = $archive->request->filter('trim')->author;
        $comment['mail'] = $archive->request->filter('trim')->mail;
        $comment['url'] = $archive->request->filter('trim')->url;

        /** 修正用户提交的url */
        if (!empty($comment['url'])) {
            $urlParams = parse_url($comment['url']);
            if (!isset($urlParams['scheme'])) {
                $comment['url'] = 'http://' . $comment['url'];
            }
        }

        $expire = $options->gmtTime + $options->timezone + 30*24*3600;
        Typecho_Cookie::set('__typecho_remember_author', $comment['author'], $expire);
        Typecho_Cookie::set('__typecho_remember_mail', $comment['mail'], $expire);
        Typecho_Cookie::set('__typecho_remember_url', $comment['url'], $expire);
    } else {
        $comment['author'] = $user->screenName;
        $comment['mail'] = $user->mail;
        $comment['url'] = $user->url;

        /** 记录登录用户的id */
        $comment['authorId'] = $user->uid;
    }

    /** 评论者之前须有评论通过了审核 */
    if (!$options->commentsRequireModeration && $options->commentsWhitelist) {
        if ($feedback->size($feedback->select()->where('author = ? AND mail = ? AND status = ?', $comment['author'], $comment['mail'], 'approved'))) {
            $comment['status'] = 'approved';
        } else {
            $comment['status'] = 'waiting';
        }
    }

    if ($error = $validator->run($comment)) {
        $archive->response->throwJson(array('status'=>0,'msg'=> implode(';',$error)));
    }

    /** 添加评论 */
    $commentId = $feedback->insert($comment);
    if(!$commentId){
        $archive->response->throwJson(array('status'=>0,'msg'=>_t('评论失败')));
    }
    Typecho_Cookie::delete('__typecho_remember_text');
    $db->fetchRow($feedback->select()->where('coid = ?', $commentId)
    ->limit(1), array($feedback, 'push'));
    // 返回评论数据
    $data = array(
        'cid' => $feedback->cid,
        'coid' => $feedback->coid,
        'parent' => $feedback->parent,
        'mail' => $feedback->mail,
        'url' => $feedback->url,
        'ip' => $feedback->ip,
        'agent' => $feedback->agent,
        'author' => $feedback->author,
        'authorId' => $feedback->authorId,
        'permalink' => $feedback->permalink,
        'created' => $feedback->created,
        'datetime' => $feedback->date->format('Y-m-d H:i:s'),
        'status' => $feedback->status,
    );
    // 评论内容
    ob_start();
    $feedback->content();
    $data['content'] = ob_get_clean();

    $data['avatar'] = Typecho_Common::gravatarUrl($data['mail'], 48, Helper::options()->commentsAvatarRating, NULL, $archive->request->isSecure());
    $archive->response->throwJson(array('status'=>1,'comment'=>$data));
}

当已经在functions.php文件中添加完上述方法时,就已经可以接收ajax评论了。
此时我们需要修改评论表单添加的方式及提交的地址。

footer.php文件中添加方法

// 依赖jquery,请自行加载
$(function(){
    // 监听评论表单提交
    $('#comment-form').submit(function(){
        var form = $(this), params = form.serialize();
        // 添加functions.php中定义的判断参数
        params += '&themeAction=comment';
        
        // 解析新评论并附加到评论列表
        var appendComment = function(comment){
            // 评论列表
            var el = $('#comments > .comment-list');
            if(0 != comment.parent){
                // 子评论则重新定位评论列表
                var el = $('#comment-'+comment.parent);
                // 父评论不存在子评论时
                if(el.find('.comment-children').length < 1){
                    $('<div class="comment-children"><ol class="comment-list"></ol></div>').appendTo(el);
                }else if(el.find('.comment-children > .comment-list').length <1){
                    $('<ol class="comment-list"></ol>').appendTo(el.find('.comment-children'));
                }
                el = $('#comment-'+comment.parent).find('.comment-children').find('.comment-list');
            }
            if(0 == el.length){
                $('<ol class="comment-list"></ol>').appendTo($('#comments'));
                el = $('#comments > .comment-list');
            }
                        // 评论html模板,根据具体主题定制
            var html = '<li id="comment-{coid}" class="comment-body comment-ajax"><div class="comment-author"><span><img class="avatar" src="{avatar}" alt="{author}" width="32" height="32"></span><cite class="fn">{author}</cite></div><div class="comment-meta"><a href="{permalink}"><time>{datetime}</time></a></div><div class="comment-content">{content}</div></li>';
            $.each(comment,function(k,v){
                regExp = new RegExp('{'+k+'}', 'g');
                html = html.replace(regExp, v);
            });
            $(html).appendTo(el);
        }
        // ajax提交评论
        $.ajax({
            url: '<?php $this->permalink();?>',
            type: 'POST',
            data: params,
            dataType: 'json',
            beforeSend: function() { form.find('.submit').addClass('loading').html('<i class="icon icon-loading icon-pulse"></i> 提交中...').attr('disabled','disabled');},
            complete: function() { form.find('.submit').removeClass('loading').html('提交评论').removeAttr('disabled');},
            success: function(result){
                if(1 == result.status){
                    // 新评论附加到评论列表
                    appendComment(result.comment);
                    form.find('textarea').val('');
                }else{
                    // 提醒错误消息
                    alert(undefined === result.msg ? '<?php _e('评论出错!');?>' : result.msg);
                }
            },
            error:function(xhr, ajaxOptions, thrownError){
                alert('评论失败,请重试');
            }
        });
        return false;
    });
});

ajax评论需要自定义评论模板,获取使用其他方式拼接评论html

评论form表单的提交按钮需要添加class="submit"或修改代码为其他自定义的class

注:需开启评论的反垃圾保护

以上为转载内容,在实际投入使用时遇到了两点问题

一,通过ajax进行评论,邮件通知插件并不会发出通知!
原因是因为重写了评论的函数,而函数中没写评论完成后触发插件接口,所以邮件通知插件不会发邮件给作者。
解决方法也很简单在上述php代码$db->fetchRow($feedback->select()->where('coid = ?', $commentId)->limit(1), array($feedback, 'push'));后面加入$feedback->pluginHandle()->finishComment($feedback);即可。

二,评论过滤插件也会失效!
原因也是没有写入评论过滤的接口
这个接口我试着往里面写,没有成功,所以换了个方式来解决,直接在里面写过滤,而不是用插件过滤评论。
比如在$commentId = $feedback->insert($comment);前面加入

if (preg_match("/[\x{4e00}-\x{9fa5}]/u", $comment['text']) == 0) {
$archive->response->throwJson(array('status'=>0,'msg'=>_t('评论内容请不少于一个中文汉字')));
}

即可屏蔽非中文评论,建议同时使用评论过滤插件,因为有些垃圾评论是通过评论机器人完成的,并不会经过ajax评论,所以还需要继续使用评论过滤插件,亦或者在模板层面上用上评论过滤接口(我是这么做的,目前来看大概是有效果的)

22条评论

    尚寂新

    盘了一会 感觉不错 就是不知道ajax评论之后 回复按钮的情况...(感觉发完这条之后就知道了

    用户 Windows10 70天前回复

      泽泽社长
      @尚寂新

      我直接没让他显示,反正不重要,懒得研究啦

      作者 Windows7 70天前回复

        尚寂新
        @泽泽社长

        也是 刚才也研究了一下 te开发版的那个回调接口也跟着gg了 看来以后评论过滤都得在这一层搞了...

        用户 Windows10 70天前回复

          泽泽社长
          @尚寂新

          我其实已经不需要这么套一层评论过滤的 教程留点瑕疵挺好的,有助于读者进行思考

          作者 Windows7 70天前回复

            尚寂新
            @泽泽社长

            emmm...我已经有点开始思考不明白了 评论状态审核那块一搞一个waiting...

            用户 Windows10 70天前回复

    黑洞

    正需要这个

    游客 Windows10 114天前回复

    枫叶

    部署好把玩了好一会儿。发现了两个可以看见的问题。点击评论发送后,不能跟你这样的显示提交中,我确定绑定submit了,也审查了下元素,点击发送,html确实有变化,html里有提交中,但是在这个按钮上没显示。还有一个就是新评论不能第一个显示,而是显示在当前页面的最后一个。刷新下页面就跑到第一个了。

    游客 Windows10 163天前回复

      泽泽社长
      @枫叶

      js代码中的$(html).appendTo(el);改成$(html).prependTo(el);评论就会默认跑到第一个了

      作者 Linux 163天前回复

    枫叶

    好东西,成了。就是火狐浏览器上的弹窗提示太鸡儿low了。谷歌浏览器上的弹窗提示还能接受。。。

    游客 Windows10 163天前回复

      泽泽社长
      @枫叶

      新模板的提示已经换个好看的了

      作者 Windows7 70天前回复

    枫叶

    我谷歌了下我这个说法的官方词语,叫做:pjax无刷新返回数据。就是提交后的数据不会新开一个页面来显示,而是直接在当前页面的某个地方来显示提示的内容。

    游客 Windows10 164天前回复

      泽泽社长
      @枫叶

      typecho有很多pjax的教程,你可以搜搜看,如果想要pajx 的模板推荐隔壁的这个https://blog.imalan.cn/

      作者 Windows7 163天前回复

    枫叶

    其实我对pjax也有一种热爱,但是看到好多的博客里的pjax很少有完美的。我现在只奢求页面部分pjax,比如说评论没有填写内容点击提交评论后会跳入一个页面来提示。这个体验很差劲。然而这部分好像没人做单独的pjax。

    游客 Windows10 164天前回复

      泽泽社长
      @枫叶

      没理解你的意思,我的这个文章说的是评论ajax,当前模板也只是评论加了ajax,并没有全站加入pjax,暂时也不准备加

      作者 Windows7 164天前回复

    南蛰

    同纯js实现,实现起来简单的多

    游客 MacOS 171天前回复

    子午

    还好,不过浏览器有自动填表基本都有记录了,使用时间更久。[ 实际是我按照你的教程做了一遍,没成功! ]

    游客 Windows10 173天前回复

    子午

    看看啥效果~!

    游客 Windows10 174天前回复

      泽泽社长
      @子午

      感觉如何呀

      作者 Linux 173天前回复

    熊猫小A

    这相当于是自己实现了一遍评论的逻辑呀,很厉害。另外可以看看 Siphils 的这篇,我在我的主题上使用了他的方法,后端代码不需要怎么改,主要是前端的工作,对插件的兼容性也很好。

    游客 Windows10 174天前回复

      泽泽社长
      @熊猫小A

      我这个兼容也做好了,评论过滤功能集成到模板里,邮件提醒接口也写进去了,应该没啥问题了,他的纯js的,我看着懵逼,我其实js基础很差的

      作者 174天前回复

        Hran
        @泽泽社长

        我的也是纯 JS 实现,后端不需要做修改

        游客 Windows10 173天前回复

          泽泽社长
          @Hran

          萌新的我瑟瑟发抖

          作者 Linux 172天前回复

智能推荐