Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce6fbb4e39 | ||
|
|
cf39e72126 | ||
|
|
740bd48f42 | ||
|
|
18e579a8c6 |
@@ -42,7 +42,7 @@ export const extensionsSection = {
|
||||
label: '百度推送',
|
||||
route: '/extensions/push',
|
||||
title: '百度推送',
|
||||
desc: '配置搜索引擎主动推送、批量提交和状态追踪。',
|
||||
desc: '配置百度自动推送、收录通道与触发规则;推送结果与「重新提交」在文章/分类编辑页查看。',
|
||||
canonicalDomain: 'extensions',
|
||||
section: 'push',
|
||||
}),
|
||||
|
||||
@@ -108,16 +108,6 @@ export const usersSection = {
|
||||
canonicalDomain: 'users',
|
||||
section: 'sms',
|
||||
}),
|
||||
schemaPage({
|
||||
key: 'users-wechat',
|
||||
name: 'UsersWechat',
|
||||
label: '微信通知',
|
||||
route: '/users/wechat',
|
||||
title: '微信通知',
|
||||
desc: '配置微信模板消息、订阅/解绑和站外通知策略。',
|
||||
canonicalDomain: 'users',
|
||||
section: 'wechat',
|
||||
}),
|
||||
customPage({
|
||||
key: 'users-wechat-template',
|
||||
name: 'UsersWechatTemplate',
|
||||
|
||||
@@ -2,21 +2,16 @@ export const pushSchema = {
|
||||
key: 'extensions.push',
|
||||
title: '百度推送',
|
||||
icon: 'fa fas fa-paper-plane',
|
||||
description: '配置搜索引擎主动推送、批量提交和状态追踪。',
|
||||
description: '配置百度主动推送、收录通道和推送状态。',
|
||||
defaults: {
|
||||
baidu_push_enabled: false,
|
||||
baidu_push_token: '',
|
||||
baidu_push_auto: true,
|
||||
baidu_push_on_publish: true,
|
||||
baidu_push_on_update: false,
|
||||
baidu_push_batch_size: 50,
|
||||
baidu_push_normal: true,
|
||||
baidu_push_fast: false,
|
||||
baidu_push_log_enabled: true,
|
||||
xzh_on: false,
|
||||
xzh_appid: '',
|
||||
xzh_render_tail: true,
|
||||
xzh_jsonld_single: true,
|
||||
xzh_jsonld_page: false,
|
||||
xzh_jsonld_img: false,
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
@@ -25,7 +20,7 @@ export const pushSchema = {
|
||||
icon: 'fa fas fa-rocket',
|
||||
fields: [
|
||||
{ key: 'baidu_push_enabled', label: '启用百度推送', type: 'switch', default: false, help: '开启后将自动向百度搜索推送页面链接' },
|
||||
{ key: 'baidu_push_token', label: '推送 Token', type: 'input', default: '', dependsOn: { key: 'baidu_push_enabled', value: true }, help: '在百度搜索资源平台获取的推送准入 Token' },
|
||||
{ key: 'baidu_push_token', label: '推送 Token', type: 'input', default: '', dependsOn: { key: 'baidu_push_enabled', value: true }, help: '在百度搜索资源平台获取的推送准入 Token(仅填 Token,勿填完整链接),并确保平台中的站点为本站' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -34,21 +29,17 @@ export const pushSchema = {
|
||||
icon: 'fa fas fa-bolt',
|
||||
fields: [
|
||||
{ key: 'baidu_push_auto', label: '自动推送 (页面加载时)', type: 'switch', default: true, dependsOn: { key: 'baidu_push_enabled', value: true }, help: '在前台页面中嵌入百度自动推送 JS 代码' },
|
||||
{ key: 'baidu_push_on_publish', label: '发布时主动推送', type: 'switch', default: true, dependsOn: { key: 'baidu_push_enabled', value: true } },
|
||||
{ key: 'baidu_push_on_update', label: '更新时推送', type: 'switch', default: false, dependsOn: { key: 'baidu_push_enabled', value: true } },
|
||||
{ key: 'baidu_push_on_publish', label: '发布时主动推送', type: 'switch', default: true, dependsOn: { key: 'baidu_push_enabled', value: true }, help: '文章 / 分类首次发布时推送链接;推送成功后会记录,不再重复提交' },
|
||||
{ key: 'baidu_push_on_update', label: '更新时推送', type: 'switch', default: false, dependsOn: { key: 'baidu_push_enabled', value: true }, help: '已发布内容再次保存时,若上次推送未成功则重试;需主动再次提交请在编辑页勾选「重新提交」' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'xzh',
|
||||
title: '熊掌号 / JSON-LD',
|
||||
icon: 'fa fas fa-paw',
|
||||
key: 'channel',
|
||||
title: '收录通道',
|
||||
icon: 'fa fas fa-inbox',
|
||||
fields: [
|
||||
{ key: 'xzh_on', label: '启用熊掌号', type: 'switch', default: false },
|
||||
{ key: 'xzh_appid', label: '百度 AppID', type: 'input', default: '', dependsOn: { key: 'xzh_on', value: true } },
|
||||
{ key: 'xzh_render_tail', label: '文章底部显示熊掌号尾部', type: 'switch', default: true, dependsOn: { key: 'xzh_on', value: true } },
|
||||
{ key: 'xzh_jsonld_single', label: '文章页 JSON-LD', type: 'switch', default: true, dependsOn: { key: 'xzh_on', value: true } },
|
||||
{ key: 'xzh_jsonld_page', label: '页面 JSON-LD', type: 'switch', default: false, dependsOn: { key: 'xzh_on', value: true } },
|
||||
{ key: 'xzh_jsonld_img', label: '不添加图片', type: 'switch', default: false, dependsOn: { key: 'xzh_on', value: true } },
|
||||
{ key: 'baidu_push_normal', label: '普通收录', type: 'switch', default: true, dependsOn: { key: 'baidu_push_enabled', value: true }, help: '每天可提交最多10万条有价值内容,收录速度较慢' },
|
||||
{ key: 'baidu_push_fast', label: '快速收录', type: 'switch', default: false, dependsOn: { key: 'baidu_push_enabled', value: true }, help: '百度新推出的高效收录接口,目前仅对部分优质站点开放,请确保您的站点已开放快速收录功能' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -56,8 +47,7 @@ export const pushSchema = {
|
||||
title: '高级设置',
|
||||
icon: 'fa fas fa-cog',
|
||||
fields: [
|
||||
{ key: 'baidu_push_batch_size', label: '批量推送条数', type: 'number', default: 50, min: 1, max: 500, dependsOn: { key: 'baidu_push_enabled', value: true } },
|
||||
{ key: 'baidu_push_log_enabled', label: '记录推送日志', type: 'switch', default: true, dependsOn: { key: 'baidu_push_enabled', value: true } },
|
||||
{ key: 'baidu_push_log_enabled', label: '记录推送日志', type: 'switch', default: true, dependsOn: { key: 'baidu_push_enabled', value: true }, help: 'WP_DEBUG 开启时,将推送结果写入错误日志' },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -13,10 +13,6 @@ export const seoSchema = {
|
||||
ai_seo_title_prompt: '',
|
||||
ai_seo_keywords_prompt: '',
|
||||
ai_seo_description_prompt: '',
|
||||
seo_baidu_push: false,
|
||||
seo_baidu_normal: false,
|
||||
seo_baidu_fast: false,
|
||||
seo_baidu_token: '',
|
||||
seo_image_optimize: true,
|
||||
seo_image_default: '',
|
||||
seo_image_always_default: false,
|
||||
@@ -95,17 +91,6 @@ export const seoSchema = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'baidu',
|
||||
title: '百度推送',
|
||||
icon: 'fa fas fa-paper-plane',
|
||||
fields: [
|
||||
{ key: 'seo_baidu_push', label: '启用百度自动推送', type: 'switch', default: false },
|
||||
{ key: 'seo_baidu_normal', label: '普通收录推送', type: 'switch', default: false },
|
||||
{ key: 'seo_baidu_fast', label: '快速收录推送', type: 'switch', default: false },
|
||||
{ key: 'seo_baidu_token', label: '百度推送 Token', type: 'input', default: '', placeholder: '百度资源平台 Token' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'image',
|
||||
title: '图片 SEO',
|
||||
|
||||
@@ -10,7 +10,6 @@ import { featuresSchema } from './features.js'
|
||||
import { verifySchema } from './verify.js'
|
||||
import { levelSchema } from './level.js'
|
||||
import { badgeSchema } from './badge.js'
|
||||
import { wechatSchema } from './wechat.js'
|
||||
import { reportSchema } from './report.js'
|
||||
import { smsSchema } from './sms.js'
|
||||
|
||||
@@ -23,7 +22,6 @@ export const usersSchemas = {
|
||||
verify: verifySchema,
|
||||
level: levelSchema,
|
||||
badge: badgeSchema,
|
||||
wechat: wechatSchema,
|
||||
report: reportSchema,
|
||||
sms: smsSchema,
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
export const wechatSchema = {
|
||||
key: 'users.wechat',
|
||||
title: '微信通知',
|
||||
icon: 'fa fab fa-weixin',
|
||||
description: '配置微信模板消息、订阅/解绑和站外通知策略。',
|
||||
defaults: {
|
||||
wechat_notify_enabled: false,
|
||||
wechat_app_id: '',
|
||||
wechat_app_secret: '',
|
||||
wechat_template_id: '',
|
||||
wechat_token: '',
|
||||
wechat_encoding_aes_key: '',
|
||||
wechat_notify_comment: true,
|
||||
wechat_notify_reply: true,
|
||||
wechat_notify_system: true,
|
||||
wechat_bind_entry: true,
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'account',
|
||||
title: '公众号配置',
|
||||
icon: 'fa fab fa-weixin',
|
||||
fields: [
|
||||
{ key: 'wechat_notify_enabled', label: '启用微信通知', type: 'switch', default: false, help: '开启后用户可绑定微信接收通知' },
|
||||
{ key: 'wechat_app_id', label: 'AppID', type: 'input', default: '', dependsOn: { key: 'wechat_notify_enabled', value: true } },
|
||||
{ key: 'wechat_app_secret', label: 'AppSecret', type: 'input', default: '', dependsOn: { key: 'wechat_notify_enabled', value: true } },
|
||||
{ key: 'wechat_template_id', label: '模板消息 ID', type: 'input', default: '', dependsOn: { key: 'wechat_notify_enabled', value: true }, help: '在微信公众平台申请的模板消息 ID' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'verify',
|
||||
title: '服务器验证',
|
||||
icon: 'fa fas fa-check-circle',
|
||||
fields: [
|
||||
{ key: 'wechat_token', label: 'Token', type: 'input', default: '', dependsOn: { key: 'wechat_notify_enabled', value: true }, help: '微信公众平台服务器配置中的 Token' },
|
||||
{ key: 'wechat_encoding_aes_key', label: 'EncodingAESKey', type: 'input', default: '', dependsOn: { key: 'wechat_notify_enabled', value: true } },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'triggers',
|
||||
title: '通知触发器',
|
||||
icon: 'fa fas fa-bell',
|
||||
fields: [
|
||||
{ key: 'wechat_notify_comment', label: '新评论通知', type: 'switch', default: true, dependsOn: { key: 'wechat_notify_enabled', value: true } },
|
||||
{ key: 'wechat_notify_reply', label: '评论回复通知', type: 'switch', default: true, dependsOn: { key: 'wechat_notify_enabled', value: true } },
|
||||
{ key: 'wechat_notify_system', label: '系统通知', type: 'switch', default: true, dependsOn: { key: 'wechat_notify_enabled', value: true } },
|
||||
{ key: 'wechat_bind_entry', label: '显示绑定入口', type: 'switch', default: true, dependsOn: { key: 'wechat_notify_enabled', value: true }, help: '在用户中心显示微信绑定/解绑按钮' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -295,6 +295,9 @@ if (class_exists('\AKSRT\Extensions\MeiliSearch')) {
|
||||
if (class_exists('\AKSRT\Extensions\SearchPush')) {
|
||||
\AKSRT\Extensions\SearchPush::get_instance()->init();
|
||||
}
|
||||
if (class_exists('\AKSRT\Extensions\SearchPushMetaBox')) {
|
||||
\AKSRT\Extensions\SearchPushMetaBox::get_instance()->init();
|
||||
}
|
||||
if (class_exists('\AKSRT\Core\SEOHead')) {
|
||||
\AKSRT\Core\SEOHead::get_instance()->init();
|
||||
}
|
||||
|
||||
@@ -79,12 +79,6 @@ class GlobalSchema {
|
||||
['key' => 'ai_seo_keywords_prompt', 'label' => '关键词提示词', 'type' => 'textarea', 'default' => '', 'dependsOn' => ['key' => 'seo_ai_enabled', 'value' => true], 'help' => '留空使用内置默认。'],
|
||||
['key' => 'ai_seo_description_prompt', 'label' => '描述提示词', 'type' => 'textarea', 'default' => '', 'dependsOn' => ['key' => 'seo_ai_enabled', 'value' => true], 'help' => '留空使用内置默认。'],
|
||||
]],
|
||||
['key' => 'baidu', 'title' => '百度SEO', 'fields' => [
|
||||
['key' => 'seo_baidu_push', 'label' => '百度链接自动提交', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'seo_baidu_normal', 'label' => '百度普通收录', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'seo_baidu_fast', 'label' => '百度快速收录', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'seo_baidu_token', 'label' => '百度准入密钥Token', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'image', 'title' => '图片SEO', 'fields' => [
|
||||
['key' => 'seo_image_optimize', 'label' => '搜索引擎图片展示优化', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'seo_image_default', 'label' => '默认图像', 'type' => 'media', 'default' => ''],
|
||||
|
||||
@@ -25,16 +25,11 @@ class SchemaExtensionsPush {
|
||||
['key' => 'baidu_push_on_publish', 'label' => '发布时主动推送', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'baidu_push_on_update', 'label' => '更新时推送', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'xzh', 'title' => '熊掌号 / JSON-LD', 'fields' => [
|
||||
['key' => 'xzh_on', 'label' => '启用熊掌号', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'xzh_appid', 'label' => '百度 AppID', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'xzh_render_tail', 'label' => '文章底部显示熊掌号尾部', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'xzh_jsonld_single', 'label' => '文章页 JSON-LD', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'xzh_jsonld_page', 'label' => '页面 JSON-LD', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'xzh_jsonld_img', 'label' => '不添加图片', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'channel', 'title' => '收录通道', 'fields' => [
|
||||
['key' => 'baidu_push_normal', 'label' => '普通收录', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'baidu_push_fast', 'label' => '快速收录', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'advanced', 'title' => '高级设置', 'fields' => [
|
||||
['key' => 'baidu_push_batch_size', 'label' => '批量推送条数', 'type' => 'number', 'default' => 50, 'min' => 1, 'max' => 500],
|
||||
['key' => 'baidu_push_log_enabled', 'label' => '记录推送日志', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
],
|
||||
|
||||
@@ -611,36 +611,6 @@ class SchemaUsers {
|
||||
]],
|
||||
],
|
||||
],
|
||||
'wechat' => [
|
||||
'key' => 'wechat',
|
||||
'title' => '微信通知',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'account', 'title' => '公众号配置', 'fields' => [
|
||||
['key' => 'wechat_notify_enabled', 'label' => '启用微信通知', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'wechat_app_id', 'label' => 'AppID', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'wechat_notify_enabled', 'value' => true]],
|
||||
['key' => 'wechat_app_secret', 'label' => 'AppSecret', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'wechat_notify_enabled', 'value' => true]],
|
||||
]],
|
||||
['key' => 'verify', 'title' => '服务器验证', 'fields' => [
|
||||
['key' => 'wechat_token', 'label' => 'Token', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'wechat_notify_enabled', 'value' => true]],
|
||||
['key' => 'wechat_encoding_aes_key', 'label' => 'EncodingAESKey', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'wechat_notify_enabled', 'value' => true]],
|
||||
]],
|
||||
['key' => 'templates', 'title' => '模板消息', 'fields' => [
|
||||
['key' => 'wechat_template_comment', 'label' => '评论通知模板', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'wechat_notify_enabled', 'value' => true]],
|
||||
['key' => 'wechat_template_reply', 'label' => '回复通知模板', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'wechat_notify_enabled', 'value' => true]],
|
||||
['key' => 'wechat_template_system', 'label' => '系统通知模板', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'wechat_notify_enabled', 'value' => true]],
|
||||
['key' => 'wechat_template_order', 'label' => '订单通知模板', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'wechat_notify_enabled', 'value' => true]],
|
||||
]],
|
||||
['key' => 'triggers', 'title' => '通知触发器', 'fields' => [
|
||||
['key' => 'wechat_notify_comment', 'label' => '新评论通知', 'type' => 'switch', 'default' => true, 'dependsOn' => ['key' => 'wechat_notify_enabled', 'value' => true]],
|
||||
['key' => 'wechat_notify_reply', 'label' => '评论回复通知', 'type' => 'switch', 'default' => true, 'dependsOn' => ['key' => 'wechat_notify_enabled', 'value' => true]],
|
||||
['key' => 'wechat_notify_system', 'label' => '系统通知', 'type' => 'switch', 'default' => true, 'dependsOn' => ['key' => 'wechat_notify_enabled', 'value' => true]],
|
||||
['key' => 'wechat_notify_order', 'label' => '订单通知', 'type' => 'switch', 'default' => true, 'dependsOn' => ['key' => 'wechat_notify_enabled', 'value' => true]],
|
||||
['key' => 'wechat_bind_entry', 'label' => '显示绑定入口', 'type' => 'switch', 'default' => true, 'dependsOn' => ['key' => 'wechat_notify_enabled', 'value' => true]],
|
||||
]],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -325,6 +325,54 @@ class UserController extends ControllerBase {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* GET /wechat/status — 微信模板消息配置状态。
|
||||
*
|
||||
* 状态形状与 WeChatNotify::get_status() 对齐(该方法的既定用途即 REST 与 AJAX 复用)。
|
||||
*/
|
||||
public function get_wechat_status(\WP_REST_Request $request): \WP_REST_Response {
|
||||
if (!class_exists('\AKSRT\Payment\WeChatNotify')) {
|
||||
return $this->error(__('微信通知组件未加载', 'aksrt'), 500);
|
||||
}
|
||||
|
||||
return $this->success(['data' => \AKSRT\Payment\WeChatNotify::get_instance()->get_status()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /wechat/settings — 更新微信模板消息配置。
|
||||
*
|
||||
* 前端在密钥留空且已配置时发送掩码 ***,此时保留已存密钥;
|
||||
* 未提供的字段沿用当前值。键白名单由 Integration::update_wechat_settings() 收敛。
|
||||
*/
|
||||
public function update_wechat_settings(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$current = \AKSRT\Settings\Settings::get_wechat_settings();
|
||||
|
||||
$enabled = array_key_exists('enabled', $params) ? !empty($params['enabled']) : !empty($current['enabled']);
|
||||
$app_id = array_key_exists('app_id', $params)
|
||||
? sanitize_text_field((string) $params['app_id'])
|
||||
: (string) ($current['app_id'] ?? '');
|
||||
$template_id = array_key_exists('template_id', $params)
|
||||
? sanitize_text_field((string) $params['template_id'])
|
||||
: (string) ($current['template_id'] ?? '');
|
||||
|
||||
// 掩码 *** 表示「不修改密钥」;仅在显式传入其他值时才覆盖。
|
||||
if (array_key_exists('app_secret', $params) && (string) $params['app_secret'] !== '***') {
|
||||
$app_secret = sanitize_text_field((string) $params['app_secret']);
|
||||
} else {
|
||||
$app_secret = (string) ($current['app_secret'] ?? '');
|
||||
}
|
||||
|
||||
\AKSRT\Settings\Settings::update_wechat_settings([
|
||||
'enabled' => $enabled,
|
||||
'app_id' => $app_id,
|
||||
'app_secret' => $app_secret,
|
||||
'template_id' => $template_id,
|
||||
]);
|
||||
|
||||
return $this->success(['message' => __('微信通知配置已保存', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function rest_handle_credit_settings(\WP_REST_Request $request): \WP_REST_Response {
|
||||
if ($request->get_method() === 'GET') {
|
||||
$config = \AKSRT\Settings\CreditsRuntime::get_credit_settings();
|
||||
|
||||
+279
-63
@@ -9,6 +9,14 @@ defined('ABSPATH') || exit;
|
||||
|
||||
class SearchPush {
|
||||
|
||||
/** 推送结果 meta 键(编辑页面板与去重逻辑共用)。 */
|
||||
public const META_KEY = '_aksrt_baidu_push_back';
|
||||
|
||||
/** 「重新提交」nonce 与复选框字段名(与 SearchPushMetaBox 共用)。 */
|
||||
public const NONCE_ACTION = 'aksrt_push_meta_save';
|
||||
public const NONCE_FIELD = 'aksrt_push_meta_nonce';
|
||||
public const RESUBMIT_FIELD = 'aksrt_push_resubmit';
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
@@ -17,16 +25,20 @@ class SearchPush {
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定运行时钩子:发布/更新主动推送 + 前台自动推送 JS。
|
||||
* 绑定运行时钩子:发布/更新主动推送 + 分类法保存推送 + 前台自动推送 JS。
|
||||
*/
|
||||
public function init(): void {
|
||||
add_action('transition_post_status', [$this, 'on_transition_post_status'], 10, 3);
|
||||
add_action('saved_term', [$this, 'on_saved_term'], 10, 3);
|
||||
add_action('wp_footer', [$this, 'output_auto_push_script'], 99);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章发布/更新时主动推送链接到百度。
|
||||
*
|
||||
* 与参考主题一致:推送结果写入 meta,成功后不再重复提交;
|
||||
* 编辑页勾选「重新提交」可清空记录并强制再次推送。
|
||||
*
|
||||
* @param string $new_status 新状态。
|
||||
* @param string $old_status 旧状态。
|
||||
* @param \WP_Post $post 文章对象。
|
||||
@@ -42,27 +54,230 @@ class SearchPush {
|
||||
return;
|
||||
}
|
||||
// 仅推送公开可索引的内容类型。
|
||||
$allowed = function_exists('aksrt_searchable_post_types')
|
||||
? aksrt_searchable_post_types()
|
||||
: ['post'];
|
||||
if (!in_array($post->post_type, $allowed, true)) {
|
||||
if (!in_array($post->post_type, self::pushable_post_types(), true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$config = $this->get_config();
|
||||
if (!$this->should_push($config)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->resubmit_requested()) {
|
||||
self::clear_push_record($post->ID);
|
||||
} else {
|
||||
$is_new = $old_status !== 'publish';
|
||||
if ($is_new && empty($config['baidu_push_on_publish'])) {
|
||||
return;
|
||||
}
|
||||
if (!$is_new && empty($config['baidu_push_on_update'])) {
|
||||
return;
|
||||
}
|
||||
// 已成功提交过的链接不再重复推送。
|
||||
if ($this->record_is_complete(self::get_push_record($post->ID), $config)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->push_and_record((string) get_permalink($post), $post->ID, false, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分类法术语保存时主动推送链接到百度。
|
||||
*
|
||||
* 术语无发布/更新状态,沿用参考主题的 saved_term 触发,
|
||||
* 与文章推送共用 meta 记录、去重与「重新提交」。
|
||||
*
|
||||
* @param int $term_id 术语 ID。
|
||||
* @param int $tt_id 术语分类法 ID。
|
||||
* @param string $taxonomy 分类法名称。
|
||||
*/
|
||||
public function on_saved_term(int $term_id, int $tt_id, string $taxonomy): void {
|
||||
if (!in_array($taxonomy, self::pushable_taxonomies(), true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$config = $this->get_config();
|
||||
if (!$this->should_push($config)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->resubmit_requested()) {
|
||||
self::clear_push_record($term_id, true);
|
||||
} elseif ($this->record_is_complete(self::get_push_record($term_id, true), $config)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url = get_term_link($term_id, $taxonomy);
|
||||
if (is_wp_error($url) || $url === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->push_and_record((string) $url, $term_id, true, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 可推送的文章类型(与站内搜索 / MeiliSearch 索引共用同一策略)。
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function pushable_post_types(): array {
|
||||
$types = function_exists('aksrt_searchable_post_types') ? aksrt_searchable_post_types() : ['post'];
|
||||
|
||||
return array_values(array_unique(array_filter((array) $types)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 可推送的分类法:可推送文章类型上的公开分类法(排除文章形式等)。
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function pushable_taxonomies(): array {
|
||||
$taxonomies = get_object_taxonomies(self::pushable_post_types(), 'names');
|
||||
$taxonomies = array_diff((array) $taxonomies, ['post_format', 'nav_menu']);
|
||||
|
||||
/**
|
||||
* 过滤百度推送覆盖的分类法。
|
||||
*
|
||||
* @param string[] $taxonomies 分类法 slug 列表。
|
||||
*/
|
||||
return array_values(array_unique(apply_filters('aksrt_baidu_push_taxonomies', $taxonomies)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送功能是否已启用且配置完整(决定编辑页面板是否展示)。
|
||||
*/
|
||||
public function is_ready(): bool {
|
||||
return $this->should_push($this->get_config());
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取文章或术语的推送记录。
|
||||
*
|
||||
* @param int $object_id 文章 / 术语 ID。
|
||||
* @param bool $is_term 是否为术语。
|
||||
* @return array 记录为空时返回 []。
|
||||
*/
|
||||
public static function get_push_record(int $object_id, bool $is_term = false): array {
|
||||
$record = $is_term ? get_term_meta($object_id, self::META_KEY, true) : get_post_meta($object_id, self::META_KEY, true);
|
||||
|
||||
return is_array($record) ? $record : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入推送记录。
|
||||
*
|
||||
* @param int $object_id 文章 / 术语 ID。
|
||||
* @param array $record 推送记录。
|
||||
* @param bool $is_term 是否为术语。
|
||||
*/
|
||||
public static function save_push_record(int $object_id, array $record, bool $is_term = false): void {
|
||||
if ($is_term) {
|
||||
update_term_meta($object_id, self::META_KEY, $record);
|
||||
} else {
|
||||
update_post_meta($object_id, self::META_KEY, $record);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除推送记录(「重新提交」时调用)。
|
||||
*
|
||||
* @param int $object_id 文章 / 术语 ID。
|
||||
* @param bool $is_term 是否为术语。
|
||||
*/
|
||||
public static function clear_push_record(int $object_id, bool $is_term = false): void {
|
||||
if ($is_term) {
|
||||
delete_term_meta($object_id, self::META_KEY);
|
||||
} else {
|
||||
delete_post_meta($object_id, self::META_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送是否可用:总开关开启、Token 已配置且至少启用一个收录通道。
|
||||
*
|
||||
* @param array $config 推送配置。
|
||||
*/
|
||||
private function should_push(array $config): bool {
|
||||
if (empty($config['baidu_push_enabled'])) {
|
||||
return false;
|
||||
}
|
||||
if ((string) ($config['baidu_push_token'] ?? '') === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !empty($config['baidu_push_normal']) || !empty($config['baidu_push_fast']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送 URL 并把结果写入 meta(成功记录用于去重)。
|
||||
*
|
||||
* @param string $url 待推送链接。
|
||||
* @param int $object_id 文章 / 术语 ID。
|
||||
* @param bool $is_term 是否为术语。
|
||||
* @param array $config 推送配置。
|
||||
*/
|
||||
private function push_and_record(string $url, int $object_id, bool $is_term, array $config): void {
|
||||
if ($url === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$is_new = $old_status !== 'publish';
|
||||
if ($is_new && empty($config['baidu_push_on_publish'])) {
|
||||
return;
|
||||
$outcome = $this->push_url($url);
|
||||
self::save_push_record($object_id, $this->build_record($outcome, $config), $is_term);
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装推送记录:通道结果 + 更新时间(结构与参考主题 meta 对齐)。
|
||||
*
|
||||
* @param array $outcome push_url() 返回值。
|
||||
* @param array $config 推送配置。
|
||||
*/
|
||||
private function build_record(array $outcome, array $config): array {
|
||||
$record = ['update_time' => current_time('Y-m-d H:i:s')];
|
||||
$result = isset($outcome['result']) && is_array($outcome['result']) ? $outcome['result'] : [];
|
||||
|
||||
if (!empty($config['baidu_push_normal'])) {
|
||||
$channel = $result['normal'] ?? null;
|
||||
$record['normal_push'] = is_array($channel) && !empty($channel['success']);
|
||||
$record['normal_result'] = $channel;
|
||||
}
|
||||
if (!$is_new && empty($config['baidu_push_on_update'])) {
|
||||
return;
|
||||
if (!empty($config['baidu_push_fast'])) {
|
||||
$channel = $result['daily'] ?? null;
|
||||
$record['daily_push'] = is_array($channel) && !empty($channel['success']);
|
||||
$record['daily_result'] = $channel;
|
||||
}
|
||||
|
||||
$this->push_url((string) get_permalink($post));
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送记录是否已覆盖全部启用通道(去重判断)。
|
||||
*
|
||||
* @param array $record 推送记录。
|
||||
* @param array $config 推送配置。
|
||||
*/
|
||||
private function record_is_complete(array $record, array $config): bool {
|
||||
if (!empty($config['baidu_push_normal']) && empty($record['normal_push'])) {
|
||||
return false;
|
||||
}
|
||||
if (!empty($config['baidu_push_fast']) && empty($record['daily_push'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑页是否勾选了「重新提交」(需通过 metabox nonce 校验)。
|
||||
*/
|
||||
private function resubmit_requested(): bool {
|
||||
if (empty($_POST[self::RESUBMIT_FIELD])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$nonce = isset($_POST[self::NONCE_FIELD]) ? sanitize_text_field(wp_unslash($_POST[self::NONCE_FIELD])) : '';
|
||||
|
||||
return (bool) wp_verify_nonce($nonce, self::NONCE_ACTION);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,7 +311,50 @@ class SearchPush {
|
||||
return ['success' => false, 'message' => __('推送 Token 未配置', 'aksrt'), 'result' => []];
|
||||
}
|
||||
|
||||
$api_url = "http://data.zz.baidu.com/urls?site=" . rawurlencode(home_url('/')) . "&token={$token}";
|
||||
// 收录通道与百度接口 type 参数对应:普通收录不带参数,快速收录为 daily。
|
||||
$channels = [];
|
||||
if (!empty($config['baidu_push_normal'])) {
|
||||
$channels['normal'] = '';
|
||||
}
|
||||
if (!empty($config['baidu_push_fast'])) {
|
||||
$channels['daily'] = '&type=daily';
|
||||
}
|
||||
if (!$channels) {
|
||||
return ['success' => false, 'message' => __('未启用普通收录或快速收录通道', 'aksrt'), 'result' => []];
|
||||
}
|
||||
|
||||
$success = false;
|
||||
$results = [];
|
||||
$messages = [];
|
||||
|
||||
foreach ($channels as $channel => $suffix) {
|
||||
$outcome = $this->submit($url, $token, $suffix, $config);
|
||||
$results[$channel] = $outcome['result'];
|
||||
if ($outcome['success']) {
|
||||
$success = true;
|
||||
} else {
|
||||
$messages[] = $outcome['message'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($success) {
|
||||
return ['success' => true, 'message' => __('推送成功', 'aksrt'), 'result' => $results];
|
||||
}
|
||||
|
||||
return ['success' => false, 'message' => implode('; ', $messages), 'result' => $results];
|
||||
}
|
||||
|
||||
/**
|
||||
* 向百度链接提交接口发送单条 URL。
|
||||
*
|
||||
* @param string $url 待推送的完整 URL。
|
||||
* @param string $token 百度准入密钥。
|
||||
* @param string $suffix 接口附加参数(快速收录为 &type=daily)。
|
||||
* @param array $config 推送配置。
|
||||
* @return array{success: bool, message: string, result: array}
|
||||
*/
|
||||
private function submit(string $url, string $token, string $suffix, array $config): array {
|
||||
$api_url = 'http://data.zz.baidu.com/urls?site=' . rawurlencode(home_url('/')) . '&token=' . $token . $suffix;
|
||||
|
||||
$response = wp_remote_post($api_url, [
|
||||
'timeout' => 10,
|
||||
@@ -105,7 +363,7 @@ class SearchPush {
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
$this->log('error', $url, $response->get_error_message());
|
||||
$this->log($config, 'error', $url, $response->get_error_message());
|
||||
return ['success' => false, 'message' => $response->get_error_message(), 'result' => []];
|
||||
}
|
||||
|
||||
@@ -113,59 +371,15 @@ class SearchPush {
|
||||
$status = wp_remote_retrieve_response_code($response);
|
||||
|
||||
if ($status === 200 && isset($body['success'])) {
|
||||
$this->log('success', $url, wp_json_encode($body));
|
||||
$this->log($config, 'success', $url, wp_json_encode($body));
|
||||
return ['success' => true, 'message' => __('推送成功', 'aksrt'), 'result' => $body];
|
||||
}
|
||||
|
||||
$err_msg = $body['message'] ?? ($body['error'] ?? sprintf('HTTP %d', $status));
|
||||
$this->log('error', $url, $err_msg);
|
||||
$this->log($config, 'error', $url, $err_msg);
|
||||
return ['success' => false, 'message' => $err_msg, 'result' => $body ?? []];
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量推送 URL
|
||||
*
|
||||
* @param string[] $urls
|
||||
* @return array{success: int, failed: int, results: array}
|
||||
*/
|
||||
public function push_batch(array $urls): array {
|
||||
$config = $this->get_config();
|
||||
$batch_size = max(1, min(500, (int) ($config['baidu_push_batch_size'] ?? 50)));
|
||||
$success = 0;
|
||||
$failed = 0;
|
||||
$results = [];
|
||||
|
||||
$chunks = array_chunk($urls, $batch_size);
|
||||
foreach ($chunks as $chunk) {
|
||||
$token = (string) ($config['baidu_push_token'] ?? '');
|
||||
$api_url = "http://data.zz.baidu.com/urls?site=" . rawurlencode(home_url('/')) . "&token={$token}";
|
||||
$body_str = implode("\n", $chunk);
|
||||
|
||||
$response = wp_remote_post($api_url, [
|
||||
'timeout' => 15,
|
||||
'headers' => ['Content-Type' => 'text/plain'],
|
||||
'body' => $body_str,
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
$failed += count($chunk);
|
||||
$results[] = ['error' => $response->get_error_message()];
|
||||
continue;
|
||||
}
|
||||
|
||||
$body = json_decode(wp_remote_retrieve_body($response), true);
|
||||
if (isset($body['success'])) {
|
||||
$success += (int) ($body['success'] ?? 0);
|
||||
$failed += (int) ($body['not_same_site'] ?? 0) + (int) ($body['not_valid'] ?? 0);
|
||||
} else {
|
||||
$failed += count($chunk);
|
||||
}
|
||||
$results[] = $body ?? [];
|
||||
}
|
||||
|
||||
return ['success' => $success, 'failed' => $failed, 'results' => $results];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取自动推送 JS 代码
|
||||
*/
|
||||
@@ -175,7 +389,6 @@ class SearchPush {
|
||||
return '';
|
||||
}
|
||||
|
||||
$site_url = home_url('/');
|
||||
return <<<SCRIPT
|
||||
<script>
|
||||
(function(){
|
||||
@@ -211,13 +424,15 @@ SCRIPT;
|
||||
'provider' => 'baidu',
|
||||
'configured' => $enabled && $token !== '',
|
||||
'available' => $enabled && $token !== '',
|
||||
'verified' => !empty($config['baidu_push_verified']),
|
||||
// 百度链接提交接口无单独校验握手,Token 配置完成即可推送。
|
||||
'verified' => false,
|
||||
'transport' => 'http_api',
|
||||
'missing_fields' => $missing,
|
||||
'auto_push' => !empty($config['baidu_push_auto']),
|
||||
'normal_push' => !empty($config['baidu_push_normal']),
|
||||
'fast_push' => !empty($config['baidu_push_fast']),
|
||||
'on_publish' => !empty($config['baidu_push_on_publish']),
|
||||
'on_update' => !empty($config['baidu_push_on_update']),
|
||||
'batch_size' => max(1, (int) ($config['baidu_push_batch_size'] ?? 50)),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -228,7 +443,8 @@ SCRIPT;
|
||||
return ['baidu_push_enabled' => false];
|
||||
}
|
||||
|
||||
private function log(string $level, string $url, string $message): void {
|
||||
private function log(array $config, string $level, string $url, string $message): void {
|
||||
if (empty($config['baidu_push_log_enabled'])) return;
|
||||
if (!defined('WP_DEBUG') || !WP_DEBUG) return;
|
||||
error_log(sprintf('[AKSRT Search Push] [%s] URL: %s — %s', $level, $url, $message));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Extensions;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* 百度推送编辑页面板(对齐参考主题「百度资源提交」Metabox)。
|
||||
*
|
||||
* 文章:侧栏 Metabox;分类法:编辑 / 新增表单行。
|
||||
* 展示推送结果与更新时间,并提供「重新提交」勾选——
|
||||
* 勾选后保存会清空去重记录并强制再次推送。
|
||||
*/
|
||||
class SearchPushMetaBox {
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function init(): void {
|
||||
add_action('add_meta_boxes', [$this, 'register_post_meta_box']);
|
||||
// 分类法可能由插件延后注册,面板在 admin_init 统一挂载。
|
||||
add_action('admin_init', [$this, 'register_term_panels']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为可推送文章类型注册侧栏 Metabox(推送功能就绪后才展示)。
|
||||
*/
|
||||
public function register_post_meta_box(): void {
|
||||
if (!$this->is_ready()) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (SearchPush::pushable_post_types() as $post_type) {
|
||||
add_meta_box(
|
||||
'aksrt_baidu_push',
|
||||
__('百度资源提交', 'aksrt'),
|
||||
[$this, 'render_post_panel'],
|
||||
$post_type,
|
||||
'side',
|
||||
'high'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 为可推送分类法注册编辑 / 新增表单面板。
|
||||
*/
|
||||
public function register_term_panels(): void {
|
||||
if (!$this->is_ready()) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (SearchPush::pushable_taxonomies() as $taxonomy) {
|
||||
add_action("{$taxonomy}_edit_form_fields", [$this, 'render_term_panel'], 20);
|
||||
add_action("{$taxonomy}_add_form_fields", [$this, 'render_term_panel'], 20);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染文章 Metabox。
|
||||
*
|
||||
* @param mixed $post WP_Post 或 null。
|
||||
*/
|
||||
public function render_post_panel($post): void {
|
||||
if (!$post instanceof \WP_Post) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->render_panel((int) $post->ID, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染分类法表单行(新增表单无术语记录,仅展示提示)。
|
||||
*
|
||||
* @param mixed $term WP_Term、分类名字符串或 null。
|
||||
*/
|
||||
public function render_term_panel($term = null): void {
|
||||
$term_id = 0;
|
||||
if ($term instanceof \WP_Term) {
|
||||
$term_id = (int) $term->term_id;
|
||||
} elseif (isset($_GET['tag_ID'])) {
|
||||
$term_id = (int) $_GET['tag_ID'];
|
||||
}
|
||||
|
||||
// 新增表单无表格结构,仅编辑表单以表单行包裹。
|
||||
$this->render_panel($term_id, true, $term_id > 0);
|
||||
}
|
||||
|
||||
private function is_ready(): bool {
|
||||
return class_exists(SearchPush::class) && SearchPush::get_instance()->is_ready();
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染推送结果面板。
|
||||
*
|
||||
* @param int $object_id 文章 / 术语 ID(0 表示新增表单)。
|
||||
* @param bool $is_term 是否为术语。
|
||||
* @param bool $as_row 是否以分类法表单行包裹。
|
||||
*/
|
||||
private function render_panel(int $object_id, bool $is_term, bool $as_row = false): void {
|
||||
ob_start();
|
||||
wp_nonce_field(SearchPush::NONCE_ACTION, SearchPush::NONCE_FIELD);
|
||||
|
||||
$record = $object_id > 0 ? SearchPush::get_push_record($object_id, $is_term) : [];
|
||||
$lines = $this->result_lines($record);
|
||||
?>
|
||||
<div class="aksrt-push-meta-box">
|
||||
<?php if ($lines) : ?>
|
||||
<p class="aksrt-push-meta-box__label"><strong><?php esc_html_e('提交结果:', 'aksrt'); ?></strong></p>
|
||||
<ul class="aksrt-push-meta-box__results">
|
||||
<?php foreach ($lines as $line) : ?>
|
||||
<li><strong><?php echo esc_html($line[0]); ?></strong> <span><?php echo esc_html($line[1]); ?></span></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php else : ?>
|
||||
<p class="description"><?php esc_html_e('发布、更新后刷新页面后可查看提交结果', 'aksrt'); ?></p>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($record['update_time'])) : ?>
|
||||
<p class="aksrt-push-meta-box__resubmit">
|
||||
<label>
|
||||
<input type="checkbox" name="<?php echo esc_attr(SearchPush::RESUBMIT_FIELD); ?>" value="1" />
|
||||
<?php esc_html_e('重新提交', 'aksrt'); ?>
|
||||
</label>
|
||||
<span class="description"><?php esc_html_e('勾选后保存将清空提交记录并再次推送', 'aksrt'); ?></span>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
$body = (string) ob_get_clean();
|
||||
|
||||
if ($as_row) {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- 面板 HTML 已在输出前逐项转义。
|
||||
echo '<tr class="form-field"><th scope="row">' . esc_html(__('百度资源提交', 'aksrt')) . '</th><td>' . $body . '</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- 面板 HTML 已在输出前逐项转义。
|
||||
echo $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* 汇总推送结果行(展示项与参考主题对齐)。
|
||||
*
|
||||
* @param array $record 推送记录。
|
||||
* @return array<int,array{0:string,1:string}>
|
||||
*/
|
||||
private function result_lines(array $record): array {
|
||||
$lines = [];
|
||||
|
||||
if (!empty($record['normal_push'])) {
|
||||
$lines[] = [__('普通收录:成功', 'aksrt'), $this->format_result($record['normal_result'] ?? null)];
|
||||
} elseif (array_key_exists('normal_push', $record)) {
|
||||
$lines[] = [__('普通收录:失败', 'aksrt'), $this->format_result($record['normal_result'] ?? null)];
|
||||
}
|
||||
|
||||
if (!empty($record['daily_push'])) {
|
||||
$lines[] = [__('快速收录:成功', 'aksrt'), $this->format_result($record['daily_result'] ?? null)];
|
||||
} elseif (array_key_exists('daily_push', $record)) {
|
||||
$lines[] = [__('快速收录:失败', 'aksrt'), $this->format_result($record['daily_result'] ?? null)];
|
||||
}
|
||||
|
||||
if (!empty($record['update_time'])) {
|
||||
$lines[] = [__('更新时间:', 'aksrt'), (string) $record['update_time']];
|
||||
}
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化接口返回结果。
|
||||
*
|
||||
* @param mixed $result 百度接口返回的解码数据。
|
||||
*/
|
||||
private function format_result($result): string {
|
||||
if (is_array($result) || is_object($result)) {
|
||||
return (string) wp_json_encode($result);
|
||||
}
|
||||
|
||||
return (string) $result;
|
||||
}
|
||||
}
|
||||
@@ -24,10 +24,6 @@ trait DisplayPageGroups {
|
||||
'ai_seo_title_prompt' => trim((string) ($s['ai_seo_title_prompt'] ?? '')),
|
||||
'ai_seo_keywords_prompt' => trim((string) ($s['ai_seo_keywords_prompt'] ?? '')),
|
||||
'ai_seo_description_prompt' => trim((string) ($s['ai_seo_description_prompt'] ?? '')),
|
||||
'seo_baidu_push' => !empty($s['seo_baidu_push']) || !empty($s['baidu_push_enabled']),
|
||||
'seo_baidu_normal' => !empty($s['seo_baidu_normal']),
|
||||
'seo_baidu_fast' => !empty($s['seo_baidu_fast']),
|
||||
'seo_baidu_token' => (string) (($s['seo_baidu_token'] ?? '') ?: ($s['baidu_push_token'] ?? '')),
|
||||
'seo_image_optimize' => !isset($s['seo_image_optimize']) || !empty($s['seo_image_optimize']),
|
||||
'seo_image_default' => esc_url_raw((string) ($s['seo_image_default'] ?? '')),
|
||||
'seo_image_always_default'=> !empty($s['seo_image_always_default']),
|
||||
|
||||
@@ -128,29 +128,15 @@ class ExtensionRuntime {
|
||||
public static function get_search_push_settings(): array {
|
||||
$settings = Settings::get_all();
|
||||
|
||||
$enabled = !empty($settings['baidu_push_enabled']) || !empty($settings['seo_baidu_push']);
|
||||
$token = (string) ($settings['baidu_push_token'] ?? '');
|
||||
if ($token === '' && !empty($settings['seo_baidu_token'])) {
|
||||
$token = (string) $settings['seo_baidu_token'];
|
||||
}
|
||||
|
||||
return [
|
||||
'baidu_push_enabled' => $enabled,
|
||||
'baidu_push_token' => $token,
|
||||
'baidu_push_enabled' => !empty($settings['baidu_push_enabled']),
|
||||
'baidu_push_token' => (string) ($settings['baidu_push_token'] ?? ''),
|
||||
'baidu_push_auto' => !isset($settings['baidu_push_auto']) || !empty($settings['baidu_push_auto']),
|
||||
'baidu_push_normal' => !isset($settings['baidu_push_normal']) || !empty($settings['baidu_push_normal']),
|
||||
'baidu_push_fast' => !empty($settings['baidu_push_fast']),
|
||||
'baidu_push_on_publish' => !isset($settings['baidu_push_on_publish']) || !empty($settings['baidu_push_on_publish']),
|
||||
'baidu_push_on_update' => !empty($settings['baidu_push_on_update']),
|
||||
'baidu_push_batch_size' => max(1, min(500, (int) ($settings['baidu_push_batch_size'] ?? 50))),
|
||||
'baidu_push_log_enabled' => !isset($settings['baidu_push_log_enabled']) || !empty($settings['baidu_push_log_enabled']),
|
||||
'baidu_push_verified' => !empty($settings['baidu_push_verified']),
|
||||
'xzh_on' => !empty($settings['xzh_on']),
|
||||
'xzh_appid' => (string) ($settings['xzh_appid'] ?? ''),
|
||||
'xzh_render_tail' => !isset($settings['xzh_render_tail']) || !empty($settings['xzh_render_tail']),
|
||||
'xzh_jsonld_single' => !isset($settings['xzh_jsonld_single']) || !empty($settings['xzh_jsonld_single']),
|
||||
'xzh_jsonld_page' => !empty($settings['xzh_jsonld_page']),
|
||||
'xzh_jsonld_img' => !empty($settings['xzh_jsonld_img']),
|
||||
'seo_baidu_normal' => !empty($settings['seo_baidu_normal']),
|
||||
'seo_baidu_fast' => !empty($settings['seo_baidu_fast']),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
<?php
|
||||
/**
|
||||
* Baidu push harness.
|
||||
*
|
||||
* Drives the REAL SearchPush / SearchPushMetaBox against WP stubs so the
|
||||
* reference-parity contract is covered end to end:
|
||||
* - normal / daily channels hit data.zz.baidu.com (daily appends type=daily);
|
||||
* - successful pushes are recorded in meta and never re-submitted;
|
||||
* - failed pushes retry on update when baidu_push_on_update is on;
|
||||
* - the 「重新提交」checkbox forces a push only with a valid metabox nonce;
|
||||
* - terms push via saved_term (post_format and other taxonomies excluded);
|
||||
* - the edit panel renders status lines, update time and the resubmit checkbox.
|
||||
*
|
||||
* Usage: php baidu-push-harness.php -> prints "Baidu push harness passed".
|
||||
*/
|
||||
|
||||
namespace {
|
||||
define('ABSPATH', __DIR__ . '/');
|
||||
define('WP_DEBUG', false);
|
||||
|
||||
final class WP_Error {
|
||||
public function __construct(private string $code, private string $message) {}
|
||||
public function get_error_message(): string { return $this->message; }
|
||||
}
|
||||
|
||||
final class WP_Post {
|
||||
public int $ID = 0;
|
||||
public string $post_type = 'post';
|
||||
public string $post_status = 'publish';
|
||||
}
|
||||
|
||||
final class WP_Term {
|
||||
public int $term_id = 0;
|
||||
public string $taxonomy = 'category';
|
||||
}
|
||||
|
||||
// ---------- WP function stubs ----------
|
||||
function add_action(...$args) {}
|
||||
function apply_filters($tag, $value) { return $value; }
|
||||
function __($s, $d = null) { return (string) $s; }
|
||||
function esc_html($s) { return htmlspecialchars((string) $s, ENT_QUOTES); }
|
||||
function esc_attr($s) { return htmlspecialchars((string) $s, ENT_QUOTES); }
|
||||
function esc_html_e($s, $d = null) { echo esc_html($s); }
|
||||
function wp_json_encode($v, $f = 0) { return (string) json_encode($v, $f); }
|
||||
function wp_nonce_field($action, $name) { echo '<input type="hidden" name="' . $name . '" value="nonce" />'; }
|
||||
function wp_unslash($s) { return $s; }
|
||||
function sanitize_text_field($s) { return trim(strip_tags((string) $s)); }
|
||||
function wp_verify_nonce($nonce, $action) { return $nonce === 'valid-nonce' && $action === 'aksrt_push_meta_save'; }
|
||||
function is_wp_error($t) { return $t instanceof WP_Error; }
|
||||
function home_url($path = '/') { return 'https://example.test' . $path; }
|
||||
function current_time($type = 'mysql') { return '2026-10-13 12:00:00'; }
|
||||
function get_permalink($post) { return 'https://example.test/?p=' . (int) $post->ID; }
|
||||
function get_term_link($term, $taxonomy = '') {
|
||||
$id = is_object($term) ? (int) $term->term_id : (int) $term;
|
||||
return 'https://example.test/?cat=' . $id;
|
||||
}
|
||||
function wp_is_post_revision($id) { return false; }
|
||||
function wp_is_post_autosave($id) { return false; }
|
||||
function aksrt_searchable_post_types(): array { return ['post']; }
|
||||
function get_object_taxonomies($types, $output = 'names') { return ['post_tag', 'category', 'post_format']; }
|
||||
|
||||
// Outgoing HTTP: every call is captured so channel/URL/body can be asserted.
|
||||
$GLOBALS['push_requests'] = [];
|
||||
function wp_remote_post($url, $args = []) {
|
||||
$GLOBALS['push_requests'][] = ['url' => (string) $url, 'body' => (string) ($args['body'] ?? '')];
|
||||
return ['response' => ['code' => 200], 'body' => (string) json_encode(['success' => 1, 'remain' => 99])];
|
||||
}
|
||||
function wp_remote_retrieve_body($r) { return is_array($r) ? (string) ($r['body'] ?? '') : ''; }
|
||||
function wp_remote_retrieve_response_code($r) { return is_array($r) ? (int) ($r['response']['code'] ?? 0) : 0; }
|
||||
|
||||
// In-memory meta stores.
|
||||
$GLOBALS['push_post_meta'] = [];
|
||||
$GLOBALS['push_term_meta'] = [];
|
||||
function get_post_meta($id, $key, $single = false) { return $GLOBALS['push_post_meta'][$id][$key] ?? ''; }
|
||||
function update_post_meta($id, $key, $value) { $GLOBALS['push_post_meta'][$id][$key] = $value; return true; }
|
||||
function delete_post_meta($id, $key) { unset($GLOBALS['push_post_meta'][$id][$key]); return true; }
|
||||
function get_term_meta($id, $key, $single = false) { return $GLOBALS['push_term_meta'][$id][$key] ?? ''; }
|
||||
function update_term_meta($id, $key, $value) { $GLOBALS['push_term_meta'][$id][$key] = $value; return true; }
|
||||
function delete_term_meta($id, $key) { unset($GLOBALS['push_term_meta'][$id][$key]); return true; }
|
||||
}
|
||||
|
||||
namespace AKSRT\Settings {
|
||||
class Settings {
|
||||
public static array $config = [];
|
||||
public static function get_search_push_settings(): array { return self::$config; }
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
require_once dirname(__DIR__, 2) . '/src/Extensions/SearchPush.php';
|
||||
require_once dirname(__DIR__, 2) . '/src/Extensions/SearchPushMetaBox.php';
|
||||
|
||||
use AKSRT\Extensions\SearchPush;
|
||||
use AKSRT\Extensions\SearchPushMetaBox;
|
||||
|
||||
$failures = [];
|
||||
$check = static function (string $label, bool $ok) use (&$failures): void {
|
||||
if (!$ok) {
|
||||
$failures[] = $label;
|
||||
}
|
||||
};
|
||||
|
||||
$reset = static function (array $config): void {
|
||||
\AKSRT\Settings\Settings::$config = $config;
|
||||
$GLOBALS['push_requests'] = [];
|
||||
$GLOBALS['push_post_meta'] = [];
|
||||
$GLOBALS['push_term_meta'] = [];
|
||||
$_POST = [];
|
||||
};
|
||||
|
||||
$post = static function (int $id): WP_Post {
|
||||
$p = new WP_Post();
|
||||
$p->ID = $id;
|
||||
return $p;
|
||||
};
|
||||
|
||||
$defaults = [
|
||||
'baidu_push_enabled' => true,
|
||||
'baidu_push_token' => 'TOKEN',
|
||||
'baidu_push_auto' => true,
|
||||
'baidu_push_on_publish' => true,
|
||||
'baidu_push_on_update' => false,
|
||||
'baidu_push_normal' => true,
|
||||
'baidu_push_fast' => false,
|
||||
'baidu_push_log_enabled' => true,
|
||||
];
|
||||
|
||||
$push = SearchPush::get_instance();
|
||||
$api = 'http://data.zz.baidu.com/urls?site=' . rawurlencode('https://example.test/') . '&token=TOKEN';
|
||||
|
||||
// 1) First publish pushes once through the normal-collection endpoint.
|
||||
$reset($defaults);
|
||||
$push->on_transition_post_status('publish', 'draft', $post(11));
|
||||
$check('首次发布推送一次', count($GLOBALS['push_requests']) === 1);
|
||||
$check('普通收录请求地址正确', ($GLOBALS['push_requests'][0]['url'] ?? '') === $api);
|
||||
$check('普通收录请求体为文章链接', ($GLOBALS['push_requests'][0]['body'] ?? '') === 'https://example.test/?p=11');
|
||||
$record = SearchPush::get_push_record(11);
|
||||
$check('推送记录写入 meta', !empty($record['normal_push']));
|
||||
$check('推送记录带更新时间', !empty($record['update_time']));
|
||||
$check('推送记录不含快速收录', !array_key_exists('daily_push', $record));
|
||||
|
||||
// 2) A completed push is never re-submitted on save.
|
||||
$push->on_transition_post_status('publish', 'publish', $post(11));
|
||||
$check('成功后更新不重复推送', count($GLOBALS['push_requests']) === 1);
|
||||
|
||||
// 3) Failed push retries on update when the update switch is on.
|
||||
$reset(array_merge($defaults, ['baidu_push_on_update' => true]));
|
||||
SearchPush::save_push_record(12, ['normal_push' => false, 'normal_result' => null, 'update_time' => '2026-10-13 09:00:00']);
|
||||
$push->on_transition_post_status('publish', 'publish', $post(12));
|
||||
$check('失败后更新重试一次', count($GLOBALS['push_requests']) === 1);
|
||||
$check('重试后记录为成功', !empty(SearchPush::get_push_record(12)['normal_push']));
|
||||
$push->on_transition_post_status('publish', 'publish', $post(12));
|
||||
$check('重试成功后不重复推送', count($GLOBALS['push_requests']) === 1);
|
||||
|
||||
// 4) 「重新提交」with a valid nonce forces a push even after success.
|
||||
$reset($defaults);
|
||||
$push->on_transition_post_status('publish', 'draft', $post(13));
|
||||
$_POST[SearchPush::RESUBMIT_FIELD] = '1';
|
||||
$_POST[SearchPush::NONCE_FIELD] = 'valid-nonce';
|
||||
$push->on_transition_post_status('publish', 'publish', $post(13));
|
||||
$check('重新提交强制再次推送', count($GLOBALS['push_requests']) === 2);
|
||||
|
||||
// 5) A forged nonce is ignored, dedup still applies.
|
||||
$reset($defaults);
|
||||
$push->on_transition_post_status('publish', 'draft', $post(14));
|
||||
$_POST[SearchPush::RESUBMIT_FIELD] = '1';
|
||||
$_POST[SearchPush::NONCE_FIELD] = 'forged-nonce';
|
||||
$push->on_transition_post_status('publish', 'publish', $post(14));
|
||||
$check('无效 nonce 的重新提交被忽略', count($GLOBALS['push_requests']) === 1);
|
||||
|
||||
// 6) Fast collection appends type=daily and records both channels.
|
||||
$reset(array_merge($defaults, ['baidu_push_fast' => true]));
|
||||
$push->on_transition_post_status('publish', 'draft', $post(15));
|
||||
$check('双通道推送两次', count($GLOBALS['push_requests']) === 2);
|
||||
$check('快速收录走 type=daily', str_contains($GLOBALS['push_requests'][1]['url'] ?? '', '&type=daily'));
|
||||
$record15 = SearchPush::get_push_record(15);
|
||||
$check('双通道记录完整', !empty($record15['normal_push']) && !empty($record15['daily_push']));
|
||||
$push->on_transition_post_status('publish', 'publish', $post(15));
|
||||
$check('双通道成功后不重复推送', count($GLOBALS['push_requests']) === 2);
|
||||
|
||||
// 7) No channel enabled: nothing pushed, nothing recorded.
|
||||
$reset(array_merge($defaults, ['baidu_push_normal' => false]));
|
||||
$push->on_transition_post_status('publish', 'draft', $post(16));
|
||||
$check('通道全关不推送', count($GLOBALS['push_requests']) === 0);
|
||||
$check('通道全关不写记录', SearchPush::get_push_record(16) === []);
|
||||
|
||||
// 8) Feature off / token missing: nothing pushed, nothing recorded.
|
||||
$reset(array_merge($defaults, ['baidu_push_enabled' => false]));
|
||||
$push->on_transition_post_status('publish', 'draft', $post(17));
|
||||
$check('未启用不推送', count($GLOBALS['push_requests']) === 0);
|
||||
$reset(array_merge($defaults, ['baidu_push_token' => '']));
|
||||
$push->on_transition_post_status('publish', 'draft', $post(18));
|
||||
$check('缺少 Token 不推送', count($GLOBALS['push_requests']) === 0);
|
||||
$check('缺少 Token 不写记录', SearchPush::get_push_record(18) === []);
|
||||
|
||||
// 9) Terms push via saved_term with the same dedup / resubmit rules.
|
||||
$reset($defaults);
|
||||
$push->on_saved_term(21, 21, 'category');
|
||||
$check('术语推送一次', count($GLOBALS['push_requests']) === 1);
|
||||
$check('术语推送链接正确', ($GLOBALS['push_requests'][0]['body'] ?? '') === 'https://example.test/?cat=21');
|
||||
$check('术语记录写入', !empty(SearchPush::get_push_record(21, true)['normal_push']));
|
||||
$push->on_saved_term(21, 21, 'category');
|
||||
$check('术语成功后不重复推送', count($GLOBALS['push_requests']) === 1);
|
||||
$push->on_saved_term(22, 22, 'post_format');
|
||||
$check('post_format 术语不推送', count($GLOBALS['push_requests']) === 1);
|
||||
$_POST[SearchPush::RESUBMIT_FIELD] = '1';
|
||||
$_POST[SearchPush::NONCE_FIELD] = 'valid-nonce';
|
||||
$push->on_saved_term(21, 21, 'category');
|
||||
$check('术语重新提交强制推送', count($GLOBALS['push_requests']) === 2);
|
||||
|
||||
// 10) Shared type / taxonomy policy.
|
||||
$check('可推送文章类型', SearchPush::pushable_post_types() === ['post']);
|
||||
$check('可推送分类法排除 post_format', SearchPush::pushable_taxonomies() === ['post_tag', 'category']);
|
||||
|
||||
// 11) Edit panel rendering.
|
||||
$reset($defaults);
|
||||
SearchPush::save_push_record(31, [
|
||||
'normal_push' => true,
|
||||
'normal_result' => ['success' => 1],
|
||||
'daily_push' => false,
|
||||
'daily_result' => null,
|
||||
'update_time' => '2026-10-13 09:00:00',
|
||||
]);
|
||||
SearchPush::save_push_record(31, [
|
||||
'normal_push' => true,
|
||||
'normal_result' => ['success' => 1],
|
||||
'daily_push' => false,
|
||||
'daily_result' => null,
|
||||
'update_time' => '2026-10-13 09:00:00',
|
||||
], true);
|
||||
$box = SearchPushMetaBox::get_instance();
|
||||
|
||||
ob_start();
|
||||
$box->render_post_panel($post(31));
|
||||
$html = (string) ob_get_clean();
|
||||
$check('面板含普通收录成功', str_contains($html, '普通收录:成功'));
|
||||
$check('面板含快速收录失败', str_contains($html, '快速收录:失败'));
|
||||
$check('面板含更新时间', str_contains($html, '2026-10-13 09:00:00'));
|
||||
$check('面板含重新提交勾选', str_contains($html, 'name="aksrt_push_resubmit"'));
|
||||
$check('面板含 nonce 字段', str_contains($html, 'name="aksrt_push_meta_nonce"'));
|
||||
$check('面板转义接口 JSON', str_contains($html, '{"success":1}'));
|
||||
|
||||
ob_start();
|
||||
$box->render_term_panel(null);
|
||||
$addHtml = (string) ob_get_clean();
|
||||
$check('新增术语表单仅提示', str_contains($addHtml, '发布、更新后刷新页面后可查看提交结果') && !str_contains($addHtml, '<tr'));
|
||||
|
||||
$term = new WP_Term();
|
||||
$term->term_id = 31;
|
||||
ob_start();
|
||||
$box->render_term_panel($term);
|
||||
$termHtml = (string) ob_get_clean();
|
||||
$check('术语编辑表单为表单行', str_contains($termHtml, '<tr class="form-field">'));
|
||||
$check('术语编辑表单含重新提交', str_contains($termHtml, 'name="aksrt_push_resubmit"'));
|
||||
|
||||
if ($failures) {
|
||||
echo "Baidu push harness FAILED\n";
|
||||
foreach ($failures as $failure) {
|
||||
echo ' - ' . $failure . "\n";
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "Baidu push harness passed\n";
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* 「百度推送」后台设置页契约(对齐参考主题 zibll):
|
||||
* 1) PHP schema(SchemaExtensionsPush.php)与 JS schema(push.js)字段键、顺序、默认值一致;
|
||||
* 2) 参考主题字段全集存在:自动推送 JS、普通收录、快速收录、准入 Token、发布/更新触发;
|
||||
* 3) 双路/迁移字段(SEO 页 seo_baidu_*、熊掌号 xzh_*、批量推送 batch_*)已彻底移除,无双路兼容;
|
||||
* 4) 运行时(SearchPush + ExtensionRuntime)只读取 canonical 键,快速收录走 type=daily;
|
||||
* 5) 分类法 saved_term 推送、meta 去重、「重新提交」nonce 与编辑页面板均已接入,
|
||||
* 并由 PHP harness 驱动真实逻辑验证。
|
||||
*/
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const { execFileSync } = require('node:child_process');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
|
||||
const root = path.resolve(__dirname, '..', '..');
|
||||
const php = fs.readFileSync(path.join(root, 'src/Admin/Schema/SchemaExtensionsPush.php'), 'utf8');
|
||||
const js = fs.readFileSync(path.join(root, 'assets/admin/src/schema/sections/extensions/push.js'), 'utf8');
|
||||
const runtime = fs.readFileSync(path.join(root, 'src/Extensions/SearchPush.php'), 'utf8');
|
||||
const extensionRuntime = fs.readFileSync(path.join(root, 'src/Settings/ExtensionRuntime.php'), 'utf8');
|
||||
const globalPhp = fs.readFileSync(path.join(root, 'src/Admin/Schema/GlobalSchema.php'), 'utf8');
|
||||
const seoJs = fs.readFileSync(path.join(root, 'assets/admin/src/schema/sections/global/seo.js'), 'utf8');
|
||||
const displayGroups = fs.readFileSync(path.join(root, 'src/Settings/DisplayPageGroups.php'), 'utf8');
|
||||
const metaBox = fs.readFileSync(path.join(root, 'src/Extensions/SearchPushMetaBox.php'), 'utf8');
|
||||
const functions = fs.readFileSync(path.join(root, 'functions.php'), 'utf8');
|
||||
|
||||
// 参考主题(zibll)百度推送字段全集,AKSRT 单一 canonical 配置面(扩展&增强 → 百度推送)。
|
||||
const REFERENCE_KEYS = [
|
||||
'baidu_push_enabled',
|
||||
'baidu_push_token',
|
||||
'baidu_push_auto',
|
||||
'baidu_push_on_publish',
|
||||
'baidu_push_on_update',
|
||||
'baidu_push_normal',
|
||||
'baidu_push_fast',
|
||||
'baidu_push_log_enabled',
|
||||
];
|
||||
|
||||
// 双路 / 迁移 / 死代码字段:SEO 页 seo_baidu_*、熊掌号 xzh_*、批量推送与校验占位。
|
||||
const DUAL_KEYS = [
|
||||
'seo_baidu_push',
|
||||
'seo_baidu_normal',
|
||||
'seo_baidu_fast',
|
||||
'seo_baidu_token',
|
||||
'xzh_on',
|
||||
'xzh_appid',
|
||||
'xzh_render_tail',
|
||||
'xzh_jsonld_single',
|
||||
'xzh_jsonld_page',
|
||||
'xzh_jsonld_img',
|
||||
'baidu_push_batch_size',
|
||||
'baidu_push_verified',
|
||||
'push_batch',
|
||||
'batch_size',
|
||||
];
|
||||
|
||||
function phpFieldKeys(src) {
|
||||
const found = [];
|
||||
const re = /'key'\s*=>\s*'([a-z0-9_]+)',\s*'label'/g;
|
||||
let m;
|
||||
while ((m = re.exec(src)) !== null) found.push(m[1]);
|
||||
return found;
|
||||
}
|
||||
|
||||
function jsFieldKeys(src) {
|
||||
const found = [];
|
||||
const re = /key:\s*'([a-z0-9_]+)',\s*label:/g;
|
||||
let m;
|
||||
while ((m = re.exec(src)) !== null) found.push(m[1]);
|
||||
return found;
|
||||
}
|
||||
|
||||
test('PHP push schema 字段键与顺序对齐参考主题', () => {
|
||||
assert.deepEqual(phpFieldKeys(php), REFERENCE_KEYS);
|
||||
});
|
||||
|
||||
test('JS push schema 字段键与顺序对齐参考主题', () => {
|
||||
assert.deepEqual(jsFieldKeys(js), REFERENCE_KEYS);
|
||||
});
|
||||
|
||||
test('PHP schema 默认值对齐', () => {
|
||||
assert.match(php, /'baidu_push_enabled',\s*'label'\s*=>\s*'启用百度推送',\s*'type'\s*=>\s*'switch',\s*'default'\s*=>\s*false/);
|
||||
assert.match(php, /'baidu_push_token',\s*'label'\s*=>\s*'推送 Token',\s*'type'\s*=>\s*'input',\s*'default'\s*=>\s*''/);
|
||||
assert.match(php, /'baidu_push_auto',\s*'label'\s*=>\s*'自动推送',\s*'type'\s*=>\s*'switch',\s*'default'\s*=>\s*true/);
|
||||
assert.match(php, /'baidu_push_on_publish',\s*'label'\s*=>\s*'发布时主动推送',\s*'type'\s*=>\s*'switch',\s*'default'\s*=>\s*true/);
|
||||
assert.match(php, /'baidu_push_on_update',\s*'label'\s*=>\s*'更新时推送',\s*'type'\s*=>\s*'switch',\s*'default'\s*=>\s*false/);
|
||||
assert.match(php, /'baidu_push_normal',\s*'label'\s*=>\s*'普通收录',\s*'type'\s*=>\s*'switch',\s*'default'\s*=>\s*true/);
|
||||
assert.match(php, /'baidu_push_fast',\s*'label'\s*=>\s*'快速收录',\s*'type'\s*=>\s*'switch',\s*'default'\s*=>\s*false/);
|
||||
assert.match(php, /'baidu_push_log_enabled',\s*'label'\s*=>\s*'记录推送日志',\s*'type'\s*=>\s*'switch',\s*'default'\s*=>\s*true/);
|
||||
});
|
||||
|
||||
test('JS schema 默认值对齐', () => {
|
||||
assert.match(js, /baidu_push_enabled:\s*false/);
|
||||
assert.match(js, /baidu_push_token:\s*''/);
|
||||
assert.match(js, /baidu_push_auto:\s*true/);
|
||||
assert.match(js, /baidu_push_on_publish:\s*true/);
|
||||
assert.match(js, /baidu_push_on_update:\s*false/);
|
||||
assert.match(js, /baidu_push_normal:\s*true/);
|
||||
assert.match(js, /baidu_push_fast:\s*false/);
|
||||
assert.match(js, /baidu_push_log_enabled:\s*true/);
|
||||
});
|
||||
|
||||
test('双路/迁移/死代码字段在 schema、运行时与展示层均已移除(无双路)', () => {
|
||||
for (const legacy of DUAL_KEYS) {
|
||||
assert.ok(!php.includes(legacy), `SchemaExtensionsPush 残留: ${legacy}`);
|
||||
assert.ok(!js.includes(legacy), `push.js 残留: ${legacy}`);
|
||||
assert.ok(!runtime.includes(legacy), `SearchPush 残留: ${legacy}`);
|
||||
assert.ok(!extensionRuntime.includes(legacy), `ExtensionRuntime 残留: ${legacy}`);
|
||||
assert.ok(!globalPhp.includes(legacy), `GlobalSchema 残留: ${legacy}`);
|
||||
assert.ok(!seoJs.includes(legacy), `seo.js 残留: ${legacy}`);
|
||||
assert.ok(!displayGroups.includes(legacy), `DisplayPageGroups 残留: ${legacy}`);
|
||||
assert.ok(!metaBox.includes(legacy), `SearchPushMetaBox 残留: ${legacy}`);
|
||||
assert.ok(!functions.includes(legacy), `functions.php 残留: ${legacy}`);
|
||||
}
|
||||
assert.ok(!globalPhp.includes("'baidu'"), 'GlobalSchema SEO 分组残留百度重复配置面');
|
||||
assert.ok(!seoJs.includes("key: 'baidu'"), 'seo.js 残留百度重复配置面');
|
||||
});
|
||||
|
||||
test('普通/快速收录通道接入百度链接提交接口', () => {
|
||||
assert.match(runtime, /data\.zz\.baidu\.com\/urls\?site=/);
|
||||
assert.match(runtime, /'baidu_push_normal'/);
|
||||
assert.match(runtime, /'baidu_push_fast'/);
|
||||
assert.match(runtime, /&type=daily/);
|
||||
assert.match(extensionRuntime, /'baidu_push_normal'\s*=>/);
|
||||
assert.match(extensionRuntime, /'baidu_push_fast'\s*=>/);
|
||||
});
|
||||
|
||||
test('前台自动推送 JS 与参考主题一致', () => {
|
||||
assert.match(runtime, /zz\.bdstatic\.com\/linksubmit\/push\.js/);
|
||||
assert.match(runtime, /push\.zhanzhang\.baidu\.com\/push\.js/);
|
||||
assert.match(runtime, /add_action\('wp_footer'/);
|
||||
});
|
||||
|
||||
test('发布/更新触发沿用 transition_post_status 且仅限可检索内容类型', () => {
|
||||
assert.match(runtime, /transition_post_status/);
|
||||
assert.match(runtime, /aksrt_searchable_post_types/);
|
||||
assert.match(runtime, /wp_is_post_revision/);
|
||||
assert.match(runtime, /wp_is_post_autosave/);
|
||||
});
|
||||
|
||||
test('推送日志开关接入运行时', () => {
|
||||
assert.match(runtime, /baidu_push_log_enabled/);
|
||||
assert.match(extensionRuntime, /'baidu_push_log_enabled'/);
|
||||
});
|
||||
|
||||
test('服务状态契约保留扩展总览消费的键', () => {
|
||||
for (const key of ['enabled', 'provider', 'configured', 'available', 'verified', 'transport', 'missing_fields']) {
|
||||
assert.ok(runtime.includes(`'${key}'`), `get_status 缺少 ${key}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('分类法推送接入 saved_term 且排除文章形式', () => {
|
||||
assert.match(runtime, /add_action\('saved_term'/);
|
||||
assert.match(runtime, /public static function pushable_taxonomies/);
|
||||
assert.match(runtime, /array_diff\(\(array\) \$taxonomies, \['post_format', 'nav_menu'\]\)/);
|
||||
assert.match(runtime, /aksrt_baidu_push_taxonomies/);
|
||||
assert.match(runtime, /get_term_link\(\$term_id, \$taxonomy\)/);
|
||||
});
|
||||
|
||||
test('推送结果写入 meta 并按记录去重', () => {
|
||||
assert.match(runtime, /META_KEY\s*=\s*'_aksrt_baidu_push_back'/);
|
||||
assert.match(runtime, /private function record_is_complete/);
|
||||
assert.match(runtime, /private function build_record/);
|
||||
assert.match(runtime, /'normal_push'/);
|
||||
assert.match(runtime, /'daily_push'/);
|
||||
assert.match(runtime, /'update_time'/);
|
||||
assert.match(runtime, /update_term_meta\(/);
|
||||
assert.match(runtime, /delete_term_meta\(/);
|
||||
});
|
||||
|
||||
test('「重新提交」勾选需通过 metabox nonce 校验', () => {
|
||||
assert.match(runtime, /NONCE_ACTION\s*=\s*'aksrt_push_meta_save'/);
|
||||
assert.match(runtime, /NONCE_FIELD\s*=\s*'aksrt_push_meta_nonce'/);
|
||||
assert.match(runtime, /RESUBMIT_FIELD\s*=\s*'aksrt_push_resubmit'/);
|
||||
assert.match(runtime, /wp_verify_nonce\(\$nonce, self::NONCE_ACTION\)/);
|
||||
});
|
||||
|
||||
test('编辑页面板与参考主题「百度资源提交」Metabox 对齐', () => {
|
||||
assert.match(metaBox, /add_meta_box\(\s*'aksrt_baidu_push',\s*__\('百度资源提交', 'aksrt'\)/);
|
||||
assert.match(metaBox, /'side',\s*'high'/);
|
||||
assert.match(metaBox, /\{\$taxonomy\}_edit_form_fields/);
|
||||
assert.match(metaBox, /\{\$taxonomy\}_add_form_fields/);
|
||||
assert.match(metaBox, /普通收录:成功/);
|
||||
assert.match(metaBox, /快速收录:失败/);
|
||||
assert.match(metaBox, /更新时间:/);
|
||||
assert.match(metaBox, /重新提交/);
|
||||
assert.match(metaBox, /发布、更新后刷新页面后可查看提交结果/);
|
||||
assert.match(metaBox, /wp_nonce_field\(SearchPush::NONCE_ACTION, SearchPush::NONCE_FIELD\)/);
|
||||
assert.match(metaBox, /esc_html\(\$line\[1\]\)/, '接口结果需转义后输出');
|
||||
});
|
||||
|
||||
test('functions.php 接线推送面板', () => {
|
||||
assert.match(functions, /SearchPushMetaBox::get_instance\(\)->init\(\)/);
|
||||
});
|
||||
|
||||
test('百度推送行为 harness 通过', () => {
|
||||
const harness = path.join(root, 'tests', 'admin', 'baidu-push-harness.php');
|
||||
const output = execFileSync('php', [harness], { encoding: 'utf8' });
|
||||
assert.match(output, /Baidu push harness passed/);
|
||||
});
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
/**
|
||||
* WeChat template-message settings harness.
|
||||
*
|
||||
* Drives the REAL UserController REST callbacks (get_wechat_status /
|
||||
* update_wechat_settings) against WP stubs so the previously-broken routes are
|
||||
* covered end to end:
|
||||
* - GET /wechat/status returns the WeChatNotify status shape (masked secret);
|
||||
* - POST /wechat/settings persists through Integration's key whitelist;
|
||||
* - the *** mask keeps the stored secret instead of overwriting it;
|
||||
* - omitted fields fall back to current values; unknown keys are dropped;
|
||||
* - inputs are sanitized before storage.
|
||||
*
|
||||
* Usage: php wechat-settings-harness.php -> prints "WeChat settings harness passed".
|
||||
*/
|
||||
|
||||
namespace {
|
||||
define('ABSPATH', __DIR__ . '/');
|
||||
|
||||
final class WP_REST_Response {
|
||||
public $data;
|
||||
public $status;
|
||||
public function __construct($data = null, int $status = 200) {
|
||||
$this->data = $data;
|
||||
$this->status = $status;
|
||||
}
|
||||
}
|
||||
|
||||
final class WP_REST_Request {
|
||||
private array $json;
|
||||
public function __construct(array $json = []) { $this->json = $json; }
|
||||
public function get_json_params(): array { return $this->json; }
|
||||
public function get_method(): string { return 'POST'; }
|
||||
}
|
||||
|
||||
// ---------- WP function stubs ----------
|
||||
function __($s, $d = null) { return (string) $s; }
|
||||
function sanitize_text_field($s) { return trim(strip_tags((string) $s)); }
|
||||
function current_user_can($c) { return true; }
|
||||
function wp_send_json_success($d) { echo (string) json_encode($d); }
|
||||
function get_transient($k) { return $GLOBALS['wechat_transient'] ?? false; }
|
||||
function get_option($k, $default = false) { return $GLOBALS['wechat_options'][$k] ?? $default; }
|
||||
function update_option($k, $v) { $GLOBALS['wechat_options'][$k] = $v; return true; }
|
||||
|
||||
$GLOBALS['wechat_options'] = ['aksrt_wechat_settings' => [
|
||||
'enabled' => true,
|
||||
'app_id' => 'wxAPPID',
|
||||
'app_secret' => 'SECRET123',
|
||||
'template_id' => 'TPL1',
|
||||
]];
|
||||
$GLOBALS['wechat_transient'] = 'ACCESS_TOKEN';
|
||||
|
||||
$GLOBALS['wpdb'] = new class {
|
||||
public $usermeta = 'wp_usermeta';
|
||||
public function get_var($sql) { return 7; }
|
||||
};
|
||||
}
|
||||
|
||||
namespace AKSRT\Settings {
|
||||
// Settings 层仅为薄委托,harness 直接对接真实 Integration。
|
||||
class Settings {
|
||||
public static function get_wechat_settings(): array { return Integration::get_wechat_settings(); }
|
||||
public static function update_wechat_settings(array $settings): void { Integration::update_wechat_settings($settings); }
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
require_once dirname(__DIR__, 2) . '/src/Admin/ControllerBase.php';
|
||||
require_once dirname(__DIR__, 2) . '/src/Admin/UserControllerReportOperations.php';
|
||||
require_once dirname(__DIR__, 2) . '/src/Admin/UserControllerAuthPhoneOperations.php';
|
||||
require_once dirname(__DIR__, 2) . '/src/Admin/UserControllerRestrictionOperations.php';
|
||||
require_once dirname(__DIR__, 2) . '/src/Admin/UserControllerFollowOperations.php';
|
||||
require_once dirname(__DIR__, 2) . '/src/Admin/UserControllerProfileMediaOperations.php';
|
||||
require_once dirname(__DIR__, 2) . '/src/Admin/UserController.php';
|
||||
require_once dirname(__DIR__, 2) . '/src/Settings/Integration.php';
|
||||
require_once dirname(__DIR__, 2) . '/src/Settings/DomainGroups.php';
|
||||
require_once dirname(__DIR__, 2) . '/src/Payment/WeChatNotify.php';
|
||||
|
||||
$failures = [];
|
||||
$check = static function (string $label, bool $ok) use (&$failures): void {
|
||||
if (!$ok) {
|
||||
$failures[] = $label;
|
||||
}
|
||||
};
|
||||
$stored = static function (): array {
|
||||
$v = get_option('aksrt_wechat_settings', []);
|
||||
return is_array($v) ? $v : [];
|
||||
};
|
||||
|
||||
$controller = \AKSRT\Admin\UserController::get_instance();
|
||||
|
||||
// 1) GET /wechat/status 返回完整状态形状(密钥掩码)。
|
||||
$response = $controller->get_wechat_status(new WP_REST_Request());
|
||||
$data = is_array($response->data) ? ($response->data['data'] ?? []) : [];
|
||||
$check('status 含 enabled', array_key_exists('enabled', $data) && !empty($data['enabled']));
|
||||
$check('status 含 appid', ($data['appid'] ?? '') === 'wxAPPID');
|
||||
$check('status configured', !empty($data['configured']));
|
||||
$check('status has_token', !empty($data['has_token']));
|
||||
$check('status has_template', !empty($data['has_template']));
|
||||
$check('status bound_users', ($data['bound_users'] ?? 0) === 7);
|
||||
$check('status 掩码 app_secret', ($data['app_secret'] ?? '') === '***');
|
||||
$check('status allowed_types 非空', !empty($data['allowed_types']));
|
||||
|
||||
// 2) 完整更新写入全部字段。
|
||||
$response = $controller->update_wechat_settings(new WP_REST_Request([
|
||||
'enabled' => true,
|
||||
'app_id' => 'wxNEW',
|
||||
'app_secret' => 'NEWSECRET',
|
||||
'template_id' => 'TPL2',
|
||||
]));
|
||||
$check('更新写入 app_id', ($stored()['app_id'] ?? '') === 'wxNEW');
|
||||
$check('更新写入 app_secret', ($stored()['app_secret'] ?? '') === 'NEWSECRET');
|
||||
$check('更新写入 template_id', ($stored()['template_id'] ?? '') === 'TPL2');
|
||||
$check('更新返回成功消息', str_contains((string) ($response->data['message'] ?? ''), '已保存'));
|
||||
|
||||
// 3) 掩码 *** 保留已存密钥。
|
||||
$controller->update_wechat_settings(new WP_REST_Request([
|
||||
'enabled' => true,
|
||||
'app_id' => 'wxNEW',
|
||||
'app_secret' => '***',
|
||||
]));
|
||||
$check('掩码保留原密钥', ($stored()['app_secret'] ?? '') === 'NEWSECRET');
|
||||
|
||||
// 4) 缺省字段沿用当前值,开关可关闭。
|
||||
$controller->update_wechat_settings(new WP_REST_Request(['enabled' => false]));
|
||||
$check('缺省字段沿用当前值', ($stored()['app_id'] ?? '') === 'wxNEW' && ($stored()['template_id'] ?? '') === 'TPL2');
|
||||
$check('enabled 可关闭', empty($stored()['enabled']));
|
||||
|
||||
// 5) 输入消毒 + 未知键被白名单丢弃。
|
||||
$controller->update_wechat_settings(new WP_REST_Request([
|
||||
'app_id' => '<b>wxXSS</b>',
|
||||
'evil' => 'x',
|
||||
]));
|
||||
$check('app_id 经消毒', ($stored()['app_id'] ?? '') === 'wxXSS');
|
||||
$check('未知键被忽略', !array_key_exists('evil', $stored()));
|
||||
|
||||
// 6) 未配置时空密钥可显式清空。
|
||||
update_option('aksrt_wechat_settings', ['enabled' => true, 'app_id' => 'a', 'app_secret' => '', 'template_id' => '']);
|
||||
$controller->update_wechat_settings(new WP_REST_Request(['app_secret' => '']));
|
||||
$check('空密钥可清存', ($stored()['app_secret'] ?? '') === '');
|
||||
|
||||
if ($failures) {
|
||||
echo "WeChat settings harness FAILED\n";
|
||||
foreach ($failures as $failure) {
|
||||
echo ' - ' . $failure . "\n";
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "WeChat settings harness passed\n";
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* 微信模板消息 REST 契约(修复存量审计错误):
|
||||
* 1) /wechat/status 与 /wechat/settings 的路由回调方法在 UserController 中真实存在;
|
||||
* 2) 状态读取委托 WeChatNotify::get_status(),写入委托 Settings::update_wechat_settings();
|
||||
* 3) app_secret 掩码 *** 不覆盖已存密钥,输入经 sanitize_text_field 消毒;
|
||||
* 4) PHP harness 驱动真实控制器验证上述行为;
|
||||
* 5) aksrt_settings 上的微信通知死键(wechat_notify_* 等)已彻底移除,
|
||||
* 活配置面仅剩 aksrt_wechat_settings(/users/wechat-template 自定义页)。
|
||||
*/
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const { execFileSync } = require('node:child_process');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
|
||||
const root = path.resolve(__dirname, '..', '..');
|
||||
const controller = fs.readFileSync(path.join(root, 'src/Admin/UserController.php'), 'utf8');
|
||||
const schemaUsers = fs.readFileSync(path.join(root, 'src/Admin/Schema/SchemaUsers.php'), 'utf8');
|
||||
const usersSchemaIndex = fs.readFileSync(path.join(root, 'assets/admin/src/schema/sections/users/index.js'), 'utf8');
|
||||
const usersSections = fs.readFileSync(path.join(root, 'assets/admin/src/constants/sections/users.js'), 'utf8');
|
||||
|
||||
// 死双路面:写进 aksrt_settings、全仓无消费方的微信通知键。
|
||||
const DEAD_WECHAT_KEYS = [
|
||||
'wechat_notify_enabled',
|
||||
'wechat_app_id',
|
||||
'wechat_app_secret',
|
||||
'wechat_template_id',
|
||||
'wechat_token',
|
||||
'wechat_encoding_aes_key',
|
||||
'wechat_template_comment',
|
||||
'wechat_template_reply',
|
||||
'wechat_template_system',
|
||||
'wechat_template_order',
|
||||
'wechat_notify_comment',
|
||||
'wechat_notify_reply',
|
||||
'wechat_notify_system',
|
||||
'wechat_notify_order',
|
||||
'wechat_bind_entry',
|
||||
];
|
||||
|
||||
test('路由与回调方法配对存在', () => {
|
||||
assert.match(controller, /register_rest_route\(self::API_NAMESPACE, '\/wechat\/status'[\s\S]*?'callback'\s*=>\s*\[\$this, 'get_wechat_status'\]/);
|
||||
assert.match(controller, /register_rest_route\(self::API_NAMESPACE, '\/wechat\/settings'[\s\S]*?'callback'\s*=>\s*\[\$this, 'update_wechat_settings'\]/);
|
||||
assert.match(controller, /public function get_wechat_status\(\\WP_REST_Request \$request\)/);
|
||||
assert.match(controller, /public function update_wechat_settings\(\\WP_REST_Request \$request\)/);
|
||||
});
|
||||
|
||||
test('状态与写入委托既有服务层', () => {
|
||||
assert.match(controller, /WeChatNotify::get_instance\(\)->get_status\(\)/);
|
||||
assert.match(controller, /Settings::update_wechat_settings\(/);
|
||||
});
|
||||
|
||||
test('密钥掩码与输入消毒', () => {
|
||||
assert.match(controller, /\$params\['app_secret'\] !== '\*\*\*'/);
|
||||
assert.match(controller, /sanitize_text_field\(\(string\) \$params\['app_id'\]\)/);
|
||||
assert.match(controller, /sanitize_text_field\(\(string\) \$params\['app_secret'\]\)/);
|
||||
assert.match(controller, /sanitize_text_field\(\(string\) \$params\['template_id'\]\)/);
|
||||
});
|
||||
|
||||
test('微信设置行为 harness 通过', () => {
|
||||
const harness = path.join(root, 'tests', 'admin', 'wechat-settings-harness.php');
|
||||
const output = execFileSync('php', [harness], { encoding: 'utf8' });
|
||||
assert.match(output, /WeChat settings harness passed/);
|
||||
});
|
||||
|
||||
test('死双路面(aksrt_settings 微信通知键)已彻底移除', () => {
|
||||
for (const key of DEAD_WECHAT_KEYS) {
|
||||
assert.ok(!schemaUsers.includes(key), `SchemaUsers 残留: ${key}`);
|
||||
assert.ok(!usersSections.includes(key), `users 菜单常量残留: ${key}`);
|
||||
}
|
||||
assert.ok(!usersSchemaIndex.includes('wechatSchema'), 'users schema index 残留 wechatSchema 注册');
|
||||
assert.ok(!usersSections.includes("route: '/users/wechat',"), '用户菜单残留微信通知 schema 页');
|
||||
assert.ok(!fs.existsSync(path.join(root, 'assets/admin/src/schema/sections/users/wechat.js')), 'wechat.js 未删除');
|
||||
});
|
||||
|
||||
test('活配置面保持唯一(aksrt_wechat_settings + 自定义页)', () => {
|
||||
assert.match(usersSections, /key: 'users-wechat-template'/);
|
||||
assert.match(usersSections, /route: '\/users\/wechat-template'/);
|
||||
assert.match(controller, /WeChatNotify::get_instance\(\)->get_status\(\)/);
|
||||
assert.match(controller, /Settings::update_wechat_settings\(/);
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generated_at": "2026-10-03T20:06:15+00:00",
|
||||
"generated_at": "2026-10-13T12:53:18+00:00",
|
||||
"errors": [],
|
||||
"warnings": [
|
||||
"customPage tools-update section 'tools/update' not in PHP schema (ok if pure operational page)",
|
||||
@@ -45,6 +45,8 @@
|
||||
"enum incomplete parse for 'search_type_in' (php subset of js): php=[post] js=[post,user]",
|
||||
"enum incomplete parse for 'sign_card_position' (php subset of js): php=[left] js=[left,center,right]",
|
||||
"enum incomplete parse for 'sign_theme_mode' (php subset of js): php=[no] js=[no,white-theme,dark-theme]",
|
||||
"enum incomplete parse for 'sms_ali_api_type' (php subset of js): php=[sms] js=[sms,verify]",
|
||||
"enum incomplete parse for 'sms_sdk' (php subset of js): php=[null] js=[null,ali,tencent,smsbao]",
|
||||
"enum incomplete parse for 'style' (php subset of js): php=[null] js=[null,style2]",
|
||||
"enum incomplete parse for 'system_notice_btn1_color' (php subset of js): php=[primary] js=[primary,success,warning,danger,info,purple,dark]",
|
||||
"enum incomplete parse for 'system_notice_btn2_color' (php subset of js): php=[primary] js=[primary,success,warning,danger,info,purple,dark]",
|
||||
@@ -57,12 +59,12 @@
|
||||
"enum incomplete parse for 'user_sign_type' (php subset of js): php=[modal] js=[modal,page]",
|
||||
"enum incomplete parse for 'user_signin_nopas_active' (php subset of js): php=[nopas] js=[nopas,pas,only_nopas]",
|
||||
"enum incomplete parse for 'wechat_payment_type' (php subset of js): php=[native] js=[native,h5]",
|
||||
"enum incomplete parse for 'weixingzh_gzh_type' (php subset of js): php=[service] js=[service,subscribe]",
|
||||
"assets/admin/build/ missing — run: cd assets/admin && npm run build"
|
||||
"enum incomplete parse for 'weixingzh_gzh_type' (php subset of js): php=[service] js=[service,subscribe]"
|
||||
],
|
||||
"info": [
|
||||
"PHP schema domains: global, page_display, post_list, permissions, users, payment, extensions, tools, ExtensionsAudit, ExtensionsIP, ExtensionsPush, ToolsBackup, ToolsHealth, ToolsImportExport",
|
||||
"Menu pages discovered: 60"
|
||||
"PHP schema domains: global, page_display, post_list, permissions, users, payment, extensions, tools, ExtensionsAudit, ExtensionsIP, ExtensionsPush, ExtensionsSecurity, ExtensionsSystem, ToolsBackup, ToolsHealth, ToolsImportExport",
|
||||
"Menu pages discovered: 61",
|
||||
"admin build ok; chunks=30"
|
||||
],
|
||||
"domains": {
|
||||
"global": {
|
||||
@@ -136,8 +138,7 @@
|
||||
"oauth",
|
||||
"report",
|
||||
"sms",
|
||||
"verify",
|
||||
"wechat"
|
||||
"verify"
|
||||
],
|
||||
"files": [
|
||||
"src/Admin/Schema/SchemaUsers.php"
|
||||
@@ -146,6 +147,7 @@
|
||||
"payment": {
|
||||
"sections": [
|
||||
"affiliate",
|
||||
"after_sale",
|
||||
"gateway",
|
||||
"knowledge",
|
||||
"recharge",
|
||||
@@ -161,7 +163,9 @@
|
||||
"sections": [
|
||||
"audit",
|
||||
"ip",
|
||||
"push"
|
||||
"push",
|
||||
"security",
|
||||
"system"
|
||||
],
|
||||
"files": [
|
||||
"src/Admin/Schema/SchemaExtensions.php"
|
||||
@@ -201,6 +205,22 @@
|
||||
"src/Admin/Schema/SchemaExtensionsPush.php"
|
||||
]
|
||||
},
|
||||
"ExtensionsSecurity": {
|
||||
"sections": [
|
||||
"security"
|
||||
],
|
||||
"files": [
|
||||
"src/Admin/Schema/SchemaExtensionsSecurity.php"
|
||||
]
|
||||
},
|
||||
"ExtensionsSystem": {
|
||||
"sections": [
|
||||
"system"
|
||||
],
|
||||
"files": [
|
||||
"src/Admin/Schema/SchemaExtensionsSystem.php"
|
||||
]
|
||||
},
|
||||
"ToolsBackup": {
|
||||
"sections": [
|
||||
"backup"
|
||||
@@ -226,11 +246,42 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"page_count": 60,
|
||||
"page_count": 61,
|
||||
"build": {
|
||||
"present": false,
|
||||
"entry": false,
|
||||
"present": true,
|
||||
"entry": true,
|
||||
"missing_chunks": [],
|
||||
"chunks": []
|
||||
"chunks": [
|
||||
"SchemaPage.BrFz57tJ.js",
|
||||
"vendor-vue.BqIbVHit.js",
|
||||
"FaIcon.BMA0TNWc.js",
|
||||
"AksMediaField.C9RRDn2V.js",
|
||||
"FaIconPicker.CA3w9r30.js",
|
||||
"shared-api.Cak1GAri.js",
|
||||
"vendor-misc.Dr7TL_YE.js",
|
||||
"vendor-draggable.CjYjXbZD.js",
|
||||
"level.E3ANSDAX.js",
|
||||
"badge.CKTsmH1z.js",
|
||||
"vendor-naive.Dzr6v856.js",
|
||||
"DeferredPage.Bo6-Z82H.js",
|
||||
"NativeOpsRedirect.wMMcdVjx.js",
|
||||
"FloatingButtonsSettings.DM1NXEcr.js",
|
||||
"PageHeader.UhySXCpq.js",
|
||||
"FooterTabbarSettings.8D3P8zNv.js",
|
||||
"PostPageSettings.B5Sm-lcm.js",
|
||||
"CommentSettings.DiQrTo4p.js",
|
||||
"PermissionsCapabilities.CnQfjtxC.js",
|
||||
"CmsCredits.Dh2b9uHb.js",
|
||||
"ThemeInfoPage.zYktV7qz.js",
|
||||
"ThemeAuthorizationPage.e-ksjN6X.js",
|
||||
"DocsPage.Dtd_-3fP.js",
|
||||
"UsersVerify.CAJUfACd.js",
|
||||
"UsersLevel.CBoqr3ux.js",
|
||||
"UsersBadge.B_lwRfdl.js",
|
||||
"UsersWechatTemplate.F0aCC5C-.js",
|
||||
"ToolsImportExport.CN30A6Ge.js",
|
||||
"ToolsHealth.B0GkAdJR.js",
|
||||
"ToolsUpdate.DHtpNLfB.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user