fix(creator-studio): 修复白屏与前台 REST 鉴权

白屏根因:CreatorRest / SubmissionVersion 位于 Jio\Core 命名空间,
但调用 Jio\Content\PrivateAssets 时缺少 use 导入,PHP 就近解析为不存在的
Jio\Core\PrivateAssets,在 wp_head 中抛致命错误导致页面输出截断。

- 为 CreatorRest、SubmissionVersion 补上 use Jio\Content\PrivateAssets
- 新增前台 api-fetch nonce 中间件:WordPress 只在后台注入 wpApiSettings,
  前台工作台里 Gutenberg core-data 请求(/wp/v2/users/me)因此全部 403
- 提交前先落一次草稿,避免自动保存未触发时发布到旧内容
- 新增 tools/check-class-refs.php:静态检测命名空间内未导入的裸类引用
- 新增 assets/creator-studio/scripts/smoke.cjs:浏览器冒烟脚本,
  抓控制台错误、失败请求与未捕获的 Promise 拒绝

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Opus 5 committed 2026-11-06 15:39:10 +08:00
1 parent fac4a714e0
commit 2c7262770b
10 files changed
+262 -36

No files matched your search

+1 -1
View File
@@ -1 +1 @@
<?php return array('dependencies' => array('react', 'wp-block-editor', 'wp-block-library', 'wp-blocks', 'wp-data', 'wp-element', 'wp-i18n', 'wp-wordcount'), 'version' => '9f8085a073c5e67a84ed');
<?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-block-editor', 'wp-block-library', 'wp-blocks', 'wp-data', 'wp-element', 'wp-i18n', 'wp-wordcount'), 'version' => 'd48b7a862f5ed6e6e859');
+1 -1
View File
@@ -1 +1 @@
(()=>{"use strict";const e=window.React,t=window.wp.element,a=window.wp.blocks,r=window.wp.blockLibrary,n=window.wp.data,o=window.wp.blockEditor,i=window.wp.wordcount,l=window.wp.i18n;function c(e,t){const a=e&&(e.message||e.error)||(401===t?"登录状态已失效,请刷新页面重新登录。":"请求失败,请稍后重试。"),r=new Error(String(a));return r.status=t,r.code=e&&e.code?String(e.code):"",r.data=e,r}async function s(e){try{return await e.json()}catch(e){return null}}async function m(e,t,a){const r=await fetch(e,{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json","X-WP-Nonce":a},body:JSON.stringify(t)}),n=await s(r);if(!r.ok)throw c(n,r.status);return n}async function u(e,t,a,r={}){const n=new FormData;n.append("file",t),Object.entries(r).forEach(([e,t])=>n.append(e,t));const o=await fetch(e,{method:"POST",credentials:"same-origin",headers:{"X-WP-Nonce":a},body:n}),i=await s(o);if(!o.ok)throw c(i,o.status);return i}const d=window.jioCreatorStudio||{},p=d.nonce||"";function _(a){const{draft:r,setDraft:n,capabilities:o,limits:i,taxonomy:c,isPublishedEdit:s,onClose:m,onSaveDraft:_,onSubmit:y,privateStorage:f,privateUploads:b,saving:E,captcha:g}=a,[j,h]=(0,t.useState)(""),[w,v]=(0,t.useState)(""),[N,k]=(0,t.useState)(!1),S=(0,t.useRef)(null),C=(0,t.useRef)(null),x=(0,t.useRef)(null);(0,t.useEffect)(()=>{const e=x.current,t=document.getElementById("jio-creator-captcha");if(!e||!t)return;const a=t.parentElement;return e.appendChild(t),t.removeAttribute("hidden"),()=>{a&&(a.appendChild(t),t.setAttribute("hidden",""))}},[]);const I=(0,t.useMemo)(()=>(r.title||"").trim().length,[r.title])>=Number(i.titleMin||1),L=()=>{const e=j.trim();if(!e)return;const t=e.split(/[,,\n]+/u).map(e=>e.trim()).filter(Boolean);n({tags:Array.from(new Set([...r.tags,...t]))}),h("")};return(0,e.createElement)("div",{className:"jio-creator-drawer",role:"dialog","aria-modal":"true","aria-labelledby":"jio-creator-publish-title"},(0,e.createElement)("button",{type:"button",className:"jio-creator-drawer__backdrop","aria-label":(0,l.__)("关闭","jio"),onClick:m}),(0,e.createElement)("div",{className:"jio-creator-drawer__panel"},(0,e.createElement)("header",{className:"jio-creator-drawer__head"},(0,e.createElement)("h2",{id:"jio-creator-publish-title"},s?(0,l.__)("提交改稿","jio"):(0,l.__)("发布文章","jio")),(0,e.createElement)("button",{type:"button",className:"jio-creator-drawer__close",onClick:m,"aria-label":(0,l.__)("关闭","jio")},(0,e.createElement)("i",{className:"fa fas fa-times","aria-hidden":"true"}))),(0,e.createElement)("div",{className:"jio-creator-drawer__body"},s&&(0,e.createElement)("p",{className:"jio-creator-drawer__note"},(0,l.__)("审核通过前,读者看到的仍然是当前线上版本。","jio")),(0,e.createElement)("section",{className:"jio-creator-field"},(0,e.createElement)("h3",null,(0,l.__)("封面","jio")),r.featured?.data?.url?(0,e.createElement)("div",{className:"jio-creator-cover"},(0,e.createElement)("img",{src:String(r.featured.data.url),alt:""}),(0,e.createElement)("button",{type:"button",onClick:()=>n({featured:{type:"",data:{}}}),"aria-label":(0,l.__)("移除封面","jio")},(0,e.createElement)("i",{className:"fa fas fa-times","aria-hidden":"true"}))):o.canUploadImage?(0,e.createElement)("label",{className:"jio-creator-cover-empty"},(0,e.createElement)("input",{ref:S,type:"file",accept:"image/*",onChange:()=>{(async()=>{const e=S.current?.files?.[0];if(e&&d.media?.uploadUrl?.image){v(""),k(!0);try{const t=await u(d.media.uploadUrl.image,e,p),a=Number(t.attachment_id||t.id||0),r=String(t.url||"");if(!a||!r)throw new Error((0,l.__)("上传后无法读取图片。","jio"));n({featured:{type:"image",mediaId:a,data:{id:a,url:r}}})}catch(e){v(String(e?.message||(0,l.__)("上传失败","jio")))}finally{k(!1)}}})()},disabled:N}),(0,e.createElement)("i",{className:"fa fas fa-plus","aria-hidden":"true"}),(0,e.createElement)("span",null,N?(0,l.__)("上传中…","jio"):(0,l.__)("上传封面","jio"))):(0,e.createElement)("p",{className:"jio-creator-muted"},(0,l.__)("当前账号不能设置封面。","jio"))),(0,e.createElement)("section",{className:"jio-creator-field"},(0,e.createElement)("h3",null,(0,l.__)("分类","jio")),(0,e.createElement)("div",{className:"jio-creator-chips"},0===c.categories.length&&(0,e.createElement)("span",{className:"jio-creator-muted"},(0,l.__)("暂无可选分类","jio")),c.categories.map(t=>(0,e.createElement)("label",{key:t.id,className:"jio-creator-chip"+(r.categories.includes(t.id)?" is-active":"")},(0,e.createElement)("input",{type:"checkbox",checked:r.categories.includes(t.id),onChange:()=>(e=>{const t=r.categories.includes(e);n({categories:t?r.categories.filter(t=>t!==e):[...r.categories,e]})})(t.id)}),(0,e.createElement)("span",null,t.name))))),c.tags&&(0,e.createElement)("section",{className:"jio-creator-field"},(0,e.createElement)("h3",null,(0,l.__)("标签","jio")),(0,e.createElement)("div",{className:"jio-creator-Line truncated
(()=>{"use strict";const e={n:t=>{const a=t&&t.__esModule?()=>t.default:()=>t;return e.d(a,{a}),a},d:(t,a)=>{for(var r in a)e.o(a,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:a[r]})},o:(e,t)=>Object.hasOwn(e,t)},t=window.React,a=window.wp.element,r=window.wp.blocks,n=window.wp.blockLibrary,o=window.wp.data,i=window.wp.blockEditor,c=window.wp.wordcount,l=window.wp.i18n;function s(e,t){const a=e&&(e.message||e.error)||(401===t?"登录状态已失效,请刷新页面重新登录。":"请求失败,请稍后重试。"),r=new Error(String(a));return r.status=t,r.code=e&&e.code?String(e.code):"",r.data=e,r}async function m(e){try{return await e.json()}catch(e){return null}}async function u(e,t,a){const r=await fetch(e,{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json","X-WP-Nonce":a},body:JSON.stringify(t)}),n=await m(r);if(!r.ok)throw s(n,r.status);return n}async function d(e,t,a,r={}){const n=new FormData;n.append("file",t),Object.entries(r).forEach(([e,t])=>n.append(e,t));const o=await fetch(e,{method:"POST",credentials:"same-origin",headers:{"X-WP-Nonce":a},body:n}),i=await m(o);if(!o.ok)throw s(i,o.status);return i}const p=window.wp.apiFetch;var _=e.n(p);const f=window.jioCreatorStudio||{},y=f.nonce||"";function b(e){const{draft:r,setDraft:n,capabilities:o,limits:i,taxonomy:c,isPublishedEdit:s,onClose:m,onSaveDraft:u,onSubmit:p,privateStorage:_,privateUploads:b,saving:E,captcha:g}=e,[j,w]=(0,a.useState)(""),[h,v]=(0,a.useState)(""),[N,k]=(0,a.useState)(!1),S=(0,a.useRef)(null),C=(0,a.useRef)(null),I=(0,a.useRef)(null);(0,a.useEffect)(()=>{const e=I.current,t=document.getElementById("jio-creator-captcha");if(!e||!t)return;const a=t.parentElement;return e.appendChild(t),t.removeAttribute("hidden"),()=>{a&&(a.appendChild(t),t.setAttribute("hidden",""))}},[]);const x=(0,a.useMemo)(()=>(r.title||"").trim().length,[r.title])>=Number(i.titleMin||1),L=()=>{const e=j.trim();if(!e)return;const t=e.split(/[,,\n]+/u).map(e=>e.trim()).filter(Boolean);n({tags:Array.from(new Set([...r.tags,...t]))}),w("")};return(0,t.createElement)("div",{className:"jio-creator-drawer",role:"dialog","aria-modal":"true","aria-labelledby":"jio-creator-publish-title"},(0,t.createElement)("button",{type:"button",className:"jio-creator-drawer__backdrop","aria-label":(0,l.__)("关闭","jio"),onClick:m}),(0,t.createElement)("div",{className:"jio-creator-drawer__panel"},(0,t.createElement)("header",{className:"jio-creator-drawer__head"},(0,t.createElement)("h2",{id:"jio-creator-publish-title"},s?(0,l.__)("提交改稿","jio"):(0,l.__)("发布文章","jio")),(0,t.createElement)("button",{type:"button",className:"jio-creator-drawer__close",onClick:m,"aria-label":(0,l.__)("关闭","jio")},(0,t.createElement)("i",{className:"fa fas fa-times","aria-hidden":"true"}))),(0,t.createElement)("div",{className:"jio-creator-drawer__body"},s&&(0,t.createElement)("p",{className:"jio-creator-drawer__note"},(0,l.__)("审核通过前,读者看到的仍然是当前线上版本。","jio")),(0,t.createElement)("section",{className:"jio-creator-field"},(0,t.createElement)("h3",null,(0,l.__)("封面","jio")),r.featured?.data?.url?(0,t.createElement)("div",{className:"jio-creator-cover"},(0,t.createElement)("img",{src:String(r.featured.data.url),alt:""}),(0,t.createElement)("button",{type:"button",onClick:()=>n({featured:{type:"",data:{}}}),"aria-label":(0,l.__)("移除封面","jio")},(0,t.createElement)("i",{className:"fa fas fa-times","aria-hidden":"true"}))):o.canUploadImage?(0,t.createElement)("label",{className:"jio-creator-cover-empty"},(0,t.createElement)("input",{ref:S,type:"file",accept:"image/*",onChange:()=>{(async()=>{const e=S.current?.files?.[0];if(e&&f.media?.uploadUrl?.image){v(""),k(!0);try{const t=await d(f.media.uploadUrl.image,e,y),a=Number(t.attachment_id||t.id||0),r=String(t.url||"");if(!a||!r)throw new Error((0,l.__)("上传后无法读取图片。","jio"));n({featured:{type:"image",mediaId:a,data:{id:a,url:r}}})}catch(e){v(String(e?.message||(0,l.__)("上传失败","jio")))}finally{k(!1)}}})()},disabled:N}),(0,t.createElement)("i",{className:"fa fas fa-plus","aria-hidden":"true"}),(0,t.createElement)("span",null,N?(0,l.__)("上传中…","jio"):(0,l.__)("上传封面","jio"))):(0,t.createElement)("p",{className:"jio-creator-muted"},(0,l.__)("当前账号不能设置封面。","jio"))),(0,t.createElement)("section",{className:"jio-creator-field"},(0,t.createElement)("h3",null,(0,l.__)("分类","jio")),(0,t.createElement)("div",{className:"jio-creator-chips"},0===c.categories.length&&(0,t.createElement)("span",{className:"jio-creator-muted"},(0,l.__)("暂无可选分类","jio")),c.categories.map(e=>(0,t.createElement)("label",{key:e.id,className:"jio-creator-chip"+(r.categories.includes(e.id)?" is-active":"")},(0,t.createElement)("input",{type:"checkbox",checked:r.categories.includes(e.id),onChange:()=>(e=>{const t=r.categories.includes(e);n({categories:t?r.categories.filter(t=>t!==e):[...r.cateLine truncated
+51 -33
View File
@@ -10,6 +10,7 @@
"devDependencies": {
"@types/react": "18.3.31",
"@types/react-dom": "18.3.7",
"@wordpress/api-fetch": "^6.28.0",
"@wordpress/block-editor": "11.8.0",
"@wordpress/block-library": "^8.8.0",
"@wordpress/blocks": "12.8.0",
@@ -4862,48 +4863,20 @@
"license": "MIT"
},
"node_modules/@wordpress/api-fetch": {
"version": "6.55.0",
"resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.55.0.tgz",
"integrity": "sha512-1HrCUsJdeRY5Y0IjplotINwqMRO81e7O7VhBScuKk7iOuDm/E1ioKv2uLGnPNWziYu+Zf025byxOqVzXDyM2gw==",
"version": "6.28.0",
"resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.28.0.tgz",
"integrity": "sha512-kEe8zqjEs1paZAS0ZSaUkiLTIrNFBy11ZkdBDYOYgl707zZCQt50HYs/J+zATWegWpp5scd4Tt18cJy1O9dosg==",
"dev": true,
"license": "GPL-2.0-or-later",
"dependencies": {
"@babel/runtime": "^7.16.0",
"@wordpress/i18n": "^4.58.0",
"@wordpress/url": "^3.59.0"
"@wordpress/i18n": "^4.31.0",
"@wordpress/url": "^3.32.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@wordpress/api-fetch/node_modules/@wordpress/i18n": {
"version": "4.58.0",
"resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.58.0.tgz",
"integrity": "sha512-VfvS3BWv/RDjRKD6PscIcvYfWKnGJcI/DEqyDgUMhxCM6NRwoL478CsUKTiGJIymeyRodNRfprdcF086DpGKYw==",
"dev": true,
"license": "GPL-2.0-or-later",
"dependencies": {
"@babel/runtime": "^7.16.0",
"@wordpress/hooks": "^3.58.0",
"gettext-parser": "^1.3.1",
"memize": "^2.1.0",
"sprintf-js": "^1.1.1",
"tannin": "^1.2.0"
},
"bin": {
"pot-to-php": "tools/pot-to-php.js"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@wordpress/api-fetch/node_modules/memize": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/memize/-/memize-2.1.1.tgz",
"integrity": "sha512-8Nl+i9S5D6KXnruM03Jgjb+LwSupvR13WBr4hJegaaEyobvowCVupi79y2WSiWvO1mzBWxPwEYE5feCe8vyA5w==",
"dev": true,
"license": "MIT"
},
"node_modules/@wordpress/autop": {
"version": "3.58.0",
"resolved": "https://registry.npmjs.org/@wordpress/autop/-/autop-3.58.0.tgz",
@@ -5786,6 +5759,21 @@
"react-dom": ">=16.8.0"
}
},
"node_modules/@wordpress/core-data/node_modules/@wordpress/api-fetch": {
"version": "6.55.0",
"resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.55.0.tgz",
"integrity": "sha512-1HrCUsJdeRY5Y0IjplotINwqMRO81e7O7VhBScuKk7iOuDm/E1ioKv2uLGnPNWziYu+Zf025byxOqVzXDyM2gw==",
"dev": true,
"license": "GPL-2.0-or-later",
"dependencies": {
"@babel/runtime": "^7.16.0",
"@wordpress/i18n": "^4.58.0",
"@wordpress/url": "^3.59.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@wordpress/core-data/node_modules/@wordpress/block-editor": {
"version": "12.26.0",
"resolved": "https://registry.npmjs.org/@wordpress/block-editor/-/block-editor-12.26.0.tgz",
@@ -6994,6 +6982,21 @@
"react-dom": ">=16.8.0"
}
},
"node_modules/@wordpress/reusable-blocks/node_modules/@wordpress/api-fetch": {
"version": "6.55.0",
"resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.55.0.tgz",
"integrity": "sha512-1HrCUsJdeRY5Y0IjplotINwqMRO81e7O7VhBScuKk7iOuDm/E1ioKv2uLGnPNWziYu+Zf025byxOqVzXDyM2gw==",
"dev": true,
"license": "GPL-2.0-or-later",
"dependencies": {
"@babel/runtime": "^7.16.0",
"@wordpress/i18n": "^4.58.0",
"@wordpress/url": "^3.59.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@wordpress/reusable-blocks/node_modules/@wordpress/block-editor": {
"version": "12.26.0",
"resolved": "https://registry.npmjs.org/@wordpress/block-editor/-/block-editor-12.26.0.tgz",
@@ -7714,6 +7717,21 @@
"react-dom": ">=16.8.0"
}
},
"node_modules/@wordpress/server-side-render/node_modules/@wordpress/api-fetch": {
"version": "6.55.0",
"resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.55.0.tgz",
"integrity": "sha512-1HrCUsJdeRY5Y0IjplotINwqMRO81e7O7VhBScuKk7iOuDm/E1ioKv2uLGnPNWziYu+Zf025byxOqVzXDyM2gw==",
"dev": true,
"license": "GPL-2.0-or-later",
"dependencies": {
"@babel/runtime": "^7.16.0",
"@wordpress/i18n": "^4.58.0",
"@wordpress/url": "^3.59.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@wordpress/server-side-render/node_modules/@wordpress/blocks": {
"version": "12.35.0",
"resolved": "https://registry.npmjs.org/@wordpress/blocks/-/blocks-12.35.0.tgz",
+1
View File
@@ -9,6 +9,7 @@
"devDependencies": {
"@types/react": "18.3.31",
"@types/react-dom": "18.3.7",
"@wordpress/api-fetch": "^6.28.0",
"@wordpress/block-editor": "11.8.0",
"@wordpress/block-library": "^8.8.0",
"@wordpress/blocks": "12.8.0",
+92
View File
@@ -0,0 +1,92 @@
/**
* Creator Studio 冒烟检查:用 puppeteer-core 连接本机浏览器,打开投稿页,
* 抓取控制台错误、失败请求与未捕获的 Promise 拒绝。
*
* 用法:
* node scripts/smoke.cjs [url]
* 浏览器路径可用环境变量覆盖(默认按常见位置探测 Chrome / Edge):
* JIO_BROWSER="/path/to/chrome" node scripts/smoke.cjs
*/
const fs = require('fs');
const puppeteer = require('puppeteer-core');
const CANDIDATES = [
process.env.JIO_BROWSER,
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe',
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
'/usr/bin/google-chrome',
'/usr/bin/chromium',
].filter(Boolean);
const EDGE = CANDIDATES.find((p) => fs.existsSync(p));
const URL = process.argv[2] || 'https://localhost/submit/';
if (!EDGE) {
console.error('未找到可用浏览器,请设置 JIO_BROWSER 环境变量指向 Chrome/Edge 可执行文件。');
process.exit(1);
}
(async () => {
const browser = await puppeteer.launch({
executablePath: EDGE,
headless: 'new',
ignoreHTTPSErrors: true,
args: ['--disable-gpu', '--no-sandbox'],
});
const page = await browser.newPage();
const events = [];
page.on('console', (m) => {
if (m.type() === 'error' || m.type() === 'warning') {
events.push(`[console.${m.type()}] ${m.text()}`);
}
});
page.on('pageerror', (e) => {
events.push(`[pageerror] ${e.message}\n stack: ${(e.stack || '').split('\n').slice(0, 6).join('\n ')}`);
});
page.on('response', async (r) => {
if (r.status() >= 400) {
let body = '';
try { body = (await r.text()).slice(0, 300); } catch (_) {}
events.push(`[http ${r.status()}] ${r.url()}\n body: ${body.replace(/\s+/g, ' ')}`);
}
});
await page.evaluateOnNewDocument(() => {
window.__jioRejections = [];
window.addEventListener('unhandledrejection', (e) => {
const r = e.reason;
window.__jioRejections.push(
r && r.status
? `status=${r.status} code=${r.code || ''} msg=${r.message}`
: String(r && r.message ? r.message : r)
);
});
window.addEventListener('error', (e) => {
window.__jioRejections.push(`onerror: ${e.message} @ ${e.filename}:${e.lineno}`);
});
});
await page.goto(URL, { waitUntil: 'networkidle2', timeout: 60000 });
await new Promise((r) => setTimeout(r, 3000));
const state = await page.evaluate(() => ({
hasBar: !!document.querySelector('.jio-creator__bar'),
hasCanvas: !!document.querySelector('.jio-creator__canvas'),
appender: !!document.querySelector('.block-editor-default-block-appender'),
blocks: document.querySelectorAll('.block-editor-block-list__block').length,
saveText: (document.querySelector('.jio-creator__save') || {}).textContent,
rejections: window.__jioRejections || [],
}));
console.log('STATE ' + JSON.stringify({ ...state, rejections: undefined }));
if (state.rejections.length) {
console.log('--- rejections ---');
state.rejections.slice(0, 10).forEach((r) => console.log(r));
}
console.log('--- events ---');
events.slice(0, 30).forEach((e) => console.log(e));
await browser.close();
})();
+10 -1
View File
@@ -17,6 +17,7 @@ import { count as countWords } from '@wordpress/wordcount';
import { __ } from '@wordpress/i18n';
import { apiPost } from './services/api';
import { installApiFetchNonce } from './services/api-fetch';
import { installMediaUploadBridge } from './services/media';
import { PublishDrawer } from './panels/publish-drawer';
import { SaveState, type SaveStateValue } from './components/save-state';
@@ -183,6 +184,13 @@ function Studio(): JSX.Element {
const submit = useCallback(
async (publishNow: boolean): Promise<void> => {
// 提交前先落一次草稿:审核 / 立即发布都以服务端最终保存的内容为准,
// 避免自动保存尚未触发时提交到旧内容。
const persisted = await save(false);
if (!persisted) {
setNotice({ tone: 'error', text: __('内容保存失败,请重试后再提交', 'jio') });
return;
}
setSaveState({ state: 'submitting', at: '' });
try {
// 站点启用「前台投稿」人机验证时,先完成挑战再提交;
@@ -218,7 +226,7 @@ function Studio(): JSX.Element {
setNotice({ tone: 'error', text: String(error?.message || __('提交失败', 'jio')) });
}
},
[buildPayload]
[buildPayload, save]
);
const preview = useCallback(async (): Promise<void> => {
@@ -436,6 +444,7 @@ function mount() {
if (!target) {
return;
}
installApiFetchNonce(nonce);
installMediaUploadBridge(bootstrap, nonce);
target.classList.add('is-ready');
createRoot(target).render(<Studio />);
@@ -0,0 +1,33 @@
/**
* 给 @wordpress/api-fetch 注入 REST nonce。
*
* WordPress 仅在后台为 wp-api 注入 wpApiSettings(含 nonce),前台工作台里
* Gutenberg 的 core-data 请求(如 /wp/v2/users/me)因此全部 403。这里统一挂一个
* nonce 中间件,让区块编辑器的数据请求与 Jio REST 使用同一次页面加载的 nonce。
*
* @package Jio
*/
import apiFetch from '@wordpress/api-fetch';
export function installApiFetchNonce(nonce: string): void {
if (!nonce || typeof apiFetch.use !== 'function') {
return;
}
if ((window as any).__jioApiFetchNonceInstalled) {
return;
}
(window as any).__jioApiFetchNonceInstalled = true;
// createNonceMiddleware 在收到 rest_cookie_invalid_nonce 时会带着 nonce 重试,
// 比在每次请求上手工加 header 更稳妥。
const factory = (apiFetch as any).createNonceMiddleware;
if (typeof factory === 'function') {
apiFetch.use(factory(nonce));
return;
}
apiFetch.use((options: any, next: any) => {
options.headers = { ...(options.headers || {}), 'X-WP-Nonce': nonce };
return next(options);
});
}
+2
View File
@@ -11,6 +11,8 @@
namespace Jio\Core;
use Jio\Content\PrivateAssets;
defined('ABSPATH') || exit;
final class CreatorRest {
+2
View File
@@ -14,6 +14,8 @@
namespace Jio\Core;
use Jio\Content\PrivateAssets;
defined('ABSPATH') || exit;
final class SubmissionVersion {
+69
View File
@@ -0,0 +1,69 @@
<?php
define('ABSPATH', 'C:/Users/Xing/Desktop/AKSRT/Jio/');
require 'C:/Users/Xing/Desktop/AKSRT/Jio/vendor/autoload.php';
$classes = [
'Jio\Core\CreatorRest',
'Jio\Core\CreatorStudio',
'Jio\Core\SubmissionVersion',
'Jio\Core\PreviewToken',
'Jio\Content\PrivateAssets',
'Jio\Content\ProtectedDownload',
'Jio\Admin\Native\SubmissionReview',
];
$bad = 0;
foreach ($classes as $c) {
$f = (new ReflectionClass($c))->getFileName();
$src = file_get_contents($f);
// 去掉注释与字符串,只保留可执行代码里的类引用。
$code = '';
foreach (token_get_all($src) as $tok) {
if (is_array($tok)) {
if (in_array($tok[0], [T_COMMENT, T_DOC_COMMENT, T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE], true)) {
continue;
}
$code .= $tok[1];
} else {
$code .= $tok;
}
}
$ns = '';
if (preg_match('/^namespace\s+([^;]+);/m', $src, $mm)) {
$ns = trim($mm[1], '\\');
}
$imports = [];
preg_match_all('/^use\s+([A-Za-z0-9_\\\\]+)(?:\s+as\s+([A-Za-z0-9_]+))?;/m', $code, $um, PREG_SET_ORDER);
foreach ($um as $u) {
$full = $u[1];
$short = (isset($u[2]) && $u[2] !== '') ? $u[2] : substr($full, strrpos($full, '\\') + 1);
$imports[$short] = true;
}
// 只匹配未加前导反斜杠的短类名;`\Foo\Bar::baz()` 是全限定引用,不受命名空间影响。
preg_match_all('/(?<![\\\\A-Za-z0-9_])([A-Z][A-Za-z0-9_]*)::/', $code, $m);
$own = substr($c, strrpos($c, '\\') + 1);
foreach (array_unique($m[1]) as $name) {
if (in_array($name, ['self', 'static', 'parent'], true) || $name === $own) {
continue;
}
if (isset($imports[$name])) {
continue;
}
$candidate = $ns . '\\' . $name;
if (class_exists($candidate) || interface_exists($candidate) || trait_exists($candidate)) {
continue;
}
if (class_exists($name) || interface_exists($name)) {
continue;
}
echo "MISSING in {$c}: {$name} (resolved as {$candidate})\n";
$bad++;
}
}
echo $bad === 0 ? "OK: no unresolved class references\n" : "FAILURES={$bad}\n";