refactor(admin): consolidate duplicate configuration
Remove duplicate comment, captcha, filing, and website-security configuration paths. Move front submission verification to the captcha scene matrix and retire obsolete admin routes and components.
This commit is contained in:
@@ -36,15 +36,6 @@ export const extensionsSection = {
|
|||||||
canonicalDomain: 'extensions',
|
canonicalDomain: 'extensions',
|
||||||
section: 'push',
|
section: 'push',
|
||||||
}),
|
}),
|
||||||
customPage({
|
|
||||||
key: 'extensions-security',
|
|
||||||
name: 'ExtensionsSecurity',
|
|
||||||
label: '网站安全',
|
|
||||||
route: '/extensions/security',
|
|
||||||
component: 'ExtensionSecurity',
|
|
||||||
title: '网站安全',
|
|
||||||
desc: '扩展安全策略和第三方安全集成。',
|
|
||||||
}),
|
|
||||||
customPage({
|
customPage({
|
||||||
key: 'extensions-tools',
|
key: 'extensions-tools',
|
||||||
name: 'ExtensionsTools',
|
name: 'ExtensionsTools',
|
||||||
|
|||||||
@@ -1,176 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="aksrt-page-container">
|
|
||||||
<PageHeader title="站点设置" desc="集中管理评论、分页、懒加载、头像源与备案信息。">
|
|
||||||
<template #actions>
|
|
||||||
<n-button type="primary" :loading="saving" @click="handleSave">保存全部</n-button>
|
|
||||||
</template>
|
|
||||||
</PageHeader>
|
|
||||||
|
|
||||||
<n-spin :show="loading" style="width:100%; min-height:200px;">
|
|
||||||
<!-- 1. 评论 -->
|
|
||||||
<n-card class="aksrt-section-card">
|
|
||||||
<div class="aksrt-section-header">
|
|
||||||
<span class="section-icon"><svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></span>
|
|
||||||
<h3>评论设置</h3>
|
|
||||||
</div>
|
|
||||||
<n-form :model="basic" label-placement="left" label-width="140" class="aksrt-form">
|
|
||||||
<n-form-item label="全局评论开关">
|
|
||||||
<n-switch v-model:value="basic.comment_enabled">
|
|
||||||
<template #checked>已开启</template>
|
|
||||||
<template #unchecked>已关闭</template>
|
|
||||||
</n-switch>
|
|
||||||
<template #feedback>
|
|
||||||
<span class="aksrt-hint-text">关闭后全站禁用评论功能。</span>
|
|
||||||
</template>
|
|
||||||
</n-form-item>
|
|
||||||
|
|
||||||
</n-form>
|
|
||||||
</n-card>
|
|
||||||
|
|
||||||
<!-- 2. 分页 -->
|
|
||||||
<n-card class="aksrt-section-card">
|
|
||||||
<div class="aksrt-section-header">
|
|
||||||
<span class="section-icon"><svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg></span>
|
|
||||||
<h3>分页设置</h3>
|
|
||||||
</div>
|
|
||||||
<n-form :model="basic" label-placement="left" label-width="140" class="aksrt-form">
|
|
||||||
<n-form-item label="文章列表分页">
|
|
||||||
<n-radio-group v-model:value="basic.paging_ajax_s">
|
|
||||||
<n-space size="large">
|
|
||||||
<n-radio-button value="0">数字翻页</n-radio-button>
|
|
||||||
<n-radio-button value="1">加载更多</n-radio-button>
|
|
||||||
<n-radio-button value="1">无限滚动</n-radio-button>
|
|
||||||
</n-space>
|
|
||||||
</n-radio-group>
|
|
||||||
</n-form-item>
|
|
||||||
|
|
||||||
<n-form-item label="评论列表分页">
|
|
||||||
<n-radio-group v-model:value="basic.comment_pagination_mode">
|
|
||||||
<n-space size="large">
|
|
||||||
<n-radio-button value="0">数字翻页</n-radio-button>
|
|
||||||
<n-radio-button value="1">加载更多</n-radio-button>
|
|
||||||
<n-radio-button value="1">无限滚动</n-radio-button>
|
|
||||||
</n-space>
|
|
||||||
</n-radio-group>
|
|
||||||
</n-form-item>
|
|
||||||
</n-form>
|
|
||||||
</n-card>
|
|
||||||
|
|
||||||
<!-- 3. 懒加载 -->
|
|
||||||
<n-card class="aksrt-section-card">
|
|
||||||
<div class="aksrt-section-header">
|
|
||||||
<span class="section-icon"><svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg></span>
|
|
||||||
<h3>图片懒加载</h3>
|
|
||||||
</div>
|
|
||||||
<n-form :model="basic" label-placement="left" label-width="140" class="aksrt-form">
|
|
||||||
<n-form-item label="启用懒加载">
|
|
||||||
<n-switch v-model:value="basic.lazyload_enabled">
|
|
||||||
<template #checked>已开启</template>
|
|
||||||
<template #unchecked>已关闭</template>
|
|
||||||
</n-switch>
|
|
||||||
<template #feedback>
|
|
||||||
<span class="aksrt-hint-text">开启后图片在进入视口时才加载,可显著提升页面加载速度。</span>
|
|
||||||
</template>
|
|
||||||
</n-form-item>
|
|
||||||
|
|
||||||
<n-form-item v-if="basic.lazyload_enabled" label="自定义占位图 URL">
|
|
||||||
<n-input v-model:value="basic.lazyload_placeholder_url" placeholder="留空使用默认占位图" />
|
|
||||||
</n-form-item>
|
|
||||||
</n-form>
|
|
||||||
</n-card>
|
|
||||||
|
|
||||||
<!-- 5. 备案 -->
|
|
||||||
<n-card class="aksrt-section-card">
|
|
||||||
<div class="aksrt-section-header">
|
|
||||||
<span class="section-icon"><svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg></span>
|
|
||||||
<h3>备案信息</h3>
|
|
||||||
</div>
|
|
||||||
<n-form :model="basic" label-placement="left" label-width="120" class="aksrt-form">
|
|
||||||
<n-form-item label="ICP 备案号">
|
|
||||||
<n-input v-model:value="basic.icp_number" placeholder="例如:ICP 备 xxxxxxxx 号" />
|
|
||||||
</n-form-item>
|
|
||||||
|
|
||||||
<n-form-item label="公安备案号">
|
|
||||||
<n-input v-model:value="basic.police_number" placeholder="例如:公网安备 xxxxxx 号" />
|
|
||||||
</n-form-item>
|
|
||||||
</n-form>
|
|
||||||
</n-card>
|
|
||||||
|
|
||||||
<div class="aksrt-form-actions">
|
|
||||||
<n-button type="primary" :loading="saving" @click="handleSave">保存全部</n-button>
|
|
||||||
</div>
|
|
||||||
</n-spin>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { onMounted, reactive, ref } from 'vue'
|
|
||||||
import { useMessage } from 'naive-ui'
|
|
||||||
import api from '@/api'
|
|
||||||
import PageHeader from '@/components/PageHeader.vue'
|
|
||||||
|
|
||||||
const message = useMessage()
|
|
||||||
const loading = ref(true)
|
|
||||||
const saving = ref(false)
|
|
||||||
|
|
||||||
// 基础设置(canonical: aksrt_settings)
|
|
||||||
// 后端 /settings/basic 返回基础字段;auth 相关字段由 /settings/auth 独立维护。
|
|
||||||
const basic = reactive({
|
|
||||||
comment_enabled: true,
|
|
||||||
icp_number: '',
|
|
||||||
police_number: '',
|
|
||||||
lazyload_enabled: true,
|
|
||||||
lazyload_placeholder_url: '',
|
|
||||||
paging_ajax_s: '1',
|
|
||||||
comment_pagination_mode: 'numbered',
|
|
||||||
})
|
|
||||||
|
|
||||||
async function loadAll() {
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
const basicRes = await api.getBasicSettings()
|
|
||||||
if (basicRes?.code === 'success' && basicRes.data) {
|
|
||||||
const d = basicRes.data
|
|
||||||
basic.comment_enabled = d.comment_enabled !== false
|
|
||||||
basic.icp_number = d.icp_number || ''
|
|
||||||
basic.police_number = d.police_number || ''
|
|
||||||
basic.lazyload_enabled = d.lazyload_enabled !== false
|
|
||||||
basic.lazyload_placeholder_url = d.lazyload_placeholder_url || ''
|
|
||||||
basic.paging_ajax_s = d.paging_ajax_s ?? '1'
|
|
||||||
basic.comment_pagination_mode = d.comment_pagination_mode || 'numbered'
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
message.error(e.message || '加载失败')
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSave() {
|
|
||||||
saving.value = true
|
|
||||||
try {
|
|
||||||
const basicPayload = {
|
|
||||||
comment_enabled: basic.comment_enabled,
|
|
||||||
icp_number: basic.icp_number,
|
|
||||||
police_number: basic.police_number,
|
|
||||||
lazyload_enabled: basic.lazyload_enabled,
|
|
||||||
lazyload_placeholder_url: basic.lazyload_placeholder_url,
|
|
||||||
paging_ajax_s: basic.paging_ajax_s,
|
|
||||||
comment_pagination_mode: basic.comment_pagination_mode,
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await api.saveBasicSettings(basicPayload)
|
|
||||||
if (res?.code === 'success') {
|
|
||||||
message.success('已保存')
|
|
||||||
} else {
|
|
||||||
throw new Error(res?.message || '保存失败')
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
message.error(e.message || '保存失败')
|
|
||||||
} finally {
|
|
||||||
saving.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(loadAll)
|
|
||||||
</script>
|
|
||||||
@@ -1,311 +0,0 @@
|
|||||||
<template>
|
|
||||||
<ExtensionsSectionPage
|
|
||||||
title="网站安全"
|
|
||||||
desc="扩展安全策略和第三方安全集成。"
|
|
||||||
>
|
|
||||||
<n-card title="安全概览" size="small" class="aksrt-security__section">
|
|
||||||
<n-grid :cols="4" :x-gap="12" :y-gap="12">
|
|
||||||
<n-grid-item v-for="item in securityItems" :key="item.key">
|
|
||||||
<div class="aksrt-security__overview-card">
|
|
||||||
<FaIcon :icon="item.icon" :size="18" :color="item.color" />
|
|
||||||
<div>
|
|
||||||
<strong>{{ item.value }}</strong>
|
|
||||||
<p>{{ item.label }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</n-grid-item>
|
|
||||||
</n-grid>
|
|
||||||
</n-card>
|
|
||||||
|
|
||||||
<n-card title="安全能力" size="small" class="aksrt-security__section">
|
|
||||||
<n-list bordered size="small">
|
|
||||||
<n-list-item v-for="feat in features" :key="feat.key">
|
|
||||||
<n-thing :title="feat.name" :description="feat.description">
|
|
||||||
<template #avatar>
|
|
||||||
<FaIcon :icon="feat.icon" :size="16" :color="feat.color" />
|
|
||||||
</template>
|
|
||||||
<template #header-extra>
|
|
||||||
<n-space align="center" size="small">
|
|
||||||
<n-tag :type="feat.enabled ? 'success' : 'default'" size="small">
|
|
||||||
{{ feat.enabled ? '已启用' : '未启用' }}
|
|
||||||
</n-tag>
|
|
||||||
<router-link v-if="feat.settingsRoute" :to="feat.settingsRoute" class="aksrt-security__config-link">
|
|
||||||
前往配置
|
|
||||||
</router-link>
|
|
||||||
</n-space>
|
|
||||||
</template>
|
|
||||||
</n-thing>
|
|
||||||
</n-list-item>
|
|
||||||
</n-list>
|
|
||||||
</n-card>
|
|
||||||
|
|
||||||
<n-card title="在线测试" size="small" class="aksrt-security__section">
|
|
||||||
<n-grid :cols="2" :x-gap="16" :y-gap="16">
|
|
||||||
<n-grid-item>
|
|
||||||
<n-form label-placement="top">
|
|
||||||
<n-form-item label="IP 归属地测试">
|
|
||||||
<n-input v-model:value="ipTest.ip" placeholder="输入 IPv4 / IPv6,例如 8.8.8.8" />
|
|
||||||
</n-form-item>
|
|
||||||
<n-space>
|
|
||||||
<n-button :loading="ipTest.loading" @click="handleTestIp">测试 IP 接口</n-button>
|
|
||||||
</n-space>
|
|
||||||
<pre v-if="ipTest.result" class="aksrt-security__output">{{ ipTest.result }}</pre>
|
|
||||||
</n-form>
|
|
||||||
</n-grid-item>
|
|
||||||
<n-grid-item>
|
|
||||||
<n-form label-placement="top">
|
|
||||||
<n-form-item label="内容审核测试">
|
|
||||||
<n-input v-model:value="auditTest.text" type="textarea" :autosize="{ minRows: 4, maxRows: 8 }" placeholder="输入一段测试文本,检查当前审核策略是否生效" />
|
|
||||||
</n-form-item>
|
|
||||||
<n-space>
|
|
||||||
<n-button :loading="auditTest.loading" @click="handleTestAudit">测试内容审核</n-button>
|
|
||||||
</n-space>
|
|
||||||
<pre v-if="auditTest.result" class="aksrt-security__output">{{ auditTest.result }}</pre>
|
|
||||||
</n-form>
|
|
||||||
</n-grid-item>
|
|
||||||
</n-grid>
|
|
||||||
</n-card>
|
|
||||||
|
|
||||||
<n-card title="风险开关" size="small" class="aksrt-security__section">
|
|
||||||
<n-alert type="warning" size="small" style="margin-bottom: 12px;">
|
|
||||||
以下开关会影响站点安全策略,请谨慎操作。
|
|
||||||
</n-alert>
|
|
||||||
<n-form label-placement="left" label-width="200" size="small">
|
|
||||||
<n-form-item label="允许未审核评论显示">
|
|
||||||
<n-switch v-model:value="riskSwitches.show_unapproved_comments" />
|
|
||||||
<n-text depth="3" style="margin-left: 8px; font-size: 12px;">
|
|
||||||
开启后未审核评论也会在前台显示(标记为待审核)
|
|
||||||
</n-text>
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="允许前台投稿无需审核">
|
|
||||||
<n-switch v-model:value="riskSwitches.skip_submit_moderation" />
|
|
||||||
<n-text depth="3" style="margin-left: 8px; font-size: 12px;">
|
|
||||||
开启后用户投稿直接发布,不经过审核流程
|
|
||||||
</n-text>
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="允许游客评论">
|
|
||||||
<n-switch v-model:value="riskSwitches.guest_comment" />
|
|
||||||
<n-text depth="3" style="margin-left: 8px; font-size: 12px;">
|
|
||||||
开启后未登录用户也可以发表评论
|
|
||||||
</n-text>
|
|
||||||
</n-form-item>
|
|
||||||
</n-form>
|
|
||||||
<template #action>
|
|
||||||
<n-space justify="end">
|
|
||||||
<n-button type="primary" size="small" :loading="saving" @click="handleSaveRisk">保存风险设置</n-button>
|
|
||||||
</n-space>
|
|
||||||
</template>
|
|
||||||
</n-card>
|
|
||||||
|
|
||||||
<n-card title="安全审计" size="small" class="aksrt-security__section">
|
|
||||||
<n-empty v-if="auditLogs.length === 0" description="暂无审计记录" />
|
|
||||||
<n-list v-else bordered size="small">
|
|
||||||
<n-list-item v-for="log in auditLogs" :key="log.id">
|
|
||||||
<n-thing :title="log.action" :description="log.detail">
|
|
||||||
<template #header-extra>
|
|
||||||
<n-text depth="3" style="font-size: 11px;">{{ log.time }}</n-text>
|
|
||||||
</template>
|
|
||||||
</n-thing>
|
|
||||||
</n-list-item>
|
|
||||||
</n-list>
|
|
||||||
</n-card>
|
|
||||||
</ExtensionsSectionPage>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, onMounted } from 'vue'
|
|
||||||
import { useMessage } from 'naive-ui'
|
|
||||||
import ExtensionsSectionPage from '@/pages/extensions/ExtensionsSectionPage.vue'
|
|
||||||
import FaIcon from '@/components/form/FaIcon.vue'
|
|
||||||
import api from '@/api/index.js'
|
|
||||||
|
|
||||||
const message = useMessage()
|
|
||||||
const saving = ref(false)
|
|
||||||
|
|
||||||
const securityItems = ref([
|
|
||||||
{ key: 'email_gate', icon: 'fas fa-envelope', color: '#1890ff', value: '—', label: '邮箱验证' },
|
|
||||||
{ key: 'captcha', icon: 'fas fa-shield-alt', color: '#52c41a', value: '—', label: '验证码保护' },
|
|
||||||
{ key: 'moderation', icon: 'fas fa-gavel', color: '#722ed1', value: '—', label: '内容审核' },
|
|
||||||
{ key: 'ip_geo', icon: 'fas fa-map-marker-alt', color: '#fa8c16', value: '—', label: 'IP 归属地' },
|
|
||||||
])
|
|
||||||
|
|
||||||
const features = ref([
|
|
||||||
{ key: 'email_verification', icon: 'fas fa-envelope', color: '#1890ff', name: '邮箱验证', description: '新用户注册需要邮箱验证', enabled: false, settingsRoute: '/users/auth' },
|
|
||||||
{ key: 'captcha_comment', icon: 'fas fa-shield-alt', color: '#52c41a', name: '评论验证码', description: '发表评论时需要人机验证', enabled: false, settingsRoute: '/permissions/login' },
|
|
||||||
{ key: 'captcha_download', icon: 'fas fa-shield-alt', color: '#52c41a', name: '下载验证码', description: '下载资源时需要人机验证', enabled: false, settingsRoute: '/permissions/login' },
|
|
||||||
{ key: 'content_audit', icon: 'fas fa-shield-virus', color: '#722ed1', name: '内容审核 API', description: '接入第三方内容安全审核', enabled: false, settingsRoute: '/extensions/audit' },
|
|
||||||
{ key: 'ip_geolocation', icon: 'fas fa-map-marker-alt', color: '#fa8c16', name: 'IP 归属地', description: '在评论和用户主页显示 IP 归属地', enabled: false, settingsRoute: '/extensions/ip' },
|
|
||||||
])
|
|
||||||
|
|
||||||
const riskSwitches = ref({
|
|
||||||
show_unapproved_comments: false,
|
|
||||||
skip_submit_moderation: false,
|
|
||||||
guest_comment: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
const auditLogs = ref([])
|
|
||||||
const ipTest = ref({ ip: '8.8.8.8', loading: false, result: '' })
|
|
||||||
const auditTest = ref({ text: '这是一段用于审核测试的示例文本。', loading: false, result: '' })
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
try {
|
|
||||||
const res = await api.request('/settings/security')
|
|
||||||
if (res?.data) {
|
|
||||||
const d = res.data
|
|
||||||
const svc = res.services || {}
|
|
||||||
|
|
||||||
const captcha = d.captcha || svc.captcha || {}
|
|
||||||
const captchaOn = !!(captcha.enabled && captcha.configured)
|
|
||||||
const captchaScenes = captcha.scenes || {}
|
|
||||||
const audit = d.content_audit || svc.audit || {}
|
|
||||||
const ipGeo = d.ip_geo || svc.ip_geo || {}
|
|
||||||
|
|
||||||
securityItems.value[0].value = d.email_gate_enabled ? '已开启' : '未开启'
|
|
||||||
securityItems.value[0].color = d.email_gate_enabled ? '#52c41a' : '#8a909c'
|
|
||||||
securityItems.value[1].value = captchaOn ? '已开启' : '未开启'
|
|
||||||
securityItems.value[1].color = captchaOn ? '#52c41a' : '#8a909c'
|
|
||||||
|
|
||||||
if (audit.enabled) {
|
|
||||||
securityItems.value[2].value = audit.configured ? '已配置' : '待补全'
|
|
||||||
securityItems.value[2].color = audit.configured ? '#52c41a' : '#faad14'
|
|
||||||
} else {
|
|
||||||
securityItems.value[2].value = '未开启'
|
|
||||||
securityItems.value[2].color = '#8a909c'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ipGeo.enabled) {
|
|
||||||
securityItems.value[3].value = ipGeo.configured ? '已配置' : '待补全'
|
|
||||||
securityItems.value[3].color = ipGeo.configured ? '#52c41a' : '#faad14'
|
|
||||||
} else {
|
|
||||||
securityItems.value[3].value = '未开启'
|
|
||||||
securityItems.value[3].color = '#8a909c'
|
|
||||||
}
|
|
||||||
|
|
||||||
features.value[0].enabled = !!d.email_gate_enabled
|
|
||||||
features.value[1].enabled = !!captchaScenes.comment
|
|
||||||
features.value[2].enabled = !!captcha.download_active
|
|
||||||
features.value[3].enabled = !!audit.enabled
|
|
||||||
features.value[4].enabled = !!ipGeo.enabled
|
|
||||||
|
|
||||||
if (d.risk_switches) {
|
|
||||||
riskSwitches.value = { ...riskSwitches.value, ...d.risk_switches }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Endpoint may not exist yet
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const logRes = await api.request('/security/audit-log')
|
|
||||||
if (logRes?.data?.logs) {
|
|
||||||
auditLogs.value = logRes.data.logs.slice(0, 10)
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Endpoint may not exist yet
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
async function handleSaveRisk() {
|
|
||||||
saving.value = true
|
|
||||||
try {
|
|
||||||
await api.request('/settings/security', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({ risk_switches: riskSwitches.value }),
|
|
||||||
})
|
|
||||||
message.success('风险设置已保存')
|
|
||||||
} catch (err) {
|
|
||||||
message.error('保存失败: ' + (err?.message || '未知错误'))
|
|
||||||
} finally {
|
|
||||||
saving.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleTestIp() {
|
|
||||||
ipTest.value.loading = true
|
|
||||||
ipTest.value.result = ''
|
|
||||||
try {
|
|
||||||
const res = await api.testIpGeo(String(ipTest.value.ip || '').trim())
|
|
||||||
const data = res?.data || {}
|
|
||||||
ipTest.value.result = JSON.stringify(data, null, 2)
|
|
||||||
if (data.ok) {
|
|
||||||
message.success(data.display ? `IP 测试成功:${data.display}` : 'IP 接口测试完成')
|
|
||||||
} else {
|
|
||||||
message.warning(data.message || '未查询到归属地信息')
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
message.error(err?.message || 'IP 测试失败')
|
|
||||||
} finally {
|
|
||||||
ipTest.value.loading = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleTestAudit() {
|
|
||||||
auditTest.value.loading = true
|
|
||||||
auditTest.value.result = ''
|
|
||||||
try {
|
|
||||||
const res = await api.testContentAudit(String(auditTest.value.text || '').trim())
|
|
||||||
const data = res?.data || {}
|
|
||||||
auditTest.value.result = JSON.stringify(data, null, 2)
|
|
||||||
if (data.ok) {
|
|
||||||
message.success(data.message || '内容审核测试完成')
|
|
||||||
} else {
|
|
||||||
message.warning(data.message || '内容审核未通过或配置异常')
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
message.error(err?.message || '内容审核测试失败')
|
|
||||||
} finally {
|
|
||||||
auditTest.value.loading = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.aksrt-security__section {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aksrt-security__overview-card {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 14px;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: #fafafa;
|
|
||||||
border: 1px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aksrt-security__overview-card strong {
|
|
||||||
display: block;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #1a1a2e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aksrt-security__overview-card p {
|
|
||||||
margin: 2px 0 0;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #8a909c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aksrt-security__config-link {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #1890ff;
|
|
||||||
text-decoration: none;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aksrt-security__config-link:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aksrt-security__output {
|
|
||||||
margin: 12px 0 0;
|
|
||||||
padding: 12px 14px;
|
|
||||||
border-radius: 12px;
|
|
||||||
background: #101828;
|
|
||||||
color: #d0d7e2;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1.7;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -90,7 +90,7 @@ const guides = [
|
|||||||
{ key: 'meilisearch', title: 'Meilisearch', desc: '配置搜索索引与同步策略', url: '#/global/search' },
|
{ key: 'meilisearch', title: 'Meilisearch', desc: '配置搜索索引与同步策略', url: '#/global/search' },
|
||||||
{ key: 'i18n', title: '多语言翻译', desc: '检查语言切换与术语配置', url: '#/global/i18n' },
|
{ key: 'i18n', title: '多语言翻译', desc: '检查语言切换与术语配置', url: '#/global/i18n' },
|
||||||
{ key: 'cdn', title: '静态资源与 CDN', desc: '核对前端资源地址与缓存策略', url: '#/global/common' },
|
{ key: 'cdn', title: '静态资源与 CDN', desc: '核对前端资源地址与缓存策略', url: '#/global/common' },
|
||||||
{ key: 'ip-geo', title: 'IP 归属地', desc: '验证 IP 查询服务与密钥配置', url: '#/extensions/security' },
|
{ key: 'ip-geo', title: 'IP 归属地', desc: '验证 IP 查询服务与密钥配置', url: '#/extensions/ip' },
|
||||||
{ key: 'payment', title: '支付与会员', desc: '核对网关、会员与充值配置', url: '#/payment/gateway' },
|
{ key: 'payment', title: '支付与会员', desc: '核对网关、会员与充值配置', url: '#/payment/gateway' },
|
||||||
{ key: 'credits', title: '积分与签到', desc: '检查积分规则、签到与邀请奖励', url: '#/users/signin' },
|
{ key: 'credits', title: '积分与签到', desc: '检查积分规则、签到与邀请奖励', url: '#/users/signin' },
|
||||||
{ key: 'badge', title: '等级与徽章', desc: '维护等级、认证与徽章体系', url: '#/users/badge' },
|
{ key: 'badge', title: '等级与徽章', desc: '维护等级、认证与徽章体系', url: '#/users/badge' },
|
||||||
|
|||||||
@@ -229,14 +229,8 @@ async function load() {
|
|||||||
const inviteReward = creditData.invite_reward || {}
|
const inviteReward = creditData.invite_reward || {}
|
||||||
const enabledScenarios = Array.isArray(captchaData.enabled_scenarios)
|
const enabledScenarios = Array.isArray(captchaData.enabled_scenarios)
|
||||||
? captchaData.enabled_scenarios
|
? captchaData.enabled_scenarios
|
||||||
: null
|
|
||||||
const disabledScenarios = Array.isArray(captchaData.disabled_scenarios)
|
|
||||||
? captchaData.disabled_scenarios
|
|
||||||
: []
|
: []
|
||||||
const sceneOn = (key) => {
|
const sceneOn = (key) => enabledScenarios.includes(key)
|
||||||
if (enabledScenarios) return enabledScenarios.includes(key)
|
|
||||||
return !disabledScenarios.includes(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
creditSnapshot.value = creditData
|
creditSnapshot.value = creditData
|
||||||
authSnapshot.value = authData
|
authSnapshot.value = authData
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
export const extensionsPageLoaders = {
|
export const extensionsPageLoaders = {
|
||||||
ExtensionTools: () => import('@/pages/extensions/ExtensionTools.vue'),
|
ExtensionTools: () => import('@/pages/extensions/ExtensionTools.vue'),
|
||||||
ExtensionSecurity: () => import('@/pages/extensions/ExtensionSecurity.vue'),
|
|
||||||
ExtensionAI: () => import('@/pages/extensions/ExtensionAI.vue'),
|
ExtensionAI: () => import('@/pages/extensions/ExtensionAI.vue'),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ export const commonSchema = {
|
|||||||
icon: 'fas fa-cog',
|
icon: 'fas fa-cog',
|
||||||
description: '管理站点常用开关、懒加载与备案信息等基础配置。',
|
description: '管理站点常用开关、懒加载与备案信息等基础配置。',
|
||||||
defaults: {
|
defaults: {
|
||||||
comment_enabled: true,
|
|
||||||
lazyload_enabled: true,
|
lazyload_enabled: true,
|
||||||
lazyload_animation: 'blur',
|
lazyload_animation: 'blur',
|
||||||
lazyload_placeholder: '',
|
lazyload_placeholder: '',
|
||||||
@@ -18,17 +17,10 @@ export const commonSchema = {
|
|||||||
lazyload_other: true,
|
lazyload_other: true,
|
||||||
lazyload_widget: true,
|
lazyload_widget: true,
|
||||||
icp_filing: '',
|
icp_filing: '',
|
||||||
|
police_filing: '',
|
||||||
footer_text: '',
|
footer_text: '',
|
||||||
},
|
},
|
||||||
groups: [
|
groups: [
|
||||||
{
|
|
||||||
key: 'comment',
|
|
||||||
title: '评论功能',
|
|
||||||
icon: 'fas fa-comment',
|
|
||||||
fields: [
|
|
||||||
{ key: 'comment_enabled', label: '开启评论', type: 'switch', default: true, help: '全局评论开关,关闭后所有文章不再显示评论区' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'performance',
|
key: 'performance',
|
||||||
title: '性能优化',
|
title: '性能优化',
|
||||||
@@ -72,6 +64,7 @@ export const commonSchema = {
|
|||||||
icon: 'fas fa-shield-alt',
|
icon: 'fas fa-shield-alt',
|
||||||
fields: [
|
fields: [
|
||||||
{ key: 'icp_filing', label: 'ICP 备案号', type: 'input', placeholder: '例如:京ICP备12345678号', help: '显示在页脚的备案信息' },
|
{ key: 'icp_filing', label: 'ICP 备案号', type: 'input', placeholder: '例如:京ICP备12345678号', help: '显示在页脚的备案信息' },
|
||||||
|
{ key: 'police_filing', label: '公安备案号', type: 'input', placeholder: '例如:公网安备11000000000000号', help: '显示在页脚的备案信息' },
|
||||||
{ key: 'footer_text', label: '页脚附加文字', type: 'input', placeholder: '自定义页脚文字', help: '显示在版权信息下方' },
|
{ key: 'footer_text', label: '页脚附加文字', type: 'input', placeholder: '自定义页脚文字', help: '显示在版权信息下方' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -824,9 +824,7 @@ export const pageDisplaySchemas = {
|
|||||||
footer_columns: 3,
|
footer_columns: 3,
|
||||||
footer_copyright: true,
|
footer_copyright: true,
|
||||||
footer_icp: true,
|
footer_icp: true,
|
||||||
icp_number: '',
|
|
||||||
footer_police: true,
|
footer_police: true,
|
||||||
police_number: '',
|
|
||||||
footer_theme_custom: false,
|
footer_theme_custom: false,
|
||||||
footer_theme_bg_custom: '',
|
footer_theme_bg_custom: '',
|
||||||
footer_theme_bg: '',
|
footer_theme_bg: '',
|
||||||
@@ -857,9 +855,7 @@ export const pageDisplaySchemas = {
|
|||||||
{ key: 'footer_columns', label: '页脚列数', type: 'number', default: 3, min: 1, max: 4 },
|
{ key: 'footer_columns', label: '页脚列数', type: 'number', default: 3, min: 1, max: 4 },
|
||||||
{ key: 'footer_copyright', label: '显示版权信息', type: 'switch', default: true },
|
{ key: 'footer_copyright', label: '显示版权信息', type: 'switch', default: true },
|
||||||
{ key: 'footer_icp', label: '显示ICP备案', type: 'switch', default: true },
|
{ key: 'footer_icp', label: '显示ICP备案', type: 'switch', default: true },
|
||||||
{ key: 'icp_number', label: 'ICP备案号', type: 'input', default: '' },
|
|
||||||
{ key: 'footer_police', label: '显示公安备案', type: 'switch', default: true },
|
{ key: 'footer_police', label: '显示公安备案', type: 'switch', default: true },
|
||||||
{ key: 'police_number', label: '公安备案号', type: 'input', default: '' },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export const permissionsSchemas = {
|
|||||||
'comment', 'login', 'register', 'bind', 'bind_phone',
|
'comment', 'login', 'register', 'bind', 'bind_phone',
|
||||||
'lostpassword', 'change_password', 'change_email', 'verify_email', 'download', 'signin',
|
'lostpassword', 'change_password', 'change_email', 'verify_email', 'download', 'signin',
|
||||||
],
|
],
|
||||||
disabled_scenarios: [],
|
|
||||||
tencent: {},
|
tencent: {},
|
||||||
aliyun: {},
|
aliyun: {},
|
||||||
cloudflare: {},
|
cloudflare: {},
|
||||||
@@ -75,6 +74,7 @@ export const permissionsSchemas = {
|
|||||||
{ label: '邮箱验证', value: 'verify_email' },
|
{ label: '邮箱验证', value: 'verify_email' },
|
||||||
{ label: '资源下载', value: 'download' },
|
{ label: '资源下载', value: 'download' },
|
||||||
{ label: '每日签到', value: 'signin' },
|
{ label: '每日签到', value: 'signin' },
|
||||||
|
{ label: '前台投稿', value: 'front_submit' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ export const submitSchema = {
|
|||||||
front_submit_draft_save: true,
|
front_submit_draft_save: true,
|
||||||
front_submit_leave_warning: true,
|
front_submit_leave_warning: true,
|
||||||
front_submit_guest_enabled: false,
|
front_submit_guest_enabled: false,
|
||||||
front_submit_captcha: false,
|
|
||||||
post_article_btn_txte: '发布文章',
|
post_article_btn_txte: '发布文章',
|
||||||
post_article_title_strlen_limit_min: 5,
|
post_article_title_strlen_limit_min: 5,
|
||||||
post_article_title_strlen_limit_max: 30,
|
post_article_title_strlen_limit_max: 30,
|
||||||
@@ -63,7 +62,6 @@ export const submitSchema = {
|
|||||||
{ key: 'post_article_cat', label: '允许投稿分类', type: 'taxonomy_select', taxonomy: 'category', multiple: true, default: [], help: '为空则允许全部分类', dependsOn: { key: 'front_submit_enabled', value: true } },
|
{ key: 'post_article_cat', label: '允许投稿分类', type: 'taxonomy_select', taxonomy: 'category', multiple: true, default: [], help: '为空则允许全部分类', dependsOn: { key: 'front_submit_enabled', value: true } },
|
||||||
{ key: 'front_submit_guest_enabled', label: '允许免登录投稿', type: 'switch', default: false, help: '开启后访客可投稿;文章归属到下方用户', dependsOn: { key: 'front_submit_enabled', value: true } },
|
{ key: 'front_submit_guest_enabled', label: '允许免登录投稿', type: 'switch', default: false, help: '开启后访客可投稿;文章归属到下方用户', dependsOn: { key: 'front_submit_enabled', value: true } },
|
||||||
{ key: 'post_article_user', label: '免登录归属用户', type: 'user_select', default: 0, help: '免登录投稿时文章归属的用户,搜索选择', dependsOn: { key: 'front_submit_guest_enabled', value: true } },
|
{ key: 'post_article_user', label: '免登录归属用户', type: 'user_select', default: 0, help: '免登录投稿时文章归属的用户,搜索选择', dependsOn: { key: 'front_submit_guest_enabled', value: true } },
|
||||||
{ key: 'front_submit_captcha', label: '提交人机验证', type: 'switch', default: false, help: '提交正式投稿时校验人机验证(需在安全扩展启用 captcha)', dependsOn: { key: 'front_submit_enabled', value: true } },
|
|
||||||
{ key: 'front_submit_tags', label: '标签输入', type: 'switch', default: true, dependsOn: { key: 'front_submit_enabled', value: true } },
|
{ key: 'front_submit_tags', label: '标签输入', type: 'switch', default: true, dependsOn: { key: 'front_submit_enabled', value: true } },
|
||||||
{ key: 'post_article_topics_s', label: '专题选择', type: 'switch', default: false, help: '投稿时可选择专题分类', dependsOn: { key: 'front_submit_enabled', value: true } },
|
{ key: 'post_article_topics_s', label: '专题选择', type: 'switch', default: false, help: '投稿时可选择专题分类', dependsOn: { key: 'front_submit_enabled', value: true } },
|
||||||
{ key: 'post_article_custom_filter_s', label: '高级筛选选择', type: 'switch', default: true, help: '投稿时可选择自定义筛选维度值', dependsOn: { key: 'front_submit_enabled', value: true } },
|
{ key: 'post_article_custom_filter_s', label: '高级筛选选择', type: 'switch', default: true, help: '投稿时可选择自定义筛选维度值', dependsOn: { key: 'front_submit_enabled', value: true } },
|
||||||
|
|||||||
@@ -210,6 +210,11 @@
|
|||||||
|
|
||||||
submit: function (btn, action) {
|
submit: function (btn, action) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
if (action !== 'posts_draft' && !this.requestCaptcha(function () {
|
||||||
|
self.submit(btn, action);
|
||||||
|
})) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.isSubmitting = true;
|
this.isSubmitting = true;
|
||||||
var original = btn.innerHTML;
|
var original = btn.innerHTML;
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
@@ -292,6 +297,25 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
requestCaptcha: function (onVerified) {
|
||||||
|
var verified = this.form.querySelector('input[name="captcha_verified"]');
|
||||||
|
if (verified && verified.value === '1') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
var captcha = window.AKSRTCaptchaState &&
|
||||||
|
window.AKSRTCaptchaState.scenes &&
|
||||||
|
window.AKSRTCaptchaState.scenes.front_submit;
|
||||||
|
if (!captcha || typeof captcha.verify !== 'function') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
captcha.verify(function (passed) {
|
||||||
|
if (passed) {
|
||||||
|
onVerified();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
bindDraftDelete: function () {
|
bindDraftDelete: function () {
|
||||||
document.addEventListener('click', function (e) {
|
document.addEventListener('click', function (e) {
|
||||||
var btn = e.target.closest('.aksrt-submit-drafts__delete');
|
var btn = e.target.closest('.aksrt-submit-drafts__delete');
|
||||||
|
|||||||
+7
-7
@@ -32,9 +32,9 @@ $more_links = $has_settings ? \AKSRT\Settings\Settings::get_more_links() : []
|
|||||||
$hot_cat_count = $has_settings ? \AKSRT\Settings\Settings::get_hot_categories_count() : 0;
|
$hot_cat_count = $has_settings ? \AKSRT\Settings\Settings::get_hot_categories_count() : 0;
|
||||||
|
|
||||||
// 备案 / 版权(customize 为空时回退)
|
// 备案 / 版权(customize 为空时回退)
|
||||||
$filing = $has_settings ? \AKSRT\Settings\Settings::get_filing_settings() : ['icp_enabled' => true, 'icp_number' => '', 'police_enabled' => true, 'police_number' => ''];
|
$filing = $has_settings ? \AKSRT\Settings\Settings::get_filing_settings() : ['icp_enabled' => true, 'icp_filing' => '', 'police_enabled' => true, 'police_filing' => ''];
|
||||||
$icp_number = !empty($filing['icp_enabled']) ? (string) ($filing['icp_number'] ?? '') : '';
|
$icp_filing = !empty($filing['icp_enabled']) ? (string) ($filing['icp_filing'] ?? '') : '';
|
||||||
$police_number = !empty($filing['police_enabled']) ? (string) ($filing['police_number'] ?? '') : '';
|
$police_filing = !empty($filing['police_enabled']) ? (string) ($filing['police_filing'] ?? '') : '';
|
||||||
$footer_text = $has_settings ? \AKSRT\Settings\Settings::footer_text() : '';
|
$footer_text = $has_settings ? \AKSRT\Settings\Settings::footer_text() : '';
|
||||||
$copyright_text = $has_settings ? \AKSRT\Settings\Settings::get_copyright_text() : '';
|
$copyright_text = $has_settings ? \AKSRT\Settings\Settings::get_copyright_text() : '';
|
||||||
|
|
||||||
@@ -145,11 +145,11 @@ $_has_cols = $_has_brand || $_has_links_col || $_has_mini;
|
|||||||
$_footer_conter = trim((string) $footer_customize_code);
|
$_footer_conter = trim((string) $footer_customize_code);
|
||||||
if ($_footer_conter === '') {
|
if ($_footer_conter === '') {
|
||||||
$footer_bits = [];
|
$footer_bits = [];
|
||||||
if ($icp_number !== '') {
|
if ($icp_filing !== '') {
|
||||||
$footer_bits[] = '<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer nofollow">' . esc_html($icp_number) . '</a>';
|
$footer_bits[] = '<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer nofollow">' . esc_html($icp_filing) . '</a>';
|
||||||
}
|
}
|
||||||
if ($police_number !== '') {
|
if ($police_filing !== '') {
|
||||||
$footer_bits[] = '<a href="https://beian.mps.gov.cn/" target="_blank" rel="noopener noreferrer nofollow">' . esc_html($police_number) . '</a>';
|
$footer_bits[] = '<a href="https://beian.mps.gov.cn/" target="_blank" rel="noopener noreferrer nofollow">' . esc_html($police_filing) . '</a>';
|
||||||
}
|
}
|
||||||
$copy = '© ' . esc_html(gmdate('Y')) . ' ' . esc_html($blog_name) . '.';
|
$copy = '© ' . esc_html(gmdate('Y')) . ' ' . esc_html($blog_name) . '.';
|
||||||
if ($copyright_text !== '') {
|
if ($copyright_text !== '') {
|
||||||
|
|||||||
@@ -25,9 +25,6 @@ add_filter('comments_open', function ($open, $post_id) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!get_option('aksrt_comment_enabled', true)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return $open;
|
return $open;
|
||||||
}, 10, 2);
|
}, 10, 2);
|
||||||
|
|
||||||
|
|||||||
@@ -454,6 +454,19 @@ if (!function_exists('aksrt_comments_render_form')) :
|
|||||||
? sanitize_text_field(wp_unslash((string) $_COOKIE['comment_author_email_' . COOKIEHASH]))
|
? sanitize_text_field(wp_unslash((string) $_COOKIE['comment_author_email_' . COOKIEHASH]))
|
||||||
: '';
|
: '';
|
||||||
$placeholder = (string) $ctx['box_placeholder'];
|
$placeholder = (string) $ctx['box_placeholder'];
|
||||||
|
$post_id = (int) get_the_ID();
|
||||||
|
$can_comment = function_exists('aksrt_user_can')
|
||||||
|
? aksrt_user_can('comment_add', get_current_user_id(), $post_id)
|
||||||
|
: $is_logged_in;
|
||||||
|
|
||||||
|
if (!$can_comment) {
|
||||||
|
if (!$is_logged_in) {
|
||||||
|
aksrt_comments_render_signarea($ctx);
|
||||||
|
} else {
|
||||||
|
echo '<p class="comments-permission-denied muted-2-color">' . esc_html__('当前账号没有发表评论的权限。', 'aksrt') . '</p>';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (function_exists('aksrt_get_comment_virtual_input_html')) {
|
if (function_exists('aksrt_get_comment_virtual_input_html')) {
|
||||||
echo aksrt_get_comment_virtual_input_html($placeholder, '#respond'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
echo aksrt_get_comment_virtual_input_html($placeholder, '#respond'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||||
|
|||||||
+8
-25
@@ -39,6 +39,7 @@ function aksrt_captcha_scenes(): array {
|
|||||||
'verify_email' => __('邮箱验证', 'aksrt'),
|
'verify_email' => __('邮箱验证', 'aksrt'),
|
||||||
'download' => __('资源下载', 'aksrt'),
|
'download' => __('资源下载', 'aksrt'),
|
||||||
'signin' => __('每日签到', 'aksrt'),
|
'signin' => __('每日签到', 'aksrt'),
|
||||||
|
'front_submit' => __('前台投稿', 'aksrt'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,14 +47,12 @@ function aksrt_captcha_scenes(): array {
|
|||||||
* 默认配置结构。
|
* 默认配置结构。
|
||||||
*/
|
*/
|
||||||
function aksrt_captcha_default_config(): array {
|
function aksrt_captcha_default_config(): array {
|
||||||
$all_scenes = array_keys(aksrt_captcha_scenes());
|
$default_scenes = array_values(array_diff(array_keys(aksrt_captcha_scenes()), ['front_submit']));
|
||||||
return [
|
return [
|
||||||
'enabled' => false,
|
'enabled' => false,
|
||||||
'active_provider' => '',
|
'active_provider' => '',
|
||||||
// Positive list: checked scenes require captcha. Default all scenes.
|
// Positive list: checked scenes require captcha. Front submissions stay opt-in.
|
||||||
'enabled_scenarios' => $all_scenes,
|
'enabled_scenarios' => $default_scenes,
|
||||||
// Legacy mirror kept for BC consumers / admin summaries.
|
|
||||||
'disabled_scenarios' => [],
|
|
||||||
'tencent' => [],
|
'tencent' => [],
|
||||||
'aliyun' => [],
|
'aliyun' => [],
|
||||||
'cloudflare' => [],
|
'cloudflare' => [],
|
||||||
@@ -85,32 +84,20 @@ function aksrt_captcha_sanitize_scene_list($raw): array {
|
|||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
function aksrt_get_captcha_config(): array {
|
function aksrt_get_captcha_config(): array {
|
||||||
$cfg = get_option('aksrt_captcha_config', []);
|
$stored = get_option('aksrt_captcha_config', []);
|
||||||
if (!is_array($cfg)) {
|
$cfg = array_merge(aksrt_captcha_default_config(), is_array($stored) ? $stored : []);
|
||||||
$cfg = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$all_scenes = array_keys(aksrt_captcha_scenes());
|
|
||||||
$provider = isset($cfg['active_provider']) ? sanitize_key((string) $cfg['active_provider']) : '';
|
$provider = isset($cfg['active_provider']) ? sanitize_key((string) $cfg['active_provider']) : '';
|
||||||
if (!in_array($provider, aksrt_captcha_providers(), true)) {
|
if (!in_array($provider, aksrt_captcha_providers(), true)) {
|
||||||
$provider = '';
|
$provider = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (array_key_exists('enabled_scenarios', $cfg)) {
|
$enabled = aksrt_captcha_sanitize_scene_list($cfg['enabled_scenarios'] ?? []);
|
||||||
// Explicit positive list (may be empty = no scenes).
|
|
||||||
$enabled = aksrt_captcha_sanitize_scene_list($cfg['enabled_scenarios']);
|
|
||||||
} else {
|
|
||||||
// Legacy: only disabled list stored → all minus disabled.
|
|
||||||
$disabled_legacy = aksrt_captcha_sanitize_scene_list($cfg['disabled_scenarios'] ?? []);
|
|
||||||
$enabled = array_values(array_diff($all_scenes, $disabled_legacy));
|
|
||||||
}
|
|
||||||
$disabled = array_values(array_diff($all_scenes, $enabled));
|
|
||||||
|
|
||||||
$result = [
|
$result = [
|
||||||
'enabled' => !empty($cfg['enabled']),
|
'enabled' => !empty($cfg['enabled']),
|
||||||
'active_provider' => $provider,
|
'active_provider' => $provider,
|
||||||
'enabled_scenarios' => $enabled,
|
'enabled_scenarios' => $enabled,
|
||||||
'disabled_scenarios' => $disabled,
|
|
||||||
];
|
];
|
||||||
foreach (aksrt_captcha_providers() as $p) {
|
foreach (aksrt_captcha_providers() as $p) {
|
||||||
$result[$p] = isset($cfg[$p]) && is_array($cfg[$p]) ? $cfg[$p] : [];
|
$result[$p] = isset($cfg[$p]) && is_array($cfg[$p]) ? $cfg[$p] : [];
|
||||||
@@ -174,11 +161,7 @@ function aksrt_is_captcha_enabled_for($scene): bool {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$enabled = isset($cfg['enabled_scenarios']) && is_array($cfg['enabled_scenarios'])
|
return in_array($scene, $cfg['enabled_scenarios'], true);
|
||||||
? $cfg['enabled_scenarios']
|
|
||||||
: array_values(array_diff(array_keys(aksrt_captcha_scenes()), (array) ($cfg['disabled_scenarios'] ?? [])));
|
|
||||||
|
|
||||||
return in_array($scene, $enabled, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -36,27 +36,6 @@ class ExtensionController extends ControllerBase {
|
|||||||
'permission_callback' => [$this, 'check_permission'],
|
'permission_callback' => [$this, 'check_permission'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Security settings summary
|
|
||||||
register_rest_route(self::API_NAMESPACE, '/settings/security', [
|
|
||||||
'methods' => 'GET',
|
|
||||||
'callback' => [$this, 'get_security_settings'],
|
|
||||||
'permission_callback' => [$this, 'check_permission'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Security settings — save risk switches
|
|
||||||
register_rest_route(self::API_NAMESPACE, '/settings/security', [
|
|
||||||
'methods' => 'POST',
|
|
||||||
'callback' => [$this, 'save_security_settings'],
|
|
||||||
'permission_callback' => [$this, 'check_permission'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Security audit log
|
|
||||||
register_rest_route(self::API_NAMESPACE, '/security/audit-log', [
|
|
||||||
'methods' => 'GET',
|
|
||||||
'callback' => [$this, 'get_audit_log'],
|
|
||||||
'permission_callback' => [$this, 'check_permission'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
// IP geo test tool
|
// IP geo test tool
|
||||||
register_rest_route(self::API_NAMESPACE, '/extensions/ip-test', [
|
register_rest_route(self::API_NAMESPACE, '/extensions/ip-test', [
|
||||||
'methods' => 'POST',
|
'methods' => 'POST',
|
||||||
@@ -151,139 +130,6 @@ class ExtensionController extends ControllerBase {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* GET /settings/security — security settings summary with real service status.
|
|
||||||
*/
|
|
||||||
public function get_security_settings(): \WP_REST_Response {
|
|
||||||
$services = $this->collect_service_statuses();
|
|
||||||
$settings = \AKSRT\Settings\Settings::get_all();
|
|
||||||
|
|
||||||
$email_gate_enabled = true;
|
|
||||||
if (defined('AKSRT_NO_EMAIL_GATE') && AKSRT_NO_EMAIL_GATE) {
|
|
||||||
$email_gate_enabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$captcha = function_exists('aksrt_get_captcha_status_summary')
|
|
||||||
? aksrt_get_captcha_status_summary()
|
|
||||||
: ['enabled' => false, 'provider' => '', 'configured' => false, 'scenes' => []];
|
|
||||||
$features = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_cms_features() : [];
|
|
||||||
$scenes = is_array($captcha['scenes'] ?? null) ? $captcha['scenes'] : [];
|
|
||||||
|
|
||||||
$risk_switches = [
|
|
||||||
'show_unapproved_comments' => !empty($settings['show_unapproved_comments']),
|
|
||||||
'skip_submit_moderation' => !empty($settings['skip_submit_moderation']),
|
|
||||||
'guest_comment' => !empty($settings['guest_comment']),
|
|
||||||
];
|
|
||||||
|
|
||||||
return $this->success([
|
|
||||||
'data' => [
|
|
||||||
'email_gate_enabled' => $email_gate_enabled,
|
|
||||||
'captcha' => [
|
|
||||||
'enabled' => !empty($captcha['enabled']),
|
|
||||||
'provider' => (string) ($captcha['provider'] ?? ''),
|
|
||||||
'configured' => !empty($captcha['configured']),
|
|
||||||
'scenes' => $scenes,
|
|
||||||
'download_required' => !empty($scenes['download']),
|
|
||||||
'download_active' => !empty($scenes['download']),
|
|
||||||
],
|
|
||||||
'content_audit' => [
|
|
||||||
'enabled' => !empty($services['audit']['enabled']),
|
|
||||||
'configured' => !empty($services['audit']['configured']),
|
|
||||||
],
|
|
||||||
'ip_geo' => [
|
|
||||||
'enabled' => !empty($services['ip_geo']['enabled']),
|
|
||||||
'configured' => !empty($services['ip_geo']['configured']),
|
|
||||||
],
|
|
||||||
'risk_switches' => $risk_switches,
|
|
||||||
'services' => $services,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POST /settings/security — save risk switches.
|
|
||||||
*/
|
|
||||||
public function save_security_settings(\WP_REST_Request $request): \WP_REST_Response {
|
|
||||||
$params = $this->get_json_params($request);
|
|
||||||
$risk_switches = $params['risk_switches'] ?? [];
|
|
||||||
|
|
||||||
if (!is_array($risk_switches)) {
|
|
||||||
return $this->error('无效的风险设置数据');
|
|
||||||
}
|
|
||||||
|
|
||||||
$allowed = [
|
|
||||||
'show_unapproved_comments' => 'bool',
|
|
||||||
'skip_submit_moderation' => 'bool',
|
|
||||||
'guest_comment' => 'bool',
|
|
||||||
];
|
|
||||||
|
|
||||||
$sanitized = $this->sanitize_fields($risk_switches, $allowed);
|
|
||||||
|
|
||||||
// Merge into main settings
|
|
||||||
$all = \AKSRT\Settings\Settings::get_all();
|
|
||||||
foreach ($sanitized as $key => $val) {
|
|
||||||
$all[$key] = $val;
|
|
||||||
}
|
|
||||||
|
|
||||||
update_option('aksrt_settings', $all);
|
|
||||||
|
|
||||||
return $this->success([
|
|
||||||
'message' => __('风险设置已保存', 'aksrt'),
|
|
||||||
'data' => $sanitized,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET /security/audit-log — recent security audit entries.
|
|
||||||
*/
|
|
||||||
public function get_audit_log(): \WP_REST_Response {
|
|
||||||
global $wpdb;
|
|
||||||
|
|
||||||
$limit = 20;
|
|
||||||
$logs = [];
|
|
||||||
|
|
||||||
// Recent user registrations
|
|
||||||
$recent_users = get_users([
|
|
||||||
'number' => $limit,
|
|
||||||
'orderby' => 'registered',
|
|
||||||
'order' => 'DESC',
|
|
||||||
'fields' => ['ID', 'display_name', 'user_email', 'user_registered'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
foreach ($recent_users as $user) {
|
|
||||||
$logs[] = [
|
|
||||||
'id' => 'reg_' . $user->ID,
|
|
||||||
'action' => '用户注册',
|
|
||||||
'detail' => sprintf('%s (%s) 完成注册', $user->display_name, $user->user_email),
|
|
||||||
'time' => $user->user_registered,
|
|
||||||
'type' => 'auth',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Content audit log
|
|
||||||
$audit_log = get_option('aksrt_content_audit_log', []);
|
|
||||||
foreach (array_slice(array_reverse($audit_log), 0, 10) as $i => $entry) {
|
|
||||||
$logs[] = [
|
|
||||||
'id' => 'audit_' . $i,
|
|
||||||
'action' => '内容审核',
|
|
||||||
'detail' => sprintf('%s — %s', $entry['content'] ?? '', ($entry['passed'] ?? false) ? '通过' : '拦截'),
|
|
||||||
'time' => $entry['time'] ?? '',
|
|
||||||
'type' => 'audit',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort by time descending
|
|
||||||
usort($logs, function ($a, $b) {
|
|
||||||
return strcmp($b['time'] ?? '', $a['time'] ?? '');
|
|
||||||
});
|
|
||||||
|
|
||||||
return $this->success([
|
|
||||||
'data' => [
|
|
||||||
'logs' => array_slice($logs, 0, $limit),
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /extensions/ip-test — test current IP provider with a given IP.
|
* POST /extensions/ip-test — test current IP provider with a given IP.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -20,14 +20,12 @@ class GlobalSchema {
|
|||||||
'version' => '1.0.0',
|
'version' => '1.0.0',
|
||||||
'option_key' => 'aksrt_settings',
|
'option_key' => 'aksrt_settings',
|
||||||
'groups' => [
|
'groups' => [
|
||||||
['key' => 'comment', 'title' => '评论功能', 'fields' => [
|
|
||||||
['key' => 'comment_enabled', 'label' => '开启评论', 'type' => 'switch', 'default' => true],
|
|
||||||
]],
|
|
||||||
['key' => 'performance', 'title' => '性能优化', 'fields' => [
|
['key' => 'performance', 'title' => '性能优化', 'fields' => [
|
||||||
['key' => 'lazyload_enabled', 'label' => '图片懒加载', 'type' => 'switch', 'default' => true],
|
['key' => 'lazyload_enabled', 'label' => '图片懒加载', 'type' => 'switch', 'default' => true],
|
||||||
]],
|
]],
|
||||||
['key' => 'filing', 'title' => '备案信息', 'fields' => [
|
['key' => 'filing', 'title' => '备案信息', 'fields' => [
|
||||||
['key' => 'icp_filing', 'label' => 'ICP 备案号', 'type' => 'input', 'default' => ''],
|
['key' => 'icp_filing', 'label' => 'ICP 备案号', 'type' => 'input', 'default' => ''],
|
||||||
|
['key' => 'police_filing', 'label' => '公安备案号', 'type' => 'input', 'default' => ''],
|
||||||
['key' => 'footer_text', 'label' => '页脚附加文字', 'type' => 'input', 'default' => ''],
|
['key' => 'footer_text', 'label' => '页脚附加文字', 'type' => 'input', 'default' => ''],
|
||||||
]],
|
]],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ class SchemaExtensions {
|
|||||||
'ip' => SchemaExtensionsIP::build(),
|
'ip' => SchemaExtensionsIP::build(),
|
||||||
'audit' => SchemaExtensionsAudit::build(),
|
'audit' => SchemaExtensionsAudit::build(),
|
||||||
'push' => SchemaExtensionsPush::build(),
|
'push' => SchemaExtensionsPush::build(),
|
||||||
'security' => SchemaExtensionsSecurity::build(),
|
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* @package AKSRT
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace AKSRT\Admin\Schema;
|
|
||||||
|
|
||||||
defined('ABSPATH') || exit;
|
|
||||||
|
|
||||||
class SchemaExtensionsSecurity {
|
|
||||||
|
|
||||||
public static function build(): array {
|
|
||||||
return [
|
|
||||||
'key' => 'security',
|
|
||||||
'title' => '网站安全',
|
|
||||||
'version' => '1.0.0',
|
|
||||||
'option_key' => 'aksrt_settings',
|
|
||||||
'groups' => [
|
|
||||||
['key' => 'basic', 'title' => '基础安全', 'fields' => [
|
|
||||||
['key' => 'hide_admin_bar', 'label' => '前台关闭顶部 admin_bar', 'type' => 'switch', 'default' => true],
|
|
||||||
['key' => 'remove_emoji', 'label' => '移除Emoji脚本', 'type' => 'switch', 'default' => true],
|
|
||||||
['key' => 'remove_open_sans', 'label' => '移除Open Sans字体', 'type' => 'switch', 'default' => true],
|
|
||||||
['key' => 'disabled_pingback', 'label' => '禁用Pingback', 'type' => 'switch', 'default' => true],
|
|
||||||
['key' => 'disabled_xmlrpc', 'label' => '禁用XML-RPC', 'type' => 'switch', 'default' => true],
|
|
||||||
['key' => 'close_attachment_page', 'label' => '关闭附件页面', 'type' => 'switch', 'default' => true],
|
|
||||||
['key' => 'user_disable_admin', 'label' => '非管理员禁止进入后台', 'type' => 'switch', 'default' => true],
|
|
||||||
]],
|
|
||||||
['key' => 'upload', 'title' => '上传设置', 'fields' => [
|
|
||||||
['key' => 'split_upload_s', 'label' => '分片上传', 'type' => 'switch', 'default' => true],
|
|
||||||
['key' => 'split_minimum_size', 'label' => '最小分片大小(MB)', 'type' => 'number', 'default' => 20, 'min' => 4, 'max' => 100, 'step' => 2],
|
|
||||||
['key' => 'newfilename', 'label' => '上传文件自动重命名', 'type' => 'switch', 'default' => false],
|
|
||||||
['key' => 'newfilename_type', 'label' => '重命名格式', 'type' => 'radio', 'default' => 'time', 'options' => [
|
|
||||||
['label' => '随机字符串', 'value' => 'random'],
|
|
||||||
['label' => '时间+原文件名', 'value' => 'time'],
|
|
||||||
]],
|
|
||||||
['key' => 'disabled_large_thumbnail', 'label' => '禁用大缩略图', 'type' => 'switch', 'default' => true],
|
|
||||||
]],
|
|
||||||
['key' => 'admin', 'title' => '后台设置', 'fields' => [
|
|
||||||
['key' => 'remove_dashboard_widgets', 'label' => 'WP 后台加速:移除站点健康/活动/禁止浏览器&PHP版本检测', 'type' => 'switch', 'default' => true],
|
|
||||||
['key' => 'display_wp_update', 'label' => '禁止 WP 检测更新', 'type' => 'switch', 'default' => true],
|
|
||||||
['key' => 'close_gutenberg', 'label' => '关闭古腾堡编辑器', 'type' => 'switch', 'default' => false, 'help' => '禁用后编辑器增强全部失效'],
|
|
||||||
['key' => 'disabled_autoembed', 'label' => '禁用自动嵌入', 'type' => 'switch', 'default' => true],
|
|
||||||
['key' => 'mce_float_toolbar', 'label' => '浮动工具栏', 'type' => 'checkbox', 'default' => ['pc_s', 'm_s'], 'sanitize_type' => 'array', 'options' => [
|
|
||||||
['label' => 'PC', 'value' => 'pc_s'],
|
|
||||||
['label' => '移动端', 'value' => 'm_s'],
|
|
||||||
]],
|
|
||||||
['key' => 'admin_user_del_fields', 'label' => '关闭后台用户编辑多余选项', 'type' => 'switch', 'default' => true],
|
|
||||||
['key' => 'admin_frontend_set', 'label' => '前台设置入口', 'type' => 'switch', 'default' => true],
|
|
||||||
]],
|
|
||||||
['key' => 'format', 'title' => '格式设置', 'fields' => [
|
|
||||||
['key' => 'time_ago_s', 'label' => '时间格式', 'type' => 'switch', 'default' => true],
|
|
||||||
['key' => 'time_format', 'label' => '时间显示格式', 'type' => 'input', 'default' => 'n月j日 H:i'],
|
|
||||||
['key' => 'remove_more_wp_head', 'label' => '移除WP head多余代码', 'type' => 'switch', 'default' => true],
|
|
||||||
]],
|
|
||||||
['key' => 'captcha-note', 'title' => '人机验证', 'fields' => [
|
|
||||||
['key' => 'captcha_redirect_hint', 'label' => '配置入口', 'type' => 'notice', 'alertType' => 'info', 'alertContent' => '人机验证(腾讯云 / 阿里云 / Cloudflare / 极验)请到「功能&权限 → 人机验证」统一配置;本页不再维护验证码字段。'],
|
|
||||||
]],
|
|
||||||
['key' => 'rate-limit', 'title' => '频率限制', 'fields' => [
|
|
||||||
['key' => 'brush_limit_post_enabled', 'label' => '文章发布限制', 'type' => 'switch', 'default' => false],
|
|
||||||
['key' => 'brush_limit_post_minutes_10', 'label' => '文章 10 分钟限制', 'type' => 'number', 'default' => 20, 'min' => 1, 'max' => 200],
|
|
||||||
['key' => 'brush_limit_post_day_1', 'label' => '文章每日限制', 'type' => 'number', 'default' => 50, 'min' => 1, 'max' => 2000],
|
|
||||||
['key' => 'brush_limit_comment_enabled', 'label' => '评论频率限制', 'type' => 'switch', 'default' => false],
|
|
||||||
['key' => 'brush_limit_comment_minutes_10', 'label' => '评论 10 分钟限制', 'type' => 'number', 'default' => 20, 'min' => 1, 'max' => 200],
|
|
||||||
['key' => 'brush_limit_comment_day_1', 'label' => '评论每日限制', 'type' => 'number', 'default' => 100, 'min' => 1, 'max' => 2000],
|
|
||||||
['key' => 'brush_limit_upload_enabled', 'label' => '上传频率限制', 'type' => 'switch', 'default' => false],
|
|
||||||
['key' => 'brush_limit_upload_minutes_10', 'label' => '上传 10 分钟限制', 'type' => 'number', 'default' => 50, 'min' => 1, 'max' => 200],
|
|
||||||
['key' => 'brush_limit_upload_day_1', 'label' => '上传每日限制', 'type' => 'number', 'default' => 200, 'min' => 1, 'max' => 2000],
|
|
||||||
['key' => 'brush_limit_register_enabled', 'label' => '注册频率限制', 'type' => 'switch', 'default' => false],
|
|
||||||
['key' => 'brush_limit_register_minutes_10', 'label' => '注册 10 分钟限制', 'type' => 'number', 'default' => 20, 'min' => 1, 'max' => 200],
|
|
||||||
['key' => 'brush_limit_register_day_1', 'label' => '注册每日限制', 'type' => 'number', 'default' => 100, 'min' => 1, 'max' => 2000],
|
|
||||||
['key' => 'brush_limit_order_enabled', 'label' => '订单频率限制', 'type' => 'switch', 'default' => false],
|
|
||||||
['key' => 'brush_limit_order_minutes_10', 'label' => '订单 10 分钟限制', 'type' => 'number', 'default' => 30, 'min' => 1, 'max' => 200],
|
|
||||||
['key' => 'brush_limit_order_day_1', 'label' => '订单每日限制', 'type' => 'number', 'default' => 100, 'min' => 1, 'max' => 2000],
|
|
||||||
['key' => 'error_frequency_limit', 'label' => '错误频率限制', 'type' => 'switch', 'default' => true],
|
|
||||||
]],
|
|
||||||
['key' => 'content-audit', 'title' => '内容审核', 'fields' => [
|
|
||||||
['key' => 'audit_comment', 'label' => '审核评论', 'type' => 'switch', 'default' => false],
|
|
||||||
['key' => 'audit_new_post', 'label' => '审核新文章', 'type' => 'switch', 'default' => false],
|
|
||||||
['key' => 'audit_upload_img', 'label' => '审核上传图片', 'type' => 'switch', 'default' => false],
|
|
||||||
['key' => 'audit_user_desc', 'label' => '审核用户简介', 'type' => 'switch', 'default' => false],
|
|
||||||
['key' => 'audit_msg_private', 'label' => '审核私信', 'type' => 'switch', 'default' => false],
|
|
||||||
['key' => 'audit_be_like_text', 'label' => '文本允许疑似合规', 'type' => 'switch', 'default' => false],
|
|
||||||
['key' => 'audit_be_like_img', 'label' => '图片允许疑似合规', 'type' => 'switch', 'default' => false],
|
|
||||||
]],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -452,9 +452,7 @@ class SchemaPageDisplay {
|
|||||||
['key' => 'footer_columns', 'label' => '页脚列数', 'type' => 'number', 'default' => 3, 'min' => 1, 'max' => 4],
|
['key' => 'footer_columns', 'label' => '页脚列数', 'type' => 'number', 'default' => 3, 'min' => 1, 'max' => 4],
|
||||||
['key' => 'footer_copyright', 'label' => '显示版权信息', 'type' => 'switch', 'default' => true],
|
['key' => 'footer_copyright', 'label' => '显示版权信息', 'type' => 'switch', 'default' => true],
|
||||||
['key' => 'footer_icp', 'label' => '显示ICP备案', 'type' => 'switch', 'default' => true],
|
['key' => 'footer_icp', 'label' => '显示ICP备案', 'type' => 'switch', 'default' => true],
|
||||||
['key' => 'icp_number', 'label' => 'ICP备案号', 'type' => 'input', 'default' => '', 'help' => '例如:ICP 备 xxxxxxxx 号'],
|
|
||||||
['key' => 'footer_police', 'label' => '显示公安备案', 'type' => 'switch', 'default' => true],
|
['key' => 'footer_police', 'label' => '显示公安备案', 'type' => 'switch', 'default' => true],
|
||||||
['key' => 'police_number', 'label' => '公安备案号', 'type' => 'input', 'default' => '', 'help' => '例如:公网安备 xxxxxx 号'],
|
|
||||||
]],
|
]],
|
||||||
['key' => 'color', 'title' => '页脚颜色', 'fields' => [
|
['key' => 'color', 'title' => '页脚颜色', 'fields' => [
|
||||||
['key' => 'footer_theme_custom', 'label' => '自定义页脚颜色', 'type' => 'switch', 'default' => false],
|
['key' => 'footer_theme_custom', 'label' => '自定义页脚颜色', 'type' => 'switch', 'default' => false],
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ class SchemaPermissions {
|
|||||||
['label' => '邮箱验证', 'value' => 'verify_email'],
|
['label' => '邮箱验证', 'value' => 'verify_email'],
|
||||||
['label' => '资源下载', 'value' => 'download'],
|
['label' => '资源下载', 'value' => 'download'],
|
||||||
['label' => '每日签到', 'value' => 'signin'],
|
['label' => '每日签到', 'value' => 'signin'],
|
||||||
|
['label' => '前台投稿', 'value' => 'front_submit'],
|
||||||
]],
|
]],
|
||||||
]],
|
]],
|
||||||
['key' => 'providers', 'title' => '服务商配置', 'fields' => [
|
['key' => 'providers', 'title' => '服务商配置', 'fields' => [
|
||||||
|
|||||||
@@ -354,7 +354,6 @@ class SchemaPostList {
|
|||||||
['key' => 'post_article_cat', 'label' => '允许投稿分类', 'type' => 'taxonomy_select', 'taxonomy' => 'category', 'multiple' => true, 'default' => [], 'help' => '为空则允许全部分类'],
|
['key' => 'post_article_cat', 'label' => '允许投稿分类', 'type' => 'taxonomy_select', 'taxonomy' => 'category', 'multiple' => true, 'default' => [], 'help' => '为空则允许全部分类'],
|
||||||
['key' => 'front_submit_guest_enabled', 'label' => '允许免登录投稿', 'type' => 'switch', 'default' => false, 'help' => '开启后访客可投稿;文章归属到下方用户'],
|
['key' => 'front_submit_guest_enabled', 'label' => '允许免登录投稿', 'type' => 'switch', 'default' => false, 'help' => '开启后访客可投稿;文章归属到下方用户'],
|
||||||
['key' => 'post_article_user', 'label' => '免登录归属用户', 'type' => 'user_select', 'default' => 0, 'help' => '免登录投稿时文章归属的用户,搜索选择'],
|
['key' => 'post_article_user', 'label' => '免登录归属用户', 'type' => 'user_select', 'default' => 0, 'help' => '免登录投稿时文章归属的用户,搜索选择'],
|
||||||
['key' => 'front_submit_captcha', 'label' => '提交人机验证', 'type' => 'switch', 'default' => false, 'help' => '提交正式投稿时校验人机验证'],
|
|
||||||
['key' => 'front_submit_tags', 'label' => '标签输入', 'type' => 'switch', 'default' => true],
|
['key' => 'front_submit_tags', 'label' => '标签输入', 'type' => 'switch', 'default' => true],
|
||||||
['key' => 'post_article_topics_s', 'label' => '专题选择', 'type' => 'switch', 'default' => false, 'help' => '投稿时可选择专题分类'],
|
['key' => 'post_article_topics_s', 'label' => '专题选择', 'type' => 'switch', 'default' => false, 'help' => '投稿时可选择专题分类'],
|
||||||
['key' => 'post_article_custom_filter_s', 'label' => '高级筛选选择', 'type' => 'switch', 'default' => true, 'help' => '投稿时可选择自定义筛选维度值'],
|
['key' => 'post_article_custom_filter_s', 'label' => '高级筛选选择', 'type' => 'switch', 'default' => true, 'help' => '投稿时可选择自定义筛选维度值'],
|
||||||
|
|||||||
@@ -306,10 +306,10 @@ class SchemaProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Captcha: always surface normalized enabled_scenarios (legacy disabled list inverted).
|
// Captcha: always surface the normalized positive scene list.
|
||||||
if ($option_key === 'aksrt_captcha_config' && function_exists('aksrt_get_captcha_config')) {
|
if ($option_key === 'aksrt_captcha_config' && function_exists('aksrt_get_captcha_config')) {
|
||||||
$normalized = aksrt_get_captcha_config();
|
$normalized = aksrt_get_captcha_config();
|
||||||
foreach (['enabled', 'active_provider', 'enabled_scenarios', 'disabled_scenarios', 'tencent', 'aliyun', 'cloudflare', 'geetest', 'mode'] as $key) {
|
foreach (['enabled', 'active_provider', 'enabled_scenarios', 'tencent', 'aliyun', 'cloudflare', 'geetest', 'mode'] as $key) {
|
||||||
if (array_key_exists($key, $normalized)) {
|
if (array_key_exists($key, $normalized)) {
|
||||||
$data[$key] = $normalized[$key];
|
$data[$key] = $normalized[$key];
|
||||||
}
|
}
|
||||||
@@ -340,23 +340,12 @@ class SchemaProvider {
|
|||||||
unset($stored['pay_rebate_withdraw_text_details'], $stored['pay_balance_withdraw_lowest_money']);
|
unset($stored['pay_rebate_withdraw_text_details'], $stored['pay_balance_withdraw_lowest_money']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Captcha: keep enabled_scenarios / disabled_scenarios mirrors in sync.
|
// Captcha stores a single positive scene list.
|
||||||
if ($option_key === 'aksrt_captcha_config' && function_exists('aksrt_captcha_scenes')) {
|
if ($option_key === 'aksrt_captcha_config' && function_exists('aksrt_captcha_scenes')) {
|
||||||
$all = array_keys(aksrt_captcha_scenes());
|
$stored['enabled_scenarios'] = function_exists('aksrt_captcha_sanitize_scene_list')
|
||||||
if (array_key_exists('enabled_scenarios', $stored)) {
|
? aksrt_captcha_sanitize_scene_list($stored['enabled_scenarios'] ?? [])
|
||||||
$enabled = function_exists('aksrt_captcha_sanitize_scene_list')
|
: (array) ($stored['enabled_scenarios'] ?? []);
|
||||||
? aksrt_captcha_sanitize_scene_list($stored['enabled_scenarios'])
|
}
|
||||||
: (array) $stored['enabled_scenarios'];
|
|
||||||
$stored['enabled_scenarios'] = $enabled;
|
|
||||||
$stored['disabled_scenarios'] = array_values(array_diff($all, $enabled));
|
|
||||||
} elseif (array_key_exists('disabled_scenarios', $stored)) {
|
|
||||||
$disabled = function_exists('aksrt_captcha_sanitize_scene_list')
|
|
||||||
? aksrt_captcha_sanitize_scene_list($stored['disabled_scenarios'])
|
|
||||||
: (array) $stored['disabled_scenarios'];
|
|
||||||
$stored['disabled_scenarios'] = $disabled;
|
|
||||||
$stored['enabled_scenarios'] = array_values(array_diff($all, $disabled));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$updated = update_option($option_key, $stored);
|
$updated = update_option($option_key, $stored);
|
||||||
\AKSRT\Settings\Settings::flush_settings_cache();
|
\AKSRT\Settings\Settings::flush_settings_cache();
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ class FrontSubmit {
|
|||||||
|
|
||||||
public function ajax_submit_post(): void {
|
public function ajax_submit_post(): void {
|
||||||
$this->verify_nonce();
|
$this->verify_nonce();
|
||||||
$this->maybe_verify_captcha();
|
$this->verify_captcha();
|
||||||
|
|
||||||
$data = wp_unslash($_POST);
|
$data = wp_unslash($_POST);
|
||||||
$result = $this->persist(is_array($data) ? $data : [], 'posts_save');
|
$result = $this->persist(is_array($data) ? $data : [], 'posts_save');
|
||||||
@@ -385,21 +385,15 @@ class FrontSubmit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function maybe_verify_captcha(): void {
|
private function verify_captcha(): void {
|
||||||
$settings = $this->settings();
|
|
||||||
if (empty($settings['captcha'])) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!function_exists('aksrt_require_captcha')) {
|
if (!function_exists('aksrt_require_captcha')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$payload = [];
|
$verify = aksrt_require_captcha('front_submit', $_POST);
|
||||||
if (isset($_POST['captcha']) && is_array($_POST['captcha'])) {
|
if ($verify !== true) {
|
||||||
$payload = wp_unslash($_POST['captcha']);
|
wp_send_json_error([
|
||||||
}
|
'message' => is_string($verify) ? $verify : __('人机验证失败,请重试', 'aksrt'),
|
||||||
$verify = aksrt_require_captcha('submit', is_array($payload) ? $payload : []);
|
], 400);
|
||||||
if (is_wp_error($verify)) {
|
|
||||||
wp_send_json_error(['message' => $verify->get_error_message()]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,18 +9,8 @@ defined('ABSPATH') || exit;
|
|||||||
|
|
||||||
trait AuthReader {
|
trait AuthReader {
|
||||||
|
|
||||||
/** 评论是否启用 */
|
|
||||||
public static function comment_enabled(): bool {
|
|
||||||
$s = self::get_all();
|
|
||||||
return !isset($s['comment_enabled']) || !empty($s['comment_enabled']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 是否要求注册用户才能评论 */
|
/** 是否要求注册用户才能评论 */
|
||||||
public static function comment_requires_registration(): bool {
|
public static function comment_requires_registration(): bool {
|
||||||
if (!self::comment_enabled()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$user_id = get_current_user_id();
|
$user_id = get_current_user_id();
|
||||||
return !class_exists(Capabilities::class) || !Capabilities::user_can('comment_add', $user_id);
|
return !class_exists(Capabilities::class) || !Capabilities::user_can('comment_add', $user_id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,48 +153,6 @@ class Capabilities {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取"网站安全"设置(人机验证见 aksrt_get_captcha_status_summary)。
|
|
||||||
*/
|
|
||||||
public static function get_security_settings(): array {
|
|
||||||
$s = Settings::get_all();
|
|
||||||
|
|
||||||
$captcha = function_exists('aksrt_get_captcha_status_summary')
|
|
||||||
? aksrt_get_captcha_status_summary()
|
|
||||||
: ['enabled' => false, 'provider' => '', 'configured' => false, 'scenes' => []];
|
|
||||||
|
|
||||||
return [
|
|
||||||
'captcha' => $captcha,
|
|
||||||
'brush_limit_post' => [
|
|
||||||
'enabled' => !empty($s['brush_limit_post_enabled']),
|
|
||||||
'minutes_10' => (int) ($s['brush_limit_post_minutes_10'] ?? 20),
|
|
||||||
'day_1' => (int) ($s['brush_limit_post_day_1'] ?? 50),
|
|
||||||
],
|
|
||||||
'brush_limit_comment' => [
|
|
||||||
'enabled' => !empty($s['brush_limit_comment_enabled']),
|
|
||||||
'minutes_10' => (int) ($s['brush_limit_comment_minutes_10'] ?? 20),
|
|
||||||
'day_1' => (int) ($s['brush_limit_comment_day_1'] ?? 100),
|
|
||||||
],
|
|
||||||
'brush_limit_upload' => [
|
|
||||||
'enabled' => !empty($s['brush_limit_upload_enabled']),
|
|
||||||
'minutes_10' => (int) ($s['brush_limit_upload_minutes_10'] ?? 50),
|
|
||||||
'day_1' => (int) ($s['brush_limit_upload_day_1'] ?? 200),
|
|
||||||
],
|
|
||||||
'brush_limit_register' => [
|
|
||||||
'enabled' => !empty($s['brush_limit_register_enabled']),
|
|
||||||
'minutes_10' => (int) ($s['brush_limit_register_minutes_10'] ?? 20),
|
|
||||||
'day_1' => (int) ($s['brush_limit_register_day_1'] ?? 100),
|
|
||||||
],
|
|
||||||
'brush_limit_order' => [
|
|
||||||
'enabled' => !empty($s['brush_limit_order_enabled']),
|
|
||||||
'minutes_10' => (int) ($s['brush_limit_order_minutes_10'] ?? 30),
|
|
||||||
'day_1' => (int) ($s['brush_limit_order_day_1'] ?? 100),
|
|
||||||
],
|
|
||||||
'error_frequency_limit' => !empty($s['error_frequency_limit'] ?? true),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取"系统工具"设置
|
* 获取"系统工具"设置
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -137,7 +137,6 @@ trait DisplayPageGroups {
|
|||||||
'header_newposts' => self::get_header_newposts_config(),
|
'header_newposts' => self::get_header_newposts_config(),
|
||||||
'header_theme' => self::get_header_theme_config(),
|
'header_theme' => self::get_header_theme_config(),
|
||||||
'friend_links' => self::get_friend_links(),
|
'friend_links' => self::get_friend_links(),
|
||||||
'comment_enabled' => self::comment_enabled(),
|
|
||||||
'cms_features' => self::get_cms_features(),
|
'cms_features' => self::get_cms_features(),
|
||||||
'page_display' => self::get_page_display_settings(),
|
'page_display' => self::get_page_display_settings(),
|
||||||
'footer_theme' => self::get_footer_theme_config(),
|
'footer_theme' => self::get_footer_theme_config(),
|
||||||
|
|||||||
@@ -158,13 +158,6 @@ trait DomainGroups {
|
|||||||
return Capabilities::user_can($cap, $user_id, $context);
|
return Capabilities::user_can($cap, $user_id, $context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取"网站安全"设置
|
|
||||||
*/
|
|
||||||
public static function get_security_settings(): array {
|
|
||||||
return Capabilities::get_security_settings();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取"系统工具"设置
|
* 获取"系统工具"设置
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -362,21 +362,20 @@ trait FacadeBulk {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取备案配置(统一 page-display footer schema 和全局设置)
|
* 获取备案配置。
|
||||||
* @return array {icp_enabled, icp_number, police_enabled, police_number}
|
* @return array {icp_enabled, icp_filing, police_enabled, police_filing}
|
||||||
*/
|
*/
|
||||||
public static function get_filing_settings(): array {
|
public static function get_filing_settings(): array {
|
||||||
$s = self::get_all();
|
$s = self::get_all();
|
||||||
$icp_enabled = !isset($s['footer_icp']) || !empty($s['footer_icp']);
|
$icp_enabled = !isset($s['footer_icp']) || !empty($s['footer_icp']);
|
||||||
$police_enabled = !isset($s['footer_police']) || !empty($s['footer_police']);
|
$police_enabled = !isset($s['footer_police']) || !empty($s['footer_police']);
|
||||||
// 优先读 footer schema 的 icp_number,回退到全局 icp_filing
|
$icp_filing = trim((string) ($s['icp_filing'] ?? ''));
|
||||||
$icp_number = trim((string) ($s['icp_number'] ?? $s['icp_filing'] ?? ''));
|
$police_filing = trim((string) ($s['police_filing'] ?? ''));
|
||||||
$police_number = trim((string) ($s['police_number'] ?? ''));
|
|
||||||
return [
|
return [
|
||||||
'icp_enabled' => $icp_enabled,
|
'icp_enabled' => $icp_enabled,
|
||||||
'icp_number' => $icp_number,
|
'icp_filing' => $icp_filing,
|
||||||
'police_enabled' => $police_enabled,
|
'police_enabled' => $police_enabled,
|
||||||
'police_number' => $police_number,
|
'police_filing' => $police_filing,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,7 +397,7 @@ trait FacadeBulk {
|
|||||||
|
|
||||||
/** 获取公安备案号 */
|
/** 获取公安备案号 */
|
||||||
public static function police_filing(): string {
|
public static function police_filing(): string {
|
||||||
return trim((string) self::get_value('police_number', ''));
|
return trim((string) self::get_value('police_filing', ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ class HomeCms {
|
|||||||
|
|
||||||
$s = Settings::get_all();
|
$s = Settings::get_all();
|
||||||
$bridge_keys = [
|
$bridge_keys = [
|
||||||
'footer_columns', 'footer_copyright', 'footer_icp', 'icp_number',
|
'footer_columns', 'footer_copyright', 'footer_icp', 'footer_police',
|
||||||
'footer_police', 'police_number',
|
|
||||||
'footer_theme_custom', 'footer_theme_bg_custom', 'footer_theme_bg', 'footer_theme_color_custom',
|
'footer_theme_custom', 'footer_theme_bg_custom', 'footer_theme_bg', 'footer_theme_color_custom',
|
||||||
'footer_contact_wechat_img', 'footer_contact_qq', 'footer_contact_weibo', 'footer_contact_email',
|
'footer_contact_wechat_img', 'footer_contact_qq', 'footer_contact_weibo', 'footer_contact_email',
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -429,7 +429,6 @@ class PostDisplay {
|
|||||||
'editor_mode' => $editor_mode,
|
'editor_mode' => $editor_mode,
|
||||||
'draft_save' => !isset($settings['front_submit_draft_save']) || !empty($settings['front_submit_draft_save']),
|
'draft_save' => !isset($settings['front_submit_draft_save']) || !empty($settings['front_submit_draft_save']),
|
||||||
'leave_warning' => !isset($settings['front_submit_leave_warning']) || !empty($settings['front_submit_leave_warning']),
|
'leave_warning' => !isset($settings['front_submit_leave_warning']) || !empty($settings['front_submit_leave_warning']),
|
||||||
'captcha' => !empty($settings['front_submit_captcha']) || !empty($settings['verification_newposts_s']),
|
|
||||||
'pay_free_s' => !isset($settings['post_article_pay_free_s']) || !empty($settings['post_article_pay_free_s']),
|
'pay_free_s' => !isset($settings['post_article_pay_free_s']) || !empty($settings['post_article_pay_free_s']),
|
||||||
'pay_price_min' => max(0, (float) ($settings['post_article_pay_price_min'] ?? 0)),
|
'pay_price_min' => max(0, (float) ($settings['post_article_pay_price_min'] ?? 0)),
|
||||||
'pay_price_max' => max(0, (float) ($settings['post_article_pay_price_max'] ?? 100)),
|
'pay_price_max' => max(0, (float) ($settings['post_article_pay_price_max'] ?? 100)),
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ if ($edit_post && in_array($status, ['publish', 'pending'], true)) {
|
|||||||
<p class="aksrt-submit-hint"><?php esc_html_e('您当前未登录,不能保存草稿,文章提交后不可再修改!', 'aksrt'); ?></p>
|
<p class="aksrt-submit-hint"><?php esc_html_e('您当前未登录,不能保存草稿,文章提交后不可再修改!', 'aksrt'); ?></p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (!empty($settings['captcha']) && function_exists('aksrt_render_captcha')) : ?>
|
<?php if (function_exists('aksrt_is_captcha_enabled_for') && function_exists('aksrt_render_captcha') && aksrt_is_captcha_enabled_for('front_submit')) : ?>
|
||||||
<div class="aksrt-submit-captcha">
|
<div class="aksrt-submit-captcha">
|
||||||
<?php aksrt_render_captcha('submit'); ?>
|
<?php aksrt_render_captcha('front_submit'); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user