<?php
namespace TypechoPlugin\LoginCaptcha;
use Typecho\Common;
use Typecho\Cookie;
use Typecho\Plugin\PluginInterface;
use Typecho\Widget\Helper\Form;
use Typecho\Widget\Helper\Form\Element\Select;
use Typecho\Request as HttpRequest;
use Typecho\Response as HttpResponse;
use Typecho\Widget\Request;
use Typecho\Widget\Response;
use Utils\Helper;
use Widget\Notice;
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
/**
* Typecho 后台登录页面增加验证码保护
*
* @package LoginCaptcha
* @author Yaner
* @version 2.0.1
* @link https://typecho.com.mp
*
*/
class Plugin implements PluginInterface
{
// 添加一个常量控制开关
const ENABLE_CAPTCHA = true; // true=on false=off
public static function activate()
{
\Typecho\Plugin::factory('index.php')->begin = [__CLASS__, 'hookRoute'];
Helper::addRoute('login-captcha', '/login-captcha', Action::class, 'renderCaptcha');
\Typecho\Plugin::factory('admin/footer.php')->end = [__CLASS__, 'addCaptcha'];
}
public static function deactivate()
{
Helper::removeRoute('login-captcha');
}
public static function config(Form $form)
{
// 添加验证码类型选择配置
$captchaType = new Select(
'captchaType',
[
'alphabet' => '纯字母',
'numeric' => '数字加法',
'mixed' => '数字汉字加法',
'random' => '随机'
],
'numeric',
_t('选择验证码类型')
);
$form->addInput($captchaType);
}
public static function personalConfig(Form $form)
{
// 个人配置项
}
public static function hookRoute()
{
if (!self::ENABLE_CAPTCHA) {
return;
}
$options = Helper::options();
if (!isset($options->plugins['activated']['LoginCaptcha']) || $options->plugin('LoginCaptcha')->enableCaptcha === 'disabled') {
return;
}
$request = new Request(HttpRequest::getInstance());
$response = new Response(HttpRequest::getInstance(), HttpResponse::getInstance());
$pathinfo = $request->getPathInfo();
// 确保会话已启动
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
// 检查验证码
if (preg_match("#/action/login#", $pathinfo)) {
if (!isset($_SESSION['captcha']) || $_POST['captcha'] != $_SESSION['captcha']) {
Notice::alloc()->set(_t('验证码错误'), 'error');
Cookie::set('__typecho_remember_captcha', '');
$response->goBack();
}
}
}
public static function addCaptcha()
{
if (!self::ENABLE_CAPTCHA) {
return;
}
$options = Helper::options();
if (!isset($options->plugins['activated']['LoginCaptcha']) || $options->plugin('LoginCaptcha')->enableCaptcha === 'disabled') {
return;
}
$request = new Request(HttpRequest::getInstance());
$pathinfo = $request->getRequestUri();
$loginPath = Common::url('login.php', defined('__TYPECHO_ADMIN_DIR__') ? __TYPECHO_ADMIN_DIR__ : '/admin/');
$secureUrl = Helper::security()->getIndex('login-captcha');
if (stripos($pathinfo, $loginPath) === 0) {
?>
<script>
(function () {
var src = '<?php echo $secureUrl ?>';
var pwd = document.getElementById('password');
if (pwd) {
var captchaSection = document.createElement('p');
captchaSection.id = 'captcha-section';
var captchaLabel = document.createElement('label');
captchaLabel.className = 'sr-only';
captchaLabel.setAttribute('for', 'captcha');
captchaLabel.innerText = '<?php _e("验证码"); ?>';
captchaSection.appendChild(captchaLabel);
var captchaInput = document.createElement('input');
captchaInput.type = 'text';
captchaInput.name = 'captcha';
captchaInput.id = 'captcha';
captchaInput.className = 'text-l w-100';
captchaInput.placeholder = '<?php _e("填写答案"); ?>';
captchaInput.required = true;
captchaSection.appendChild(captchaInput);
var captchaImg = document.createElement('img');
captchaImg.id = 'captcha-img';
captchaImg.src = src;
captchaImg.title = '<?php _e("点击刷新") ?>';
captchaSection.appendChild(captchaImg);
pwd.parentNode.insertAdjacentElement('afterend', captchaSection);
captchaImg.onclick = function () {
if (captchaImg.classList.contains('not-allow')) {
return;
}
captchaImg.classList.add('not-allow');
captchaImg.src = src + '&t=' + Math.random();
setTimeout(function () {
captchaImg.classList.remove('not-allow');
}, 1000);
};
}
})();
</script>
<style>
#captcha-section {
display: flex;
}
#captcha {
box-sizing: border-box;
}
#captcha:invalid:not(:placeholder-shown) {
border: 2px solid red;
}
#captcha:valid {
border: 2px solid green;
}
#captcha-img {
cursor: pointer;
}
#captcha-img.not-allow {
cursor: not-allowed;
}
</style>
<?php
}
}
}
?>

Plugin.php 验证码插件更新:编辑文本控制插件启用、停,以便应对异常时不能登录后。