ZigZagK的博客
让Typecho文章加密兼容PJAX
2020年5月17日 01:11
Typecho
查看标签

问题

Typecho自带了文章加密(给文章设置访问密码),但是存在奇怪的bug,以及不兼容PJAX。

  1. 由于加密文章返回403状态码,所以无法PJAX跳转页面。
  2. 存在多篇加密文章时,存在密码错乱现象(非PJAX主题也存在此问题,貌似是由于所有加密文章采用相同Cookie导致的)。

一如既往地,网上没有找到相关教程QAQ,只能自己瞎搞了。

突发奇想

我目前找到唯一能够解决PJAX跳转问题的只有泽泽社长的插件,看了下实现方式感觉不能直接移植进主题中。不过我发现该插件半路阻止了Typecho原先的加密文章策略,这可能就是解决了403的原因(在Typecho返回403状态码之前结束了该策略),根据这个猜测我们就可以采取类似的方式来解决了!

暴力解决403状态码问题

是的,它非常的暴力,只需要在functions.php中的themeInit函数最前面加上:

function themeInit($archive){
    if ($archive->hidden) header('HTTP/1.1 200 OK');
    //$archive->hidden判断是否是加密文章
    //强行返回200状态码
}

这样就可以顺利的用PJAX跳转进加密文章了。

AJAX套娃解决密码错乱

首先我们参考泽泽社长的教程,自定义密码验证表单(下面是MDUI2333中的模板):

<?php if ($this->hidden){ ?>
<form action="<?php echo Typecho_Widget::widget('Widget_Security')->getTokenUrl($this->permalink); ?>" method="post" class="mdui-center" id="password-form">
    <div class="mdui-valign mdui-center" style="width:100%;max-width:500px;">
        <div class="mdui-textfield" style="display:inline-block;width:100%;margin-right:8px;">
            <input class="mdui-textfield-input" type="text" name="protectPassword" placeholder="请输入密码访问" />
            <?php if ($this->fields->passwordhint){ ?><div class="mdui-textfield-helper">提示:<?php echo $this->fields->passwordhint; ?></div><?php } ?>
        </div>
        <input type="hidden" name="protectCID" value="<?php $this->cid(); ?>" />
        <button class="mdui-btn mdui-btn-icon mdui-color-theme-accent" type="submit" mdui-tooltip="{content:'提交密码',position:'top'}"><i class="mdui-icon material-icons">&#xe5ca;</i></button>
    </div>
</form>
<?php } else $this->content(); ?>

然后很可怜的是,PJAX又双叒叕跪了,因为PJAX跳转时密码验证的链接(getTokenUrl)无法正确的获取,这时候我们参考熊猫小A的方法,采用AJAX获取正确的链接,然后用AJAX提交表单。

第一层AJAX

先在后端写上对应的接口,在functions.phpthemeInit函数随便什么位置加上下面的代码:

function themeInit($archive){
    if ($archive->is('post') && $_SERVER['REQUEST_METHOD']=='POST' && $posts['type']=='getTokenUrl')
        die(Typecho_Widget::widget('Widget_Security')->getTokenUrl($archive->permalink));
}

前端对应的js代码(信息提示部分如mdui.alert需要自定义,下同):

$('#password-form').submit(function(){
    var passworddata=$(this).serializeArray();
    $.ajax({
        type:'POST',url:window.location.href,data:{'type':'getTokenUrl'},
        success:function(tokenurl){ //tokenurl即获取的链接
            //这里是第二层AJAX
        },
        error:function() {mdui.alert('发生了未知错误,请刷新页面');}
    });
    return false;
});

第二层AJAX

成功获取链接之后,采用AJAX提交密码:

$('#password-form').submit(function(){
    var passworddata=$(this).serializeArray();
    $.ajax({
        type:'POST',url:window.location.href,data:{'type':'getTokenUrl'},
        success:function(tokenurl){
            $.ajax({
                type:'POST',url:tokenurl,data:passworddata,
                success:function(data){
                    //这里是第三层AJAX
                },
                error:function() {mdui.alert('发生了未知错误,请刷新页面');}
            });
        },
        error:function() {mdui.alert('发生了未知错误,请刷新页面');}
    });
    return false;
});

不过,AJAX提交显然还是避免不了密码错乱的问题,所以需要第三层AJAX。

第三层AJAX

密码错乱只是影响了返回的结果(输入了正确的密码,并且Cookie已经记录了,却还是返回密码输入错误),因此我们不根据返回的结果判断密码是否正确,而是自己写一个接口。

functions.phpthemeInit函数随便什么位置加上下面的代码:

//printjson和printarray函数在下面会用到
function printjson($json) {header('Content-type:application/json;charset=utf-8');die($json);}
function printarray($data) {printjson(json_encode($data));}
function themeInit($archive){
    if ($archive->is('post') && $_SERVER['REQUEST_METHOD']=='POST' && $posts['type']=='ishidden')
        printarray(array('hidden'=>$archive->hidden));
}

然后根据返回的hidden值(true/false)就可以得知密码是否正确:

$('#password-form').submit(function(){
    var passworddata=$(this).serializeArray();
    $.ajax({
        type:'POST',url:window.location.href,data:{'type':'getTokenUrl'},
        success:function(tokenurl){
            $.ajax({
                type:'POST',url:tokenurl,data:passworddata,
                success:function(data){
                    $.ajax({
                        type:'POST',url:window.location.href,data:{'type':'ishidden'},
                        success:function(data){
                            if (data.hidden) mdui.alert('对不起,您输入的密码错误'); //文章仍为加密状态,说明密码错误
                            else $.pjax({url:window.location.href,container:'#pjax-container',fragment:'#pjax-container',timeout:8000});
                            //文章不是加密状态,PJAX刷新页面
                        },
                        error:function() {mdui.alert('发生了未知错误,请刷新页面');}
                    });
                },
                error:function() {mdui.alert('发生了未知错误,请刷新页面');}
            });
        },
        error:function() {mdui.alert('发生了未知错误,请刷新页面');}
    });
    return false;
});

总代码

将上面的代码整合,得到完整的代码如下(部分内容请根据主题自行修改):

后端(functions.php):

function printjson($json) {header('Content-type:application/json;charset=utf-8');die($json);}
function printarray($data) {printjson(json_encode($data));}
function themeInit($archive){
    if ($archive->hidden) header('HTTP/1.1 200 OK');
    //其他内容
    if ($archive->is('post') && $_SERVER['REQUEST_METHOD']=='POST' && $posts['type']=='getTokenUrl')
        die(Typecho_Widget::widget('Widget_Security')->getTokenUrl($archive->permalink));
    if ($archive->is('post') && $_SERVER['REQUEST_METHOD']=='POST' && $posts['type']=='ishidden')
        printarray(array('hidden'=>$archive->hidden));
}

前端:

<?php if ($this->hidden){ ?>
<form class="mdui-center" id="password-form">
    <div class="mdui-valign mdui-center" style="width:100%;max-width:500px;">
        <div class="mdui-textfield" style="display:inline-block;width:100%;margin-right:8px;">
            <input class="mdui-textfield-input" type="text" name="protectPassword" placeholder="请输入密码访问" />
            <?php if ($this->fields->passwordhint){ ?><div class="mdui-textfield-helper">提示:<?php echo $this->fields->passwordhint; ?></div><?php } ?>
        </div>
        <input type="hidden" name="protectCID" value="<?php $this->cid(); ?>" />
        <button class="mdui-btn mdui-btn-icon mdui-color-theme-accent" type="submit" mdui-tooltip="{content:'提交密码',position:'top'}"><i class="mdui-icon material-icons">&#xe5ca;</i></button>
    </div>
</form>
<script>
    $('#password-form').submit(function(){
        var passworddata=$(this).serializeArray();
        $.ajax({
            type:'POST',url:window.location.href,data:{'type':'getTokenUrl'},
            success:function(tokenurl){
                $.ajax({
                    type:'POST',url:tokenurl,data:passworddata,
                    success:function(data){
                        $.ajax({
                            type:'POST',url:window.location.href,data:{'type':'ishidden'},
                            success:function(data){
                                if (data.hidden) mdui.alert('对不起,您输入的密码错误');
                                else $.pjax({url:window.location.href,container:'#pjax-container',fragment:'#pjax-container',timeout:8000});
                            },
                            error:function() {mdui.alert('发生了未知错误,请刷新页面');}
                        });
                    },
                    error:function() {mdui.alert('发生了未知错误,请刷新页面');}
                });
            },
            error:function() {mdui.alert('发生了未知错误,请刷新页面');}
        })
        return false;
    });
</script>
<?php } else $this->content(); ?>

测试

至此就解决了!可以跳转到这篇文章进行测试,如果PJAX跳转、AJAX密码验证都成功了就说明这篇教程可行啦2333。

版权声明:本博客所有文章除特别声明外,均采用 CC BY 4.0 CN协议 许可协议。转载请注明出处!
请不要发毫无意义或内容不文明的评论。与本文无关评论请发留言板!
初夏阳光
2020-05-17 22:07:19初夏阳光
2020-05-17 22:07:19

文章加密码?有问题啊小老弟 奥妙重重

访客
2020-05-17 22:31:21ZigZagK
2020-05-17 22:31:21
@初夏阳光 

我不是,我没有,别乱说 我的滑稽会冒汗

博主
2020-05-17 09:29:58ohmyga
2020-05-17 09:29:58

欸,好像有点复杂了..
其实可以直接判断请求是否由 PJAX 发起的,如果是则设置状态码为 200 (如果强行设置为 200 的话可能会被爬虫抓取?)
密码验证我是直接获取密码提交表单的链接,直接提交,然后取返回结果的 我的滑稽会冒汗

访客
2020-05-17 09:32:37ohmyga
2020-05-17 09:32:37
@ohmyga 

emm 没看见是多篇文章会错乱(

访客
2020-05-17 09:43:10ZigZagK
2020-05-17 09:43:10
@ohmyga 

我自己也觉得这套娃好复杂啊 😭
其实我试了下几个主题,只有您的Castle不会错乱,不知道什么原因,我有空再看看吧
倍感压力

博主