diff --git a/build/validation-api.asset.php b/build/validation-api.asset.php index 438ceb3..f5b945c 100644 --- a/build/validation-api.asset.php +++ b/build/validation-api.asset.php @@ -1 +1 @@ - array('wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-plugins'), 'version' => 'ebffb00f91b03991c44b'); + array('wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-plugins'), 'version' => '8dd2b2d70b25cb6f7678'); diff --git a/build/validation-api.js b/build/validation-api.js index ada7d3d..d61743b 100644 --- a/build/validation-api.js +++ b/build/validation-api.js @@ -18,21 +18,21 @@ t = {}; (e.r(t), e.d(t, { - getBlockValidation: () => re, - getInvalidBlocks: () => Y, - getInvalidEditorChecks: () => te, - getInvalidMeta: () => ee, - hasErrors: () => ne, - hasWarnings: () => oe, + getBlockValidation: () => te, + getInvalidBlocks: () => X, + getInvalidEditorChecks: () => ee, + getInvalidMeta: () => Y, + hasErrors: () => re, + hasWarnings: () => ne, })); var r = {}; (e.r(r), e.d(r, { - clearBlockValidation: () => ue, - setBlockValidation: () => ce, - setInvalidBlocks: () => ie, - setInvalidEditorChecks: () => le, - setInvalidMeta: () => ae, + clearBlockValidation: () => ce, + setBlockValidation: () => le, + setInvalidBlocks: () => oe, + setInvalidEditorChecks: () => ae, + setInvalidMeta: () => ie, })); const n = window.wp.plugins, o = window.wp.data, @@ -146,8 +146,6 @@ message: n, errorMsg: o, warningMsg: i, - error_msg: o, - warning_msg: i, }, r ); @@ -162,107 +160,113 @@ for (var r = 0, n = Array(t); r < t; r++) n[r] = e[r]; return n; } - var w, - O = function (e) { - var t, - r = e.name, - n = e.attributes, - o = [], - i = - (null === (t = window.ValidationAPI) || - void 0 === t || - null === (t = t.validationRules) || - void 0 === t - ? void 0 - : t[r]) || {}; - if (0 === Object.keys(i).length) - return { isValid: !0, issues: [], mode: 'none', clientId: e.clientId, name: r }; - Object.entries(i).forEach(function (t) { - var i, - a, - l = - ((a = 2), - (function (e) { - if (Array.isArray(e)) return e; - })((i = t)) || - (function (e, t) { - var r = - null == e - ? null - : ('undefined' != typeof Symbol && e[Symbol.iterator]) || - e['@@iterator']; - if (null != r) { - var n, - o, - i, - a, - l = [], - c = !0, - u = !1; + var w = function (e) { + var t = e.name, + r = e.attributes, + n = [], + o = (O().validationRules || {})[t] || {}; + if (0 === Object.keys(o).length) + return { isValid: !0, issues: [], mode: 'none', clientId: e.clientId, name: t }; + Object.entries(o).forEach(function (o) { + var i, + a, + l = + ((a = 2), + (function (e) { + if (Array.isArray(e)) return e; + })((i = o)) || + (function (e, t) { + var r = + null == e + ? null + : ('undefined' != typeof Symbol && e[Symbol.iterator]) || + e['@@iterator']; + if (null != r) { + var n, + o, + i, + a, + l = [], + c = !0, + u = !1; + try { + if (((i = (r = r.call(e)).next), 0 === t)) { + if (Object(r) !== r) return; + c = !1; + } else + for ( + ; + !(c = (n = i.call(r)).done) && + (l.push(n.value), l.length !== t); + c = !0 + ); + } catch (e) { + ((u = !0), (o = e)); + } finally { try { - if (((i = (r = r.call(e)).next), 0 === t)) { - if (Object(r) !== r) return; - c = !1; - } else - for ( - ; - !(c = (n = i.call(r)).done) && - (l.push(n.value), l.length !== t); - c = !0 - ); - } catch (e) { - ((u = !0), (o = e)); + if ( + !c && + null != r.return && + ((a = r.return()), Object(a) !== a) + ) + return; } finally { - try { - if ( - !c && - null != r.return && - ((a = r.return()), Object(a) !== a) - ) - return; - } finally { - if (u) throw o; - } + if (u) throw o; } - return l; } - })(i, a) || - (function (e, t) { - if (e) { - if ('string' == typeof e) return h(e, t); - var r = {}.toString.call(e).slice(8, -1); - return ( - 'Object' === r && e.constructor && (r = e.constructor.name), - 'Map' === r || 'Set' === r - ? Array.from(e) - : 'Arguments' === r || - /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r) - ? h(e, t) - : void 0 - ); - } - })(i, a) || - (function () { - throw new TypeError( - 'Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.' + return l; + } + })(i, a) || + (function (e, t) { + if (e) { + if ('string' == typeof e) return h(e, t); + var r = {}.toString.call(e).slice(8, -1); + return ( + 'Object' === r && e.constructor && (r = e.constructor.name), + 'Map' === r || 'Set' === r + ? Array.from(e) + : 'Arguments' === r || + /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r) + ? h(e, t) + : void 0 ); - })()), - c = l[0], - u = l[1]; - if (v(u)) { - var s = !0; - ('function' == typeof u.validator && (s = u.validator(n, e)), - (s = (0, g.applyFilters)('validation_api_validate_block', s, r, n, c, e)) || - o.push(y(u, c))); - } - }); - var a = 'none'; - return ( - m(o) ? (a = 'error') : p(o) && (a = 'warning'), - b(o, { mode: a, clientId: e.clientId, name: r }) - ); - }; - function E(e, t) { + } + })(i, a) || + (function () { + throw new TypeError( + 'Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.' + ); + })()), + c = l[0], + u = l[1]; + if (v(u)) { + var s = !0; + ('function' == typeof u.validator && (s = u.validator(r, e)), + (s = (0, g.applyFilters)('editor.validateBlock', s, t, r, c, e)) || + n.push(y(u, c))); + } + }); + var i = 'none'; + return ( + m(n) ? (i = 'error') : p(n) && (i = 'warning'), + b(n, { mode: i, clientId: e.clientId, name: t }) + ); + }; + function O() { + try { + var e = (0, o.select)('core/editor').getEditorSettings(); + return (null == e ? void 0 : e.validationApi) || {}; + } catch (e) { + return {}; + } + } + function E() { + return O().metaValidationRules || {}; + } + function k() { + return O().editorContext || 'none'; + } + function S(e, t) { if (e) { if ('string' == typeof e) return j(e, t); var r = {}.toString.call(e).slice(8, -1); @@ -281,10 +285,10 @@ for (var r = 0, n = Array(t); r < t; r++) n[r] = e[r]; return n; } - function S(e) { + function P(e) { return e.flatMap(function (e) { var t, - r = O(e), + r = w(e), n = []; return ( r.isValid || n.push(r), @@ -293,7 +297,7 @@ n, (function (e) { if (Array.isArray(e)) return j(e); - })((t = S(e.innerBlocks))) || + })((t = P(e.innerBlocks))) || (function (e) { if ( ('undefined' != typeof Symbol && @@ -302,7 +306,7 @@ ) return Array.from(e); })(t) || - E(t) || + S(t) || (function () { throw new TypeError( 'Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.' @@ -313,12 +317,12 @@ ); }); } - function k(e) { + function R(e) { var t, r = (function (e) { var t = ('undefined' != typeof Symbol && e[Symbol.iterator]) || e['@@iterator']; if (!t) { - if (Array.isArray(e) || (t = E(e))) { + if (Array.isArray(e) || (t = S(e))) { t && (e = t); var _n = 0, r = function () {}; @@ -365,7 +369,7 @@ var n = t.value; if ('core/post-content' === n.name) return n; if (n.innerBlocks && n.innerBlocks.length > 0) { - var o = k(n.innerBlocks); + var o = R(n.innerBlocks); if (o) return o; } } @@ -376,26 +380,23 @@ } return null; } - function P() { - var e, - t = - (null === (e = window.ValidationAPI) || void 0 === e ? void 0 : e.editorContext) || - 'none', - r = 'post-editor' === t || 'post-editor-template' === t; - return S( + function I() { + var e = k(), + t = 'post-editor' === e || 'post-editor-template' === e; + return P( (0, o.useSelect)( function (e) { - var t = e('core/block-editor'), - n = t.getBlocks(); - if (r) { - var o = k(n); + var r = e('core/block-editor'), + n = r.getBlocks(); + if (t) { + var o = R(n); if (o) { - var i = t.getBlock(o.clientId), - a = t + var i = r.getBlock(o.clientId), + a = r .getBlockOrder(o.clientId) .map(function (e) { - var r = t.getBlock(e); - return (t.getBlockOrder(e), r); + var t = r.getBlock(e); + return (r.getBlockOrder(e), t); }) .filter(Boolean); return a.length > 0 ? a : (null == i ? void 0 : i.innerBlocks) || []; @@ -404,11 +405,11 @@ } return n; }, - [r] + [t] ) ); } - function R(e, t) { + function A(e, t) { return ( (function (e) { if (Array.isArray(e)) return e; @@ -451,7 +452,7 @@ })(e, t) || (function (e, t) { if (e) { - if ('string' == typeof e) return I(e, t); + if ('string' == typeof e) return B(e, t); var r = {}.toString.call(e).slice(8, -1); return ( 'Object' === r && e.constructor && (r = e.constructor.name), @@ -459,7 +460,7 @@ ? Array.from(e) : 'Arguments' === r || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r) - ? I(e, t) + ? B(e, t) : void 0 ); } @@ -471,36 +472,31 @@ })() ); } - function I(e, t) { + function B(e, t) { (null == t || t > e.length) && (t = e.length); for (var r = 0, n = Array(t); r < t; r++) n[r] = e[r]; return n; } - var _, - A = - (null === (w = window.ValidationAPI) || void 0 === w - ? void 0 - : w.metaValidationRules) || {}; function N(e, t, r, n) { var o, i = - null === (o = A[e]) || void 0 === o || null === (o = o[t]) || void 0 === o + null === (o = E()[e]) || void 0 === o || null === (o = o[t]) || void 0 === o ? void 0 : o[n]; if (!v(i)) return !0; var a = !0; return ( 'required' === n && (a = '' !== r && null != r), - (0, g.applyFilters)('validation_api_validate_meta', a, r, e, t, n) + (0, g.applyFilters)('editor.validateMeta', a, r, e, t, n) ); } function C(e, t, r) { for ( - var n = (A[e] || {})[t] || {}, o = [], i = 0, a = Object.entries(n); + var n = (E()[e] || {})[t] || {}, o = [], i = 0, a = Object.entries(n); i < a.length; i++ ) { - var l = R(a[i], 2), + var l = A(a[i], 2), c = l[0], u = l[1]; if (v(u) && !N(e, t, r, c)) { @@ -510,9 +506,9 @@ } return b(o); } - function B(e) { + function _(e) { return ( - (B = + (_ = 'function' == typeof Symbol && 'symbol' == typeof Symbol.iterator ? function (e) { return typeof e; @@ -525,10 +521,10 @@ ? 'symbol' : typeof e; }), - B(e) + _(e) ); } - function V(e, t) { + function L(e, t) { var r = Object.keys(e); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); @@ -540,16 +536,16 @@ } return r; } - function L(e) { + function M(e) { for (var t = 1; t < arguments.length; t++) { var r = null != arguments[t] ? arguments[t] : {}; t % 2 - ? V(Object(r), !0).forEach(function (t) { + ? L(Object(r), !0).forEach(function (t) { T(e, t, r[t]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(r)) - : V(Object(r)).forEach(function (t) { + : L(Object(r)).forEach(function (t) { Object.defineProperty(e, t, Object.getOwnPropertyDescriptor(r, t)); }); } @@ -559,16 +555,16 @@ return ( (t = (function (e) { var t = (function (e) { - if ('object' != B(e) || !e) return e; + if ('object' != _(e) || !e) return e; var t = e[Symbol.toPrimitive]; if (void 0 !== t) { var r = t.call(e, 'string'); - if ('object' != B(r)) return r; + if ('object' != _(r)) return r; throw new TypeError('@@toPrimitive must return a primitive value.'); } return String(e); })(e); - return 'symbol' == B(t) ? t : t + ''; + return 'symbol' == _(t) ? t : t + ''; })(t)) in e ? Object.defineProperty(e, t, { value: r, @@ -580,7 +576,7 @@ e ); } - function D(e, t) { + function V(e, t) { return ( (function (e) { if (Array.isArray(e)) return e; @@ -623,7 +619,7 @@ })(e, t) || (function (e, t) { if (e) { - if ('string' == typeof e) return M(e, t); + if ('string' == typeof e) return D(e, t); var r = {}.toString.call(e).slice(8, -1); return ( 'Object' === r && e.constructor && (r = e.constructor.name), @@ -631,7 +627,7 @@ ? Array.from(e) : 'Arguments' === r || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r) - ? M(e, t) + ? D(e, t) : void 0 ); } @@ -643,25 +639,22 @@ })() ); } - function M(e, t) { + function D(e, t) { (null == t || t > e.length) && (t = e.length); for (var r = 0, n = Array(t); r < t; r++) n[r] = e[r]; return n; } - var x = - (null === (_ = window.ValidationAPI) || void 0 === _ ? void 0 : _.editorValidationRules) || - {}; - var F = 'validation-api', - K = 'SET_INVALID_BLOCKS', - W = 'SET_INVALID_META', + var x = 'core/validation', + F = 'SET_INVALID_BLOCKS', + K = 'SET_INVALID_META', U = 'SET_INVALID_EDITOR_CHECKS', - $ = 'SET_BLOCK_VALIDATION', - q = 'CLEAR_BLOCK_VALIDATION', - H = { blocks: [], meta: [], editor: [], blockValidation: {} }, - Z = Object.freeze({ mode: 'none', issues: [] }); - function z(e) { + W = 'SET_BLOCK_VALIDATION', + $ = 'CLEAR_BLOCK_VALIDATION', + q = { blocks: [], meta: [], editor: [], blockValidation: {} }, + H = Object.freeze({ mode: 'none', issues: [] }); + function Z(e) { return ( - (z = + (Z = 'function' == typeof Symbol && 'symbol' == typeof Symbol.iterator ? function (e) { return typeof e; @@ -674,10 +667,10 @@ ? 'symbol' : typeof e; }), - z(e) + Z(e) ); } - function G(e, t) { + function z(e, t) { var r = Object.keys(e); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); @@ -689,24 +682,24 @@ } return r; } - function J(e) { + function G(e) { for (var t = 1; t < arguments.length; t++) { var r = null != arguments[t] ? arguments[t] : {}; t % 2 - ? G(Object(r), !0).forEach(function (t) { - Q(e, t, r[t]); + ? z(Object(r), !0).forEach(function (t) { + J(e, t, r[t]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(r)) - : G(Object(r)).forEach(function (t) { + : z(Object(r)).forEach(function (t) { Object.defineProperty(e, t, Object.getOwnPropertyDescriptor(r, t)); }); } return e; } - function Q(e, t, r) { + function J(e, t, r) { return ( - (t = X(t)) in e + (t = Q(t)) in e ? Object.defineProperty(e, t, { value: r, enumerable: !0, @@ -717,32 +710,32 @@ e ); } - function X(e) { + function Q(e) { var t = (function (e) { - if ('object' != z(e) || !e) return e; + if ('object' != Z(e) || !e) return e; var t = e[Symbol.toPrimitive]; if (void 0 !== t) { var r = t.call(e, 'string'); - if ('object' != z(r)) return r; + if ('object' != Z(r)) return r; throw new TypeError('@@toPrimitive must return a primitive value.'); } return String(e); })(e); - return 'symbol' == z(t) ? t : t + ''; + return 'symbol' == Z(t) ? t : t + ''; } - function Y(e) { + function X(e) { return e.blocks; } - function ee(e) { + function Y(e) { return e.meta; } - function te(e) { + function ee(e) { return e.editor; } - function re(e, t) { - return e.blockValidation[t] || Z; + function te(e, t) { + return e.blockValidation[t] || H; } - function ne(e) { + function re(e) { var t = e.blocks.some(function (e) { return 'error' === e.mode; }), @@ -754,8 +747,8 @@ }); return t || r || n; } - function oe(e) { - if (ne(e)) return !1; + function ne(e) { + if (re(e)) return !1; var t = e.blocks.some(function (e) { return 'warning' === e.mode; }), @@ -767,45 +760,45 @@ }); return t || r || n; } + function oe(e) { + return { type: F, results: e }; + } function ie(e) { return { type: K, results: e }; } function ae(e) { - return { type: W, results: e }; - } - function le(e) { return { type: U, issues: e }; } - function ce(e, t) { - return { type: $, clientId: e, result: t }; + function le(e, t) { + return { type: W, clientId: e, result: t }; } - function ue(e) { - return { type: q, clientId: e }; + function ce(e) { + return { type: $, clientId: e }; } - var se = (0, o.createReduxStore)(F, { + var ue = (0, o.createReduxStore)(x, { reducer: function () { - var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : H, + var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : q, t = arguments.length > 1 ? arguments[1] : void 0; switch (t.type) { + case F: + return G(G({}, e), {}, { blocks: t.results }); case K: - return J(J({}, e), {}, { blocks: t.results }); - case W: - return J(J({}, e), {}, { meta: t.results }); + return G(G({}, e), {}, { meta: t.results }); case U: - return J(J({}, e), {}, { editor: t.issues }); - case $: - return J( - J({}, e), + return G(G({}, e), {}, { editor: t.issues }); + case W: + return G( + G({}, e), {}, { - blockValidation: J( - J({}, e.blockValidation), + blockValidation: G( + G({}, e.blockValidation), {}, - Q({}, t.clientId, t.result) + J({}, t.clientId, t.result) ), } ); - case q: + case $: var r = e.blockValidation, n = t.clientId, o = @@ -833,8 +826,8 @@ (o[r] = e[r])); } return o; - })(r, [n].map(X))); - return J(J({}, e), {}, { blockValidation: o }); + })(r, [n].map(Q))); + return G(G({}, e), {}, { blockValidation: o }); default: return e; } @@ -842,41 +835,37 @@ selectors: t, actions: r, }); - function fe() { + function se() { var e, t, r, - n = P(), + n = I(), a = (function () { for ( - var e, - t = (0, o.useSelect)(function (e) { + var e = (0, o.useSelect)(function (e) { var t = e('core/editor'); return { postType: t.getCurrentPostType(), meta: t.getEditedPostAttribute('meta'), }; }, []), - r = t.postType, - n = t.meta, - i = - ((null === (e = window.ValidationAPI) || void 0 === e - ? void 0 - : e.metaValidationRules) || {})[r] || {}, - a = [], - l = 0, - c = Object.keys(i); - l < c.length; - l++ + t = e.postType, + r = e.meta, + n = E()[t] || {}, + i = [], + a = 0, + l = Object.keys(n); + a < l.length; + a++ ) { - var u = c[l], - s = C(r, u, null == n ? void 0 : n[u]); - s.isValid || a.push(L(L({}, s), {}, { metaKey: u })); + var c = l[a], + u = C(t, c, null == r ? void 0 : r[c]); + u.isValid || i.push(M(M({}, u), {}, { metaKey: c })); } - return a; + return i; })(), l = - ((e = (0, o.useSelect)(function (e) { + ((t = (e = (0, o.useSelect)(function (e) { var t = e('core/editor'), r = e('core/block-editor'); return { @@ -884,28 +873,23 @@ blocks: r.getBlocks(), title: t.getEditedPostAttribute('title'), }; - }, [])), - (t = e.blocks), + }, [])).blocks), (r = e.postType) && t ? (function (e, t) { for ( - var r = x[e] || {}, n = [], o = 0, i = Object.entries(r); + var r = (O().editorValidationRules || {})[e] || {}, + n = [], + o = 0, + i = Object.entries(r); o < i.length; o++ ) { - var a = D(i[o], 2), + var a = V(i[o], 2), l = a[0], c = a[1]; if ( v(c) && - !(0, g.applyFilters)( - 'validation_api_validate_editor', - !0, - t, - e, - l, - c - ) + !(0, g.applyFilters)('editor.validateEditor', !0, t, e, l, c) ) { var u = y(c, l); n.push(u); @@ -919,7 +903,7 @@ ); })(r, t).issues : []), - c = (0, o.useDispatch)(F), + c = (0, o.useDispatch)(x), u = c.setInvalidBlocks, s = c.setInvalidMeta, f = c.setInvalidEditorChecks; @@ -945,72 +929,69 @@ null ); } - function de() { - var e, - t = - (null === (e = window.ValidationAPI) || void 0 === e ? void 0 : e.editorContext) || - 'none', - r = 'post-editor' === t || 'post-editor-template' === t, - n = 'core/editor', - a = (0, o.useDispatch)(n), - l = wp.data && wp.data.select && wp.data.select(n), - c = (0, o.useSelect)(function (e) { - var t = e(F); + function fe() { + var e = k(), + t = 'post-editor' === e || 'post-editor-template' === e, + r = 'core/editor', + n = (0, o.useDispatch)(r), + a = wp.data && wp.data.select && wp.data.select(r), + l = (0, o.useSelect)(function (e) { + var t = e(x); return { invalidBlocks: t.getInvalidBlocks(), invalidMeta: t.getInvalidMeta(), invalidEditorChecks: t.getInvalidEditorChecks(), }; }, []), - u = c.invalidBlocks, - s = c.invalidMeta, - f = c.invalidEditorChecks, - d = a || {}, - v = d.lockPostSaving, - y = d.unlockPostSaving, - b = d.lockPostAutosaving, - g = d.unlockPostAutosaving, - h = d.disablePublishSidebar, - w = d.enablePublishSidebar; + c = l.invalidBlocks, + u = l.invalidMeta, + s = l.invalidEditorChecks, + f = n || {}, + d = f.lockPostSaving, + v = f.unlockPostSaving, + y = f.lockPostAutosaving, + b = f.unlockPostAutosaving, + g = f.disablePublishSidebar, + h = f.enablePublishSidebar; return ( (0, i.useEffect)( function () { - if (r && 'none' !== t && l && v && y) { - var e = u.some(function (e) { + if (t && 'none' !== e && a && d && v) { + var r = c.some(function (e) { return 'error' === e.mode; }), - n = s.some(function (e) { + n = u.some(function (e) { return e.hasErrors; }), - o = m(f); - e || n || o - ? (v('validation-api'), b && b('validation-api'), h && h()) - : (y('validation-api'), g && g('validation-api'), w && w()); + o = m(s); + r || n || o + ? (d('core/validation'), y && y('core/validation'), g && g()) + : (v('core/validation'), b && b('core/validation'), h && h()); } }, - [u, s, f, v, y, b, g, h, w, r, t, l] + [c, u, s, d, v, y, b, g, h, t, e, a] ), (0, i.useEffect)( function () { - if (r && 'none' !== t && document.body) { - var e = u.some(function (e) { + if (t && 'none' !== e && document.body) { + var r = c.some(function (e) { return 'error' === e.mode; }), - n = u.some(function (e) { + n = c.some(function (e) { return 'warning' === e.mode; }), - o = s.some(function (e) { + o = u.some(function (e) { return e.hasErrors; }), - i = s.some(function (e) { + i = u.some(function (e) { return e.hasWarnings && !e.hasErrors; }), - a = m(f), - l = p(f), - c = e || o || a, - d = !c && (n || i || l); + a = m(s), + l = p(s), + f = r || o || a, + d = !f && (n || i || l); return ( - c + f ? (document.body.classList.add('has-validation-errors'), document.body.classList.remove('has-validation-warnings')) : d @@ -1030,17 +1011,17 @@ ); } }, - [u, s, f, r, t] + [c, u, s, t, e] ), null ); } - (0, o.register)(se); - const me = window.wp.editor, - pe = window.wp.components, - ve = window.wp.i18n, - ye = window.wp.blocks; - function be(e) { + (0, o.register)(ue); + const de = window.wp.editor, + me = window.wp.components, + pe = window.wp.i18n, + ve = window.wp.blocks; + function ye(e) { var t = e.fill, r = void 0 === t ? 'currentColor' : t; return React.createElement( @@ -1060,20 +1041,20 @@ }) ); } - function ge(e, t) { + function be(e, t) { var r = new Map(); return ( e.forEach(function (e) { ('error' === t ? f(e.issues || []) : d(e.issues || [])).forEach(function (n) { var o, i, - a = 'error' === t ? n.error_msg : n.warning_msg || n.error_msg, + a = 'error' === t ? n.errorMsg : n.warningMsg || n.errorMsg, l = ''.concat(e.name, '|').concat(a); (r.has(l) || r.set(l, { blockName: ((o = e.name), - (i = (0, ye.getBlockType)(o)), + (i = (0, ve.getBlockType)(o)), i && i.title ? i.title : (o.split('/')[1] || o) @@ -1094,12 +1075,12 @@ Array.from(r.values()) ); } - function he(e, t) { + function ge(e, t) { var r = new Map(); return ( e.forEach(function (e) { ('error' === t ? f(e.issues || []) : d(e.issues || [])).forEach(function (n) { - var o = 'error' === t ? n.error_msg : n.warning_msg || n.error_msg, + var o = 'error' === t ? n.errorMsg : n.warningMsg || n.errorMsg, i = ''.concat(e.metaKey, '|').concat(o); r.has(i) || r.set(i, { metaKey: e.metaKey, message: o }); }); @@ -1107,23 +1088,20 @@ Array.from(r.values()) ); } - function we(e, t) { + function he(e, t) { var r = new Map(); return ( e.forEach(function (e) { - var n = - 'error' === t - ? e.errorMsg || e.error_msg - : e.warningMsg || e.warning_msg || e.errorMsg || e.error_msg, + var n = 'error' === t ? e.errorMsg : e.warningMsg || e.errorMsg, o = n; r.has(o) || r.set(o, { message: n, description: e.description }); }), Array.from(r.values()) ); } - function Oe() { + function we() { var e = (0, o.useSelect)(function (e) { - var t = e(F); + var t = e(x); return { invalidBlocks: t.getInvalidBlocks(), invalidMeta: t.getInvalidMeta(), @@ -1137,17 +1115,17 @@ l = (0, i.useRef)(null), c = s(n, 'error'), u = s(n, 'warning'), - f = ge(t, 'error'), - d = ge(t, 'warning'), - m = he(r, 'error'), - p = he(r, 'warning'), - v = we(c, 'error'), - y = we(u, 'warning'), + f = be(t, 'error'), + d = be(t, 'warning'), + m = ge(r, 'error'), + p = ge(r, 'warning'), + v = he(c, 'error'), + y = he(u, 'warning'), b = f.length + m.length + v.length, g = d.length + p.length + y.length, h = 'currentColor'; b > 0 ? (h = '#d82000') : g > 0 && (h = '#dbc900'); - var w = React.createElement(be, { fill: h }), + var w = React.createElement(ye, { fill: h }), O = function (e) { e && (a(e), @@ -1174,20 +1152,20 @@ 0 === b && 0 === g ? null : React.createElement( - me.PluginSidebar, + de.PluginSidebar, { name: 'validation-sidebar', - title: (0, ve.__)('Validation', 'validation-api'), + title: (0, pe.__)('Validation', 'validation-api'), icon: w, className: 'validation-api-validation-sidebar', }, b > 0 && React.createElement( - pe.PanelBody, + me.PanelBody, { - title: (0, ve.sprintf)( + title: (0, pe.sprintf)( /* translators: %d: number of errors */ /* translators: %d: number of errors */ - (0, ve.__)('Errors (%d)', 'validation-api'), + (0, pe.__)('Errors (%d)', 'validation-api'), b ), initialOpen: !0, @@ -1195,7 +1173,7 @@ }, f.length > 0 && React.createElement( - pe.PanelRow, + me.PanelRow, null, React.createElement( 'div', @@ -1203,7 +1181,7 @@ React.createElement( 'p', { className: 'validation-api-error-subheading' }, - (0, ve.__)('Block Issues', 'validation-api') + (0, pe.__)('Block Issues', 'validation-api') ), React.createElement( 'ul', @@ -1236,7 +1214,7 @@ ), m.length > 0 && React.createElement( - pe.PanelRow, + me.PanelRow, null, React.createElement( 'div', @@ -1244,7 +1222,7 @@ React.createElement( 'p', { className: 'validation-api-error-subheading' }, - (0, ve.__)('Field Issues', 'validation-api') + (0, pe.__)('Field Issues', 'validation-api') ), React.createElement( 'ul', @@ -1261,7 +1239,7 @@ ), v.length > 0 && React.createElement( - pe.PanelRow, + me.PanelRow, null, React.createElement( 'div', @@ -1269,7 +1247,7 @@ React.createElement( 'p', { className: 'validation-api-error-subheading' }, - (0, ve.__)('Editor Issues', 'validation-api') + (0, pe.__)('Editor Issues', 'validation-api') ), React.createElement( 'ul', @@ -1287,11 +1265,11 @@ ), g > 0 && React.createElement( - pe.PanelBody, + me.PanelBody, { - title: (0, ve.sprintf)( + title: (0, pe.sprintf)( /* translators: %d: number of warnings */ /* translators: %d: number of warnings */ - (0, ve.__)('Warnings (%d)', 'validation-api'), + (0, pe.__)('Warnings (%d)', 'validation-api'), g ), initialOpen: !0, @@ -1299,7 +1277,7 @@ }, d.length > 0 && React.createElement( - pe.PanelRow, + me.PanelRow, null, React.createElement( 'div', @@ -1307,7 +1285,7 @@ React.createElement( 'p', { className: 'validation-api-warning-subheading' }, - (0, ve.__)('Block Issues', 'validation-api') + (0, pe.__)('Block Issues', 'validation-api') ), React.createElement( 'ul', @@ -1340,7 +1318,7 @@ ), p.length > 0 && React.createElement( - pe.PanelRow, + me.PanelRow, null, React.createElement( 'div', @@ -1348,7 +1326,7 @@ React.createElement( 'p', { className: 'validation-api-warning-subheading' }, - (0, ve.__)('Field Issues', 'validation-api') + (0, pe.__)('Field Issues', 'validation-api') ), React.createElement( 'ul', @@ -1365,7 +1343,7 @@ ), y.length > 0 && React.createElement( - pe.PanelRow, + me.PanelRow, null, React.createElement( 'div', @@ -1373,7 +1351,7 @@ React.createElement( 'p', { className: 'validation-api-warning-subheading' }, - (0, ve.__)('Editor Issues', 'validation-api') + (0, pe.__)('Editor Issues', 'validation-api') ), React.createElement( 'ul', @@ -1392,25 +1370,25 @@ ) ); } - (0, n.registerPlugin)('validation-api', { + (0, n.registerPlugin)('core-validation', { render: function () { return React.createElement( React.Fragment, null, + React.createElement(se, null), React.createElement(fe, null), - React.createElement(de, null), - React.createElement(Oe, null) + React.createElement(we, null) ); }, }); - const Ee = window.wp.compose, - je = window.wp.blockEditor; - function Se(e, t) { + const Oe = window.wp.compose, + Ee = window.wp.blockEditor; + function ke(e, t) { (null == t || t > e.length) && (t = e.length); for (var r = 0, n = Array(t); r < t; r++) n[r] = e[r]; return n; } - function ke(e) { + function Se(e) { var t, r, n = e.issues, @@ -1464,7 +1442,7 @@ })(t, r) || (function (e, t) { if (e) { - if ('string' == typeof e) return Se(e, t); + if ('string' == typeof e) return ke(e, t); var r = {}.toString.call(e).slice(8, -1); return ( 'Object' === r && e.constructor && (r = e.constructor.name), @@ -1472,7 +1450,7 @@ ? Array.from(e) : 'Arguments' === r || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r) - ? Se(e, t) + ? ke(e, t) : void 0 ); } @@ -1489,25 +1467,25 @@ u = f(n), s = d(n), p = c - ? React.createElement(be, { fill: '#d82000' }) - : React.createElement(be, { fill: '#dbc900' }); + ? React.createElement(ye, { fill: '#d82000' }) + : React.createElement(ye, { fill: '#dbc900' }); return React.createElement( React.Fragment, null, - React.createElement(pe.ToolbarButton, { + React.createElement(me.ToolbarButton, { icon: p, onClick: function () { return l(!0); }, - label: (0, ve.__)('View block issues or concerns', 'validation-api'), + label: (0, pe.__)('View block issues or concerns', 'validation-api'), className: 'validation-api-toolbar-button', isCompact: !0, }), a && React.createElement( - pe.Modal, + me.Modal, { - title: (0, ve.__)('Issues or Concerns', 'validation-api'), + title: (0, pe.__)('Issues or Concerns', 'validation-api'), onRequestClose: function () { return l(!1); }, @@ -1529,7 +1507,7 @@ React.createElement('span', { className: 'validation-api-indicator-section-title-circle', }), - (0, ve.__)('Errors', 'validation-api') + (0, pe.__)('Errors', 'validation-api') ), React.createElement( 'ul', @@ -1538,7 +1516,7 @@ return React.createElement( 'li', { key: 'error-'.concat(t) }, - e.error_msg + e.errorMsg ); }) ) @@ -1556,7 +1534,7 @@ React.createElement('span', { className: 'validation-api-indicator-section-title-circle', }), - (0, ve.__)('Warnings', 'validation-api') + (0, pe.__)('Warnings', 'validation-api') ), React.createElement( 'ul', @@ -1565,7 +1543,7 @@ return React.createElement( 'li', { key: 'warning-'.concat(t) }, - e.warning_msg || e.error_msg + e.warningMsg || e.errorMsg ); }) ) @@ -1574,14 +1552,14 @@ ) ); } - function Pe(e, t) { + function je(e, t) { (null == t || t > e.length) && (t = e.length); for (var r = 0, n = Array(t); r < t; r++) n[r] = e[r]; return n; } - function Re(e) { + function Pe(e) { return ( - (Re = + (Pe = 'function' == typeof Symbol && 'symbol' == typeof Symbol.iterator ? function (e) { return typeof e; @@ -1594,10 +1572,10 @@ ? 'symbol' : typeof e; }), - Re(e) + Pe(e) ); } - function Ie(e, t) { + function Re(e, t) { var r = Object.keys(e); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); @@ -1609,16 +1587,16 @@ } return r; } - function _e(e) { + function Ie(e) { for (var t = 1; t < arguments.length; t++) { var r = null != arguments[t] ? arguments[t] : {}; t % 2 - ? Ie(Object(r), !0).forEach(function (t) { + ? Re(Object(r), !0).forEach(function (t) { Ae(e, t, r[t]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(r)) - : Ie(Object(r)).forEach(function (t) { + : Re(Object(r)).forEach(function (t) { Object.defineProperty(e, t, Object.getOwnPropertyDescriptor(r, t)); }); } @@ -1628,16 +1606,16 @@ return ( (t = (function (e) { var t = (function (e) { - if ('object' != Re(e) || !e) return e; + if ('object' != Pe(e) || !e) return e; var t = e[Symbol.toPrimitive]; if (void 0 !== t) { var r = t.call(e, 'string'); - if ('object' != Re(r)) return r; + if ('object' != Pe(r)) return r; throw new TypeError('@@toPrimitive must return a primitive value.'); } return String(e); })(e); - return 'symbol' == Re(t) ? t : t + ''; + return 'symbol' == Pe(t) ? t : t + ''; })(t)) in e ? Object.defineProperty(e, t, { value: r, @@ -1649,7 +1627,7 @@ e ); } - var Ne = (0, Ee.createHigherOrderComponent)(function (e) { + var Be = (0, Oe.createHigherOrderComponent)(function (e) { return function (t) { var r = t.clientId, n = t.attributes, @@ -1659,7 +1637,7 @@ }, [r] ), - l = (0, o.useDispatch)(F), + l = (0, o.useDispatch)(x), c = l.setBlockValidation, u = l.clearBlockValidation, s = (function (e, t) { @@ -1721,7 +1699,7 @@ })(r, n) || (function (e, t) { if (e) { - if ('string' == typeof e) return Pe(e, t); + if ('string' == typeof e) return je(e, t); var r = {}.toString.call(e).slice(8, -1); return ( 'Object' === r && @@ -1733,7 +1711,7 @@ /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test( r ) - ? Pe(e, t) + ? je(e, t) : void 0 ); } @@ -1764,8 +1742,8 @@ })( function () { if (!a) return { isValid: !0, issues: [], mode: 'none' }; - var e = _e(_e({}, a), {}, { attributes: n || a.attributes }); - return O(e); + var e = Ie(Ie({}, a), {}, { attributes: n || a.attributes }); + return w(e); }, [a, n], { delay: 300 } @@ -1788,17 +1766,17 @@ React.createElement(e, t), !s.isValid && React.createElement( - je.BlockControls, + Ee.BlockControls, { group: 'block' }, - React.createElement(ke, { issues: s.issues }) + React.createElement(Se, { issues: s.issues }) ) ) ); }; }, 'withErrorHandling'); - function Ce(e) { + function Ne(e) { return ( - (Ce = + (Ne = 'function' == typeof Symbol && 'symbol' == typeof Symbol.iterator ? function (e) { return typeof e; @@ -1811,12 +1789,12 @@ ? 'symbol' : typeof e; }), - Ce(e) + Ne(e) ); } - function Be() { + function Ce() { return ( - (Be = Object.assign + (Ce = Object.assign ? Object.assign.bind() : function (e) { for (var t = 1; t < arguments.length; t++) { @@ -1825,10 +1803,10 @@ } return e; }), - Be.apply(null, arguments) + Ce.apply(null, arguments) ); } - function Ve(e, t) { + function _e(e, t) { var r = Object.keys(e); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); @@ -1844,31 +1822,31 @@ for (var t = 1; t < arguments.length; t++) { var r = null != arguments[t] ? arguments[t] : {}; t % 2 - ? Ve(Object(r), !0).forEach(function (t) { - Te(e, t, r[t]); + ? _e(Object(r), !0).forEach(function (t) { + Me(e, t, r[t]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(r)) - : Ve(Object(r)).forEach(function (t) { + : _e(Object(r)).forEach(function (t) { Object.defineProperty(e, t, Object.getOwnPropertyDescriptor(r, t)); }); } return e; } - function Te(e, t, r) { + function Me(e, t, r) { return ( (t = (function (e) { var t = (function (e) { - if ('object' != Ce(e) || !e) return e; + if ('object' != Ne(e) || !e) return e; var t = e[Symbol.toPrimitive]; if (void 0 !== t) { var r = t.call(e, 'string'); - if ('object' != Ce(r)) return r; + if ('object' != Ne(r)) return r; throw new TypeError('@@toPrimitive must return a primitive value.'); } return String(e); })(e); - return 'symbol' == Ce(t) ? t : t + ''; + return 'symbol' == Ne(t) ? t : t + ''; })(t)) in e ? Object.defineProperty(e, t, { value: r, @@ -1880,95 +1858,7 @@ e ); } - function De(e) { - return ( - (De = - 'function' == typeof Symbol && 'symbol' == typeof Symbol.iterator - ? function (e) { - return typeof e; - } - : function (e) { - return e && - 'function' == typeof Symbol && - e.constructor === Symbol && - e !== Symbol.prototype - ? 'symbol' - : typeof e; - }), - De(e) - ); - } - function Me(e, t) { - var r = Object.keys(e); - if (Object.getOwnPropertySymbols) { - var n = Object.getOwnPropertySymbols(e); - (t && - (n = n.filter(function (t) { - return Object.getOwnPropertyDescriptor(e, t).enumerable; - })), - r.push.apply(r, n)); - } - return r; - } - function xe(e) { - for (var t = 1; t < arguments.length; t++) { - var r = null != arguments[t] ? arguments[t] : {}; - t % 2 - ? Me(Object(r), !0).forEach(function (t) { - Fe(e, t, r[t]); - }) - : Object.getOwnPropertyDescriptors - ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(r)) - : Me(Object(r)).forEach(function (t) { - Object.defineProperty(e, t, Object.getOwnPropertyDescriptor(r, t)); - }); - } - return e; - } - function Fe(e, t, r) { - return ( - (t = (function (e) { - var t = (function (e) { - if ('object' != De(e) || !e) return e; - var t = e[Symbol.toPrimitive]; - if (void 0 !== t) { - var r = t.call(e, 'string'); - if ('object' != De(r)) return r; - throw new TypeError('@@toPrimitive must return a primitive value.'); - } - return String(e); - })(e); - return 'symbol' == De(t) ? t : t + ''; - })(t)) in e - ? Object.defineProperty(e, t, { - value: r, - enumerable: !0, - configurable: !0, - writable: !0, - }) - : (e[t] = r), - e - ); - } - function Ke(e) { - return ( - (Ke = - 'function' == typeof Symbol && 'symbol' == typeof Symbol.iterator - ? function (e) { - return typeof e; - } - : function (e) { - return e && - 'function' == typeof Symbol && - e.constructor === Symbol && - e !== Symbol.prototype - ? 'symbol' - : typeof e; - }), - Ke(e) - ); - } - (wp.hooks.addFilter('editor.BlockEdit', 'validation-api/with-error-handling', Ne), + (wp.hooks.addFilter('editor.BlockEdit', 'validation-api/with-error-handling', Be), (0, g.addFilter)( 'editor.BlockListBlock', 'validation-api/with-block-validation-classes', @@ -1976,7 +1866,7 @@ return function (t) { var r = (0, o.useSelect)( function (e) { - return e(F).getBlockValidation(t.clientId); + return e(x).getBlockValidation(t.clientId); }, [t.clientId] ); @@ -1991,108 +1881,8 @@ {}, { className: [i.className, n].filter(Boolean).join(' ') } ); - return React.createElement(e, Be({}, t, { wrapperProps: a })); + return React.createElement(e, Ce({}, t, { wrapperProps: a })); }; } - ), - void 0 === window.ValidationAPI && (window.ValidationAPI = {}), - (window.ValidationAPI.useMetaField = function (e) { - var t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : '', - r = (function (e) { - return (0, o.useSelect)( - function (t) { - var r = t('core/editor'), - n = r.getEditedPostAttribute, - o = (0, r.getCurrentPostType)(), - i = n('meta'), - a = i ? i[e] : ''; - if (!o || !e) - return { - isValid: !0, - hasErrors: !1, - hasWarnings: !1, - issues: [], - wrapperClassName: '', - }; - var l = C(o, e, a), - c = ''; - return ( - l.hasErrors - ? (c = 'validation-api-meta-error') - : l.hasWarnings && (c = 'validation-api-meta-warning'), - xe(xe({}, l), {}, { wrapperClassName: c }) - ); - }, - [e] - ); - })(e), - n = (0, o.useSelect)( - function (t) { - var r = t('core/editor'); - if (!r) return { value: '' }; - var n = r.getEditedPostAttribute('meta'); - return { value: n ? n[e] : '' }; - }, - [e] - ).value, - i = (0, o.useDispatch)('core/editor').editPost, - a = t; - if (r && (r.hasErrors || r.hasWarnings)) { - var l = r.issues - .map(function (e) { - return e.message || e.error_msg || e.warning_msg; - }) - .join('. '), - c = r.hasErrors ? 'validation-api-error-text' : 'validation-api-warning-text'; - a = a - ? React.createElement( - React.Fragment, - null, - a, - React.createElement('span', { className: c }, '* ', l) - ) - : React.createElement('span', { className: c }, '* ', l); - } - return { - value: n || '', - onChange: function (t) { - var r, n, o; - i && - i({ - meta: - ((r = {}), - (n = e), - (o = t), - (n = (function (e) { - var t = (function (e) { - if ('object' != Ke(e) || !e) return e; - var t = e[Symbol.toPrimitive]; - if (void 0 !== t) { - var r = t.call(e, 'string'); - if ('object' != Ke(r)) return r; - throw new TypeError( - '@@toPrimitive must return a primitive value.' - ); - } - return String(e); - })(e); - return 'symbol' == Ke(t) ? t : t + ''; - })(n)) in r - ? Object.defineProperty(r, n, { - value: o, - enumerable: !0, - configurable: !0, - writable: !0, - }) - : (r[n] = o), - r), - }); - }, - help: a, - className: - null != r && r.wrapperClassName - ? 'validation-api-field '.concat(r.wrapperClassName) - : '', - }; - })); + )); })(); diff --git a/docs/INTEGRATION.md b/docs/INTEGRATION.md index d1a162a..b4989c9 100644 --- a/docs/INTEGRATION.md +++ b/docs/INTEGRATION.md @@ -1,6 +1,6 @@ # Gutenberg Integration Strategy -This document analyzes how the Validation API plugin maps to Gutenberg's architecture and outlines a strategy for proposing this framework upstream. +This document analyzes how the Validation API plugin maps to Gutenberg's architecture and outlines a strategy for proposing this framework upstream. The plugin's naming and API surface have already been aligned with Gutenberg conventions to minimize the diff when contributing upstream. ## Gutenberg's Current Validation Landscape @@ -43,8 +43,8 @@ How the Validation API's components map to Gutenberg's architecture: | Validation API | Gutenberg | Notes | |---|---|---| -| `lockPostSaving('validation-api')` | `core/editor` action | Already uses the Gutenberg API directly | -| `unlockPostSaving('validation-api')` | `core/editor` action | Already uses the Gutenberg API directly | +| `lockPostSaving('validation')` | `core/editor` action | Already uses the Gutenberg API directly | +| `unlockPostSaving('validation')` | `core/editor` action | Already uses the Gutenberg API directly | | `disablePublishSidebar()` | `core/editor` action | Already uses the Gutenberg API directly | | `@wordpress/hooks` filters | `@wordpress/hooks` package | `addFilter`/`applyFilters` used for all validation execution | | `PluginSidebar` usage | `@wordpress/editor` slot/fill | Sidebar renders via standard slot system | @@ -52,18 +52,20 @@ How the Validation API's components map to Gutenberg's architecture: | `editor.BlockListBlock` filter | `@wordpress/block-editor` filter | Used for per-block CSS class injection | | `useSelect` / `useDispatch` | `@wordpress/data` hooks | Store reads/writes use standard data APIs | -### Needs Adaptation for Core +### Already Aligned with Core Conventions -| Validation API | Current Approach | Core Approach | +These items were adapted from plugin conventions to core conventions and are now complete: + +| Component | Status | Details | |---|---|---| -| Store name `validation-api` | Standalone store | Rename to `core/validation` | -| `window.ValidationAPI` config | `wp_localize_script()` | Pass through editor settings via `setupEditor()` | -| `PluginContext` wrapper | `validation_api_register_plugin()` | Replace with `namespace` field in check args | -| PHP singleton registries | `Block\Registry::get_instance()` | Aligns with `WP_Block_Type_Registry` pattern | -| `registerPlugin('validation-api')` | Plugin-based initialization | Integrate into `ExperimentalEditorProvider` flow | -| Function names `validation_api_*` | Plugin namespace | Rename to `wp_*` prefix | -| JS filter names `validation_api_*` | Plugin namespace | Rename to `editor.*` or `validation.*` namespace | -| Dual camelCase/snake_case in issues | Compatibility layer | Standardize: snake_case in PHP, camelCase in JS | +| Store name `core/validation` | Done | Renamed from `validation-api` | +| Editor settings config | Done | Replaced `window.ValidationAPI` with `block_editor_settings_all` filter | +| `namespace` field in check args | Done | Replaced `PluginContext` / `validation_api_register_plugin()` | +| PHP singleton registries | Done | Follows `WP_Block_Type_Registry` pattern | +| Function names `wp_register_*_validation_check()` | Done | Renamed from `validation_api_*` prefix | +| JS filter names `editor.validate*` | Done | Renamed from `validation_api_validate_*` | +| camelCase-only issue model in JS | Done | Removed dual camelCase/snake_case compatibility layer | +| REST endpoint `wp/v2/validation-checks` | Done | Renamed from `validation-api/v1/checks` | ### New to Gutenberg (No Equivalent) @@ -75,9 +77,9 @@ How the Validation API's components map to Gutenberg's architecture: | `ValidationSidebar` | Consolidated validation results panel | | `ValidationToolbarButton` | Per-block validation toolbar UI | | Block/Meta/Editor registries | Declarative check registration | -| `validation_api_check_level` filter | Runtime severity override | +| `wp_validation_check_level` filter | Runtime severity override | | `Validator::required()` helper | Bridge between client and server meta validation | -| REST `/validation-api/v1/checks` | Check introspection for admin tooling | +| REST `wp/v2/validation-checks` | Check introspection for admin tooling | ## Packages Affected @@ -87,12 +89,12 @@ This is where the bulk of the integration lives: - **Store**: Register `core/validation` store alongside `core/editor` - **Components**: `ValidationProvider`, `ValidationSidebar`, `ValidationAPI` integrated into editor initialization via `ExperimentalEditorProvider` -- **Editor settings**: Pass validation config from PHP through the settings object that `setupEditor()` receives, replacing `wp_localize_script` +- **Editor settings**: Validation config passed from PHP through the settings object that `setupEditor()` receives, via the `block_editor_settings_all` filter ### `@wordpress/block-editor` -- **`editor.BlockListBlock` filter**: Already used by the plugin for CSS class injection (`validation-api-block-error`, `validation-api-block-warning`) -- **`editor.BlockEdit` filter**: Already used for per-block validation toolbar button +- **`editor.BlockListBlock` filter**: Used for CSS class injection (`validation-block-error`, `validation-block-warning`) +- **`editor.BlockEdit` filter**: Used for per-block validation toolbar button - No store changes needed -- per-block validation state lives in `core/validation`, not `core/block-editor` ### `@wordpress/blocks` @@ -109,9 +111,11 @@ This is where the bulk of the integration lives: - New file (e.g., `wp-includes/validation.php`) or extend `wp-includes/blocks.php` - Registration functions: `wp_register_block_validation_check()`, `wp_register_meta_validation_check()`, `wp_register_editor_validation_check()` - Registry classes following the `WP_Block_Type_Registry` singleton pattern -- Config export integrated into the editor settings bootstrap +- Config export integrated into the editor settings bootstrap via `block_editor_settings_all` + +## Current State and Upstream Strategy -## Phased Approach +The naming alignment refactor is complete. The plugin now uses core-style names for all public API surfaces. The remaining work is the actual Gutenberg contribution, structured as follows: ### Phase 1: RFC and Data Foundation @@ -139,7 +143,7 @@ This is where the bulk of the integration lives: - `wp_register_editor_validation_check( $post_type, $args )` -- Register checks for document state - `wp_validation_check_level` filter for runtime severity override - `wp_validation_check_args` filter for check modification before registration -- Validation config passed to JS via editor settings (not `wp_localize_script`) +- Validation config passed to JS via `block_editor_settings_all` filter **Check args structure**: ```php @@ -195,39 +199,52 @@ This is where the bulk of the integration lives: - `block.json` declarative validation rules - Default check bundles -## API Surface Changes for Core +## Completed API Alignment + +The following naming changes have been applied throughout the codebase. These reflect the current state of the plugin and the names that will be used in the Gutenberg PR. -### Naming +### PHP -| Current (Plugin) | Proposed (Core) | +| Old (Plugin) | Current | |---|---| -| `validation_api_register_plugin()` | Drop; use `namespace` field in check args | +| `validation_api_register_plugin()` | Removed; `namespace` field in check args | | `validation_api_register_block_check()` | `wp_register_block_validation_check()` | | `validation_api_register_meta_check()` | `wp_register_meta_validation_check()` | | `validation_api_register_editor_check()` | `wp_register_editor_validation_check()` | | `validation_api_check_level` filter | `wp_validation_check_level` | | `validation_api_check_args` filter | `wp_validation_check_args` | -| `validation_api_validate_block` JS filter | `editor.validateBlock` | -| `validation_api_validate_meta` JS filter | `editor.validateMeta` | -| `validation_api_validate_editor` JS filter | `editor.validateEditor` | + +### JavaScript + +| Old (Plugin) | Current | +|---|---| +| `validation_api_validate_block` filter | `editor.validateBlock` | +| `validation_api_validate_meta` filter | `editor.validateMeta` | +| `validation_api_validate_editor` filter | `editor.validateEditor` | | Store: `validation-api` | `core/validation` | -| `window.ValidationAPI` | Editor settings object | +| `window.ValidationAPI` | Config via `block_editor_settings_all` filter / editor settings | + +### REST API + +| Old (Plugin) | Current | +|---|---| +| `validation-api/v1/checks` | `wp/v2/validation-checks` | -### Simplifications +### Structural -1. **Drop `PluginContext`/`validation_api_register_plugin()`** -- Replace with a required `namespace` field in check registration args. Simpler, matches block registration pattern. +1. **`PluginContext` / `validation_api_register_plugin()` removed** -- Replaced with a required `namespace` field in check registration args. Simpler, matches block registration pattern. -2. **Drop `CheckProvider` interface** -- Useful for plugin organization but not needed in core API surface. Plugins can structure their own code. +2. **`CheckProvider` interface dropped from public API** -- Useful for plugin organization but not needed in core API surface. Plugins can structure their own code. -3. **Standardize issue model** -- Pick one convention: snake_case for PHP, camelCase for JS runtime. No dual-format compatibility layer. +3. **Issue model standardized** -- snake_case in PHP, camelCase in JS. No dual-format compatibility layer. -4. **Drop `window.ValidationAPI` global** -- Pass config through editor settings, which Gutenberg already handles via `ExperimentalEditorProvider`. +4. **`window.ValidationAPI` global removed** -- Config passed through editor settings via the `block_editor_settings_all` filter, which Gutenberg already handles via `ExperimentalEditorProvider`. ## Risks 1. **Performance at scale** -- Validating every block change in posts with hundreds of blocks needs benchmarking. The current debouncing (300ms) and `ValidationProvider` single-computation pattern help, but core demands higher standards. -2. **API permanence** -- Once filter names and function signatures land in core, they cannot change without deprecation cycles. Getting the naming right in Phases 1-2 is critical. +2. **API permanence** -- Once filter names and function signatures land in core, they cannot change without deprecation cycles. The current naming has been chosen to align with existing core conventions. 3. **Scope creep** -- Discussions may pull in content linting, accessibility auditing, or editorial workflows. The framework/rules boundary must hold. diff --git a/docs/PROPOSAL.md b/docs/PROPOSAL.md index 9141c02..e21f43a 100644 --- a/docs/PROPOSAL.md +++ b/docs/PROPOSAL.md @@ -68,46 +68,22 @@ The common thread is that developers have repeatedly asked for a standardized wa ### Registration Pattern -Checks are registered through a scoped plugin wrapper. This pattern declares the plugin identity, guards against the API being absent, and attributes all checks to the registering plugin: +Checks are registered with a flat function call, using a `namespace` field to attribute them to the registering plugin. A `function_exists` guard ensures integrating plugins work correctly whether or not the Validation API is active: ```php add_action( 'init', function() { - if ( ! function_exists( 'validation_api_register_plugin' ) ) { + if ( ! function_exists( 'wp_register_block_validation_check' ) ) { return; } - validation_api_register_plugin( - [ 'name' => 'My Content Rules' ], - function() { - // Register checks here -- all are attributed to 'My Content Rules' - } - ); + wp_register_block_validation_check( 'core/image', [ + 'namespace' => 'my-content-rules', + 'name' => 'alt_text', + 'error_msg' => __( 'Images must have alt text.', 'my-plugin' ), + ] ); } ); ``` -The `function_exists` guard ensures integrating plugins work correctly whether or not the Validation API is active. - -For larger integrations, the API provides a `CheckProvider` interface for class-based registration: - -```php -use ValidationAPI\Contracts\CheckProvider; - -class ImageChecks implements CheckProvider { - public function register(): void { - validation_api_register_block_check( 'core/image', [ - 'name' => 'alt_text', - 'level' => 'error', - 'error_msg' => __( 'Images must have alt text.', 'my-plugin' ), - ] ); - } -} - -validation_api_register_plugin( - [ 'name' => 'Enterprise Content Rules' ], - [ ImageChecks::class, HeadingChecks::class ] -); -``` - ### The Three Validation Scopes #### 1. Block Attributes Validation @@ -117,7 +93,8 @@ Validate individual block attributes such as image alt text, heading content, bu **PHP Registration:** ```php -validation_api_register_block_check( 'core/image', [ +wp_register_block_validation_check( 'core/image', [ + 'namespace' => 'my-plugin', 'name' => 'alt_text', 'level' => 'error', 'description' => __( 'Ensures images have alt text for screen reader users.', 'my-plugin' ), @@ -132,7 +109,7 @@ validation_api_register_block_check( 'core/image', [ import { addFilter } from '@wordpress/hooks'; addFilter( - 'validation_api_validate_block', + 'editor.validateBlock', 'my-plugin/image-alt-text', ( isValid, blockType, attributes, checkName ) => { if ( blockType !== 'core/image' || checkName !== 'alt_text' ) { @@ -153,7 +130,8 @@ Validate WordPress post meta fields with real-time client-side feedback. The met **PHP Registration:** ```php -validation_api_register_meta_check( 'post', [ +wp_register_meta_validation_check( 'post', [ + 'namespace' => 'my-plugin', 'name' => 'required', 'meta_key' => 'seo_description', 'level' => 'error', @@ -167,7 +145,7 @@ validation_api_register_meta_check( 'post', [ ```javascript addFilter( - 'validation_api_validate_meta', + 'editor.validateMeta', 'my-plugin/seo-description', ( isValid, value, postType, metaKey, checkName ) => { if ( metaKey !== 'seo_description' || checkName !== 'required' ) { @@ -202,7 +180,8 @@ Validate the overall editor state: block order, document structure, required ele **PHP Registration:** ```php -validation_api_register_editor_check( 'post', [ +wp_register_editor_validation_check( 'post', [ + 'namespace' => 'my-plugin', 'name' => 'first_block_heading', 'level' => 'warning', 'description' => __( 'Ensures content begins with a heading for structure.', 'my-plugin' ), @@ -215,7 +194,7 @@ validation_api_register_editor_check( 'post', [ ```javascript addFilter( - 'validation_api_validate_editor', + 'editor.validateEditor', 'my-plugin/first-block-heading', ( isValid, blocks, postType, checkName ) => { if ( checkName !== 'first_block_heading' ) { @@ -243,7 +222,7 @@ Every active check passes through a filter that allows any plugin to override it ```php apply_filters( - 'validation_api_check_level', + 'wp_validation_check_level', $registered_level, $context // [ 'scope' => 'block', 'block_type' => 'core/image', 'check_name' => 'alt_text' ] ); @@ -304,26 +283,26 @@ The [Validation API](https://github.com/troychaplin/validation-api) plugin demon ### Architecture - **PHP Registries** -- Singleton registries (`Block\Registry`, `Meta\Registry`, `Editor\Registry`) manage check registration, configuration, and data export via filters and actions. -- **`@wordpress/data` Store** -- A dedicated Redux store (`validation-api`) centralizes all validation state with actions, selectors, and a reducer. +- **`@wordpress/data` Store** -- A dedicated Redux store (`core/validation`) centralizes all validation state with actions, selectors, and a reducer. - **`ValidationProvider`** -- A renderless component that serves as the single computation point. Calls validation hooks for blocks, meta, and editor checks, then dispatches results to the store. - **`ValidationAPI`** -- A renderless component that manages side effects: `lockPostSaving`/`unlockPostSaving`, `lockPostAutosaving`/`unlockPostAutosaving`, `disablePublishSidebar`/`enablePublishSidebar`, and body CSS classes. -- **JavaScript Validation** -- Validation logic runs entirely in JavaScript via WordPress filters (`validation_api_validate_block`, `validation_api_validate_meta`, `validation_api_validate_editor`). -- **Configuration Export** -- PHP configuration is passed to JavaScript via `wp_localize_script`, creating a global `window.ValidationAPI` object with validation rules and editor context. -- **Plugin Attribution** -- The `PluginContext` system tracks which plugin registers which checks, enabling organized settings and REST API attribution. +- **JavaScript Validation** -- Validation logic runs entirely in JavaScript via WordPress filters (`editor.validateBlock`, `editor.validateMeta`, `editor.validateEditor`). +- **Configuration Export** -- PHP configuration is passed to JavaScript via the `block_editor_settings_all` filter, delivering validation rules and editor context through editor settings. +- **Plugin Attribution** -- The `namespace` field in check registration args attributes checks to the registering plugin, enabling organized settings and REST API attribution. ### REST API -A read-only endpoint (`GET /validation-api/v1/checks`) exposes all registered checks grouped by scope (block, meta, editor). This enables admin tooling and companion packages to read the validation configuration without parsing PHP internals. +A read-only endpoint (`GET /wp/v2/validation-checks`) exposes all registered checks grouped by scope (block, meta, editor). This enables admin tooling and companion packages to read the validation configuration without parsing PHP internals. ### External Plugin Integration -Plugins register checks using `validation_api_register_plugin()` with a `function_exists` guard, and validation logic is added via JavaScript filters: +Plugins register checks using `wp_register_block_validation_check()` (and related functions) with a `function_exists` guard, and validation logic is added via JavaScript filters: -- [Integration Example Plugin](https://github.com/troychaplin/validation-api-integration-example) -- A complete example demonstrating block, meta, and editor checks using the CheckProvider pattern. +- [Integration Example Plugin](https://github.com/troychaplin/validation-api-integration-example) -- A complete example demonstrating block, meta, and editor checks. ### Companion Settings Package -The [validation-api-settings](https://github.com/troychaplin/validation-api-settings) companion plugin provides an admin settings page built on WordPress DataForm. It reads registered checks from the REST API and lets admins override severity levels globally via the `validation_api_check_level` filter. +The [validation-api-settings](https://github.com/troychaplin/validation-api-settings) companion plugin provides an admin settings page built on WordPress DataForm. It reads registered checks from the REST API and lets admins override severity levels globally via the `wp_validation_check_level` filter. This separation is intentional: the core framework has no settings UI and no storage, making it suitable for core merge. The companion stays in plugin-land. @@ -334,18 +313,17 @@ The proposal is specifically for the **Validation API framework** -- the infrast **Included:** - Check registration system (PHP registries for block, meta, and editor checks) -- Global registration functions (`validation_api_register_plugin()`, `validation_api_register_block_check()`, `validation_api_register_meta_check()`, `validation_api_register_editor_check()`) -- CheckProvider interface for class-based registration -- JavaScript validation filter hooks (`validation_api_validate_block`, `validation_api_validate_meta`, `validation_api_validate_editor`) -- Dedicated `@wordpress/data` store for validation state -- Severity model with runtime override via `validation_api_check_level` filter +- Global registration functions (`wp_register_block_validation_check()`, `wp_register_meta_validation_check()`, `wp_register_editor_validation_check()`) +- JavaScript validation filter hooks (`editor.validateBlock`, `editor.validateMeta`, `editor.validateEditor`) +- Dedicated `@wordpress/data` store (`core/validation`) for validation state +- Severity model with runtime override via `wp_validation_check_level` filter - Validation result model (severity levels, issue reporting, standardized result objects) - Post-locking integration (automatic `lockPostSaving` based on error-level failures) - Standardized UI components (block indicators, validation sidebar, toolbar button) - Editor context scoping (post editor only, content blocks within templates) -- PHP action hooks for lifecycle events (`validation_api_initialized`, `validation_api_ready`, `validation_api_editor_checks_ready`) -- PHP filter hooks for check modification (`validation_api_check_args`, `validation_api_should_register_check`, `validation_api_check_level`) -- REST API endpoint (`GET /validation-api/v1/checks`) for admin tooling +- PHP action hooks for lifecycle events (`wp_validation_initialized`, `wp_validation_ready`, `wp_validation_editor_checks_ready`) +- PHP filter hooks for check modification (`wp_validation_check_args`, `wp_validation_should_register_check`, `wp_validation_check_level`) +- REST API endpoint (`GET /wp/v2/validation-checks`) for admin tooling - Meta validation helper (`Validator::required()`) for server-side enforcement via `register_post_meta()` **Not included (remains in plugin territory):** @@ -361,7 +339,6 @@ The distinction is important: the API provides the *capability* to validate; plu 1. **Async validation** -- Should the filter hooks support async validation for server-side checks (e.g., link checking, content analysis)? 2. **Block.json integration** -- Could validation rules be declared in `block.json` for simple checks (e.g., `"required": true` on attributes), with JavaScript filters for complex logic? -3. **Core checks** -- Should WordPress ship with any default validation checks (e.g., image alt text), or should all checks come from plugins? ## Next Steps diff --git a/docs/README.md b/docs/README.md index b9baa83..ba16b03 100644 --- a/docs/README.md +++ b/docs/README.md @@ -19,6 +19,6 @@ For contributors and the WordPress core team reviewing this plugin. - **[Architecture](technical/README.md)** — System design, registries, data store, and UI components - **[Data Flow](technical/data-flow.md)** — How checks move from PHP registration through to JS validation and UI - **[Hooks Reference](technical/hooks.md)** — Every PHP action/filter and JS filter with signatures -- **[API Reference](technical/api.md)** — All public functions, registry methods, and contracts +- **[API Reference](technical/api.md)** — All public registration functions, registry methods, and contracts - **[Companion Package](technical/companion-package.md)** — Settings companion architecture and the filter bridge - **[Design Decisions](technical/decisions.md)** — Why the API is shaped the way it is diff --git a/docs/TODO.md b/docs/TODO.md index ebb553d..baf63d4 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -1,139 +1,44 @@ # Integration TODO -Work items to make the Validation API plugin more closely align with Gutenberg's architecture before proposing upstream. Ordered by priority. +Remaining work items to prepare the Validation API plugin for a Gutenberg core proposal. The naming alignment and architectural changes are complete -- these items cover testing, performance, and future enhancements. -## Naming Alignment +## Completed -These changes bring the plugin's public API closer to what core naming conventions would require. +The following work has been done to align with Gutenberg conventions: -### Rename store to `core/validation` +- **Store renamed** to `core/validation` (from `validation-api`) +- **JS filter hooks renamed** to `editor.validateBlock`, `editor.validateMeta`, `editor.validateEditor` +- **`window.ValidationAPI` replaced** with `block_editor_settings_all` filter. Config available via `select('core/editor').getEditorSettings().validationApi` +- **`PluginContext` dropped**. Replaced with required `namespace` field in check registration args +- **PHP functions renamed** to `wp_register_block_validation_check()`, `wp_register_meta_validation_check()`, `wp_register_editor_validation_check()` +- **PHP hooks renamed** from `validation_api_*` to `wp_validation_*` +- **REST endpoint moved** to `wp/v2/validation-checks` +- **Issue model standardized** to camelCase only in JS (`errorMsg`, `warningMsg`). PHP still uses `error_msg`, `warning_msg` -- transformation happens at the boundary in `createIssue()` +- **`window.ValidationAPI.useMetaField` export dropped**. External plugins import directly +- **Documentation updated** across all guide, technical, and root docs +- **Integration example plugin updated** to use new API names -**Files**: `src/editor/store/constants.js`, all files importing `STORE_NAME` - -Change `STORE_NAME` from `'validation-api'` to `'core/validation'`. This matches Gutenberg's convention (`core/editor`, `core/block-editor`, `core/blocks`). Every `useSelect` and `useDispatch` call referencing the store name will need updating. - -### Rename JS filter hooks - -**Files**: `src/editor/validation/blocks/validateBlock.js`, `src/editor/validation/meta/validateMeta.js`, `src/editor/validation/editor/validateEditor.js`, and any external integration examples - -| Current | Target | -|---|---| -| `validation_api_validate_block` | `editor.validateBlock` | -| `validation_api_validate_meta` | `editor.validateMeta` | -| `validation_api_validate_editor` | `editor.validateEditor` | - -These are the filters external plugins hook into, so this is a breaking change for integrations. Consider supporting both names during a transition period. - -### Rename PHP registration functions - -**Files**: `validation-api.php`, all registry classes, documentation - -| Current | Target | -|---|---| -| `validation_api_register_plugin()` | See "Drop PluginContext" below | -| `validation_api_register_block_check()` | `wp_register_block_validation_check()` | -| `validation_api_register_meta_check()` | `wp_register_meta_validation_check()` | -| `validation_api_register_editor_check()` | `wp_register_editor_validation_check()` | - -### Rename PHP filter/action hooks - -**Files**: All registry classes, `Core/Assets.php`, `Core/Plugin.php` - -| Current | Target | -|---|---| -| `validation_api_check_level` | `wp_validation_check_level` | -| `validation_api_check_args` | `wp_validation_check_args` | -| `validation_api_should_register_check` | `wp_validation_should_register_check` | -| `validation_api_check_registered` | `wp_validation_check_registered` | -| `validation_api_initialized` | `wp_validation_initialized` | -| `validation_api_ready` | `wp_validation_ready` | -| Similar for all `validation_api_*` hooks | `wp_validation_*` | - -## Architecture Alignment - -### Replace `wp_localize_script` with editor settings - -**Files**: `includes/Core/Assets.php` - -Currently, validation config is passed to JS via `wp_localize_script()` as `window.ValidationAPI`. Gutenberg passes server-side config through editor settings via the `ExperimentalEditorProvider` props, stored in the `core/editor` store under `editorSettings`. - -The target approach: -1. Hook into the `block_editor_settings_all` filter -2. Add validation config to the settings array under a `validation` key -3. On the JS side, read config from `select('core/editor').getEditorSettings().validation` instead of `window.ValidationAPI` - -This eliminates the global object and uses the standard Gutenberg data flow. - -### Drop PluginContext / `validation_api_register_plugin()` - -**Files**: `validation-api.php`, `includes/Core/PluginContext.php`, all registries that read from `PluginContext` - -Replace the wrapping context pattern with a required `namespace` field in check registration args: - -```php -// Before -validation_api_register_plugin( - [ 'name' => 'My Plugin' ], - function() { - validation_api_register_block_check( 'core/image', [ - 'name' => 'alt_text', - // ... - ] ); - } -); - -// After -wp_register_block_validation_check( 'core/image', [ - 'namespace' => 'my-plugin', - 'name' => 'alt_text', - // ... -] ); -``` - -This removes `PluginContext.php` entirely. The `_plugin` stamp on each check becomes the `namespace` field. Simpler API, matches how `registerBlockType` uses namespace prefixes. - -Note: `validation_api_register_plugin()` and `CheckProvider` can remain as convenience wrappers in the plugin without being part of the core proposal. They become plugin-level sugar, not core API. - -### Standardize issue model (camelCase/snake_case) - -**Files**: `src/shared/utils/validation/issueHelpers.js`, `src/editor/validation/blocks/validateBlock.js`, `src/editor/validation/meta/validateMeta.js`, `src/editor/validation/editor/validateEditor.js` - -Currently, issue objects contain both `error_msg` and `errorMsg`. Pick one convention: -- PHP registration: snake_case (`error_msg`, `warning_msg`) -- JS runtime: camelCase (`errorMsg`, `warningMsg`) -- Transform at the boundary (when PHP config is read in JS) - -Remove the dual-format output from `createIssue()`. - -## Data Flow - -### Read validation config from editor settings instead of window global - -**Files**: `src/editor/validation/blocks/validateBlock.js`, `src/editor/validation/meta/validateMeta.js`, `src/editor/validation/editor/validateEditor.js`, `src/shared/utils/validation/getInvalidBlocks.js` - -All validation functions currently read from `window.ValidationAPI.validationRules`, `window.ValidationAPI.metaValidationRules`, etc. Change these to read from the `core/editor` store's editor settings. - -This requires passing editor settings into the validation functions (or accessing the store within them), rather than reading a global. +## Testing -### Pass editor context through store instead of window global +### Add unit tests for store -**Files**: `src/editor/validation/ValidationAPI.js`, `src/shared/utils/validation/getInvalidBlocks.js` +**Files**: New test files in `src/editor/store/__tests__/` -`window.ValidationAPI.editorContext` is read directly. Move this to editor settings so it flows through the store like other config. +Test reducer, actions, and selectors. The store is the foundation -- it should have comprehensive test coverage before proposing upstream. -## TypeScript +### Add unit tests for validation functions -### Add type definitions +**Files**: New test files alongside validation modules -**Files**: New `.d.ts` files or convert `.js` to `.ts` +- `validateBlock()` with various block types and check configs +- `validateMeta()` with required, custom, and multi-check scenarios +- `validateEditor()` with various block arrangements +- `issueHelpers` utility functions +- `getValidationConfig` utility functions -Gutenberg packages include TypeScript definitions. At minimum, add type definitions for: -- Store state shape, actions, and selectors -- Check registration args (PHP side documented, JS side needs types) -- Validation result and issue objects -- Public hooks (`useMetaField`, `useMetaValidation`) +### Add integration tests for the full validation flow -This isn't blocking but would be expected for a Gutenberg package. +Test the end-to-end flow: PHP registration -> editor settings injection -> JS validation -> store dispatch -> lock/unlock. This could use `@wordpress/env` and `@wordpress/e2e-test-utils`. ## Performance @@ -155,40 +60,17 @@ If performance is an issue, consider: Ensure validation re-computation is triggered only when relevant data changes, not on every render. The `ValidationProvider` dispatches to the store on every effect run -- verify that React's dependency array prevents unnecessary cycles. -## Testing - -### Add unit tests for store - -**Files**: New test files in `src/editor/store/__tests__/` - -Test reducer, actions, and selectors. The store is the foundation -- it should have comprehensive test coverage before proposing upstream. - -### Add unit tests for validation functions - -**Files**: New test files alongside validation modules - -- `validateBlock()` with various block types and check configs -- `validateMeta()` with required, custom, and multi-check scenarios -- `validateEditor()` with various block arrangements -- `issueHelpers` utility functions - -### Add integration tests for the full validation flow - -Test the end-to-end flow: PHP registration -> config export -> JS validation -> store dispatch -> lock/unlock. This could use `@wordpress/env` and `@wordpress/e2e-test-utils`. - -## Documentation - -### Update developer guide for renamed APIs - -**Files**: All files in `docs/guide/`, `docs/technical/` - -Once naming changes are made, all code examples in the documentation need updating to reflect the new function names, filter names, and store name. +## TypeScript -### Document the integration example with new API +### Add type definitions -**Files**: Update the [integration example plugin](https://github.com/troychaplin/validation-api-integration-example) +**Files**: New `.d.ts` files or convert `.js` to `.ts` -The example plugin should demonstrate the new `namespace`-based registration pattern without `validation_api_register_plugin()`. +Gutenberg packages include TypeScript definitions. At minimum, add type definitions for: +- Store state shape, actions, and selectors +- Check registration args (PHP side documented, JS side needs types) +- Validation result and issue objects +- Public hooks (`useMetaField`, `useMetaValidation`) ## Future Considerations diff --git a/docs/guide/README.md b/docs/guide/README.md index d31cdc9..4244563 100644 --- a/docs/guide/README.md +++ b/docs/guide/README.md @@ -7,7 +7,7 @@ The Validation API is a framework for the WordPress block editor. It provides re Every integration follows the same structure: 1. **Guard** — Check that the Validation API is active -2. **Register** — Declare your plugin and its checks (PHP) +2. **Register** — Declare your checks with a `namespace` field (PHP) 3. **Validate** — Implement the logic that decides pass/fail (JavaScript) ## Your First Check @@ -18,22 +18,18 @@ This example registers an image alt text check. If a `core/image` block has no a ```php add_action( 'init', function() { - if ( ! function_exists( 'validation_api_register_plugin' ) ) { + if ( ! function_exists( 'wp_register_block_validation_check' ) ) { return; } - validation_api_register_plugin( - [ 'name' => 'My Content Rules' ], - function() { - validation_api_register_block_check( 'core/image', [ - 'name' => 'alt_text', - 'level' => 'error', - 'description' => 'Images must have alt text', - 'error_msg' => 'This image is missing alt text.', - 'warning_msg' => 'Consider adding alt text to this image.', - ] ); - } - ); + wp_register_block_validation_check( 'core/image', [ + 'namespace' => 'my-content-rules', + 'name' => 'alt_text', + 'level' => 'error', + 'description' => 'Images must have alt text', + 'error_msg' => 'This image is missing alt text.', + 'warning_msg' => 'Consider adding alt text to this image.', + ] ); } ); ``` @@ -43,7 +39,7 @@ add_action( 'init', function() { import { addFilter } from '@wordpress/hooks'; addFilter( - 'validation_api_validate_block', + 'editor.validateBlock', 'my-plugin/image-alt', ( isValid, blockType, attributes, checkName ) => { if ( blockType === 'core/image' && checkName === 'alt_text' ) { @@ -63,25 +59,28 @@ That's it. The Validation API handles the sidebar panel, block indicators, and p Your plugin must not break when the Validation API is deactivated. Always wrap registration in: ```php -if ( ! function_exists( 'validation_api_register_plugin' ) ) { +if ( ! function_exists( 'wp_register_block_validation_check' ) ) { return; } ``` -This is the only dependency check you need. If `validation_api_register_plugin` exists, the entire API is available. +This is the only dependency check you need. If `wp_register_block_validation_check` exists, the entire API is available. -### validation_api_register_plugin() +### The namespace Field -This function declares your plugin identity and scopes your checks: +The `namespace` field in the check args declares your plugin identity and scopes your checks: ```php -validation_api_register_plugin( array $plugin_info, callable|array $checks ); +wp_register_block_validation_check( 'core/image', [ + 'namespace' => 'my-plugin', + 'name' => 'alt_text', + 'error_msg' => 'Alt text required.', +] ); ``` -- `$plugin_info` — Array with a required `'name'` key. This name appears in the REST API and the companion settings page. -- `$checks` — Either a callback that registers checks, or an array of `CheckProvider` class names (see [CheckProvider Pattern](check-providers.md)). +- `namespace` — A string identifier for your plugin. This name appears in the REST API and the companion settings page. -All checks registered inside the callback are automatically attributed to your plugin. +All checks with the same `namespace` are grouped together as belonging to the same plugin. ### Three Scopes @@ -89,9 +88,9 @@ The Validation API has three registries, each for a different kind of check: | Scope | Function | What It Validates | |---|---|---| -| Block | `validation_api_register_block_check()` | Block attributes (alt text, links, required fields) | -| Meta | `validation_api_register_meta_check()` | Post meta fields (SEO description, custom fields) | -| Editor | `validation_api_register_editor_check()` | Document-level concerns (heading hierarchy, content structure) | +| Block | `wp_register_block_validation_check()` | Block attributes (alt text, links, required fields) | +| Meta | `wp_register_meta_validation_check()` | Post meta fields (SEO description, custom fields) | +| Editor | `wp_register_editor_validation_check()` | Document-level concerns (heading hierarchy, content structure) | Each has its own PHP registration function and JS validation filter. See the dedicated guides: @@ -110,7 +109,7 @@ Every check has a `level` that controls its behavior: | `none` | Check is disabled. Skipped entirely. | | *(omitted)* | Defaults to `error`. | -Every active check passes through the `validation_api_check_level` filter, so any check's severity can be overridden at runtime — without modifying the registering plugin. See [Severity Model](severity.md) for details. +Every active check passes through the `wp_validation_check_level` filter, so any check's severity can be overridden at runtime — without modifying the registering plugin. See [Severity Model](severity.md) for details. ## What the API Provides @@ -120,7 +119,7 @@ When you register checks, the Validation API automatically handles: - **Block indicators** — Red (error) and yellow (warning) borders on blocks with issues - **Validation sidebar** — All issues grouped by severity, with click-to-navigate - **Publish locking** — Error-level checks prevent publishing via `lockPostSaving`/`unlockPostSaving` -- **REST API** — Registered checks exposed at `GET /validation-api/v1/checks` +- **REST API** — Registered checks exposed at `GET /wp/v2/validation-checks` - **Multi-context** — Works in both the post editor and the site editor You only write the registration and the validation logic. Everything else is handled. diff --git a/docs/guide/block-checks.md b/docs/guide/block-checks.md index 217f749..d60aab4 100644 --- a/docs/guide/block-checks.md +++ b/docs/guide/block-checks.md @@ -4,10 +4,11 @@ Block checks validate the attributes of individual blocks. If a `core/image` is ## Registration (PHP) -Register a block check inside your `validation_api_register_plugin()` callback: +Register a block check with the `namespace` field to identify your plugin: ```php -validation_api_register_block_check( 'core/image', [ +wp_register_block_validation_check( 'core/image', [ + 'namespace' => 'my-plugin', 'name' => 'alt_text', 'level' => 'error', 'description' => 'Images must have alt text', @@ -24,6 +25,7 @@ The second argument is an array of check configuration: | Key | Type | Required | Default | Description | |---|---|---|---|---| +| `namespace` | `string` | Yes | — | Identifier for the plugin registering this check | | `name` | `string` | Yes | — | Unique identifier for this check within the block type | | `error_msg` | `string` | Yes | — | Message shown when the check fails at error level | | `warning_msg` | `string` | No | Same as `error_msg` | Message shown when the check fails at warning level | @@ -37,13 +39,15 @@ The second argument is an array of check configuration: Register as many checks as needed for a single block type: ```php -validation_api_register_block_check( 'core/image', [ +wp_register_block_validation_check( 'core/image', [ + 'namespace' => 'my-plugin', 'name' => 'alt_text', 'level' => 'error', 'error_msg' => 'This image is missing alt text.', ] ); -validation_api_register_block_check( 'core/image', [ +wp_register_block_validation_check( 'core/image', [ + 'namespace' => 'my-plugin', 'name' => 'decorative_flag', 'level' => 'warning', 'error_msg' => 'Decorative images should be marked as decorative.', @@ -55,13 +59,13 @@ Each check needs a unique `name` within the block type. ## Validation Logic (JavaScript) -PHP registration tells the API *what* to check. JavaScript tells it *how* to check. Use the `validation_api_validate_block` filter: +PHP registration tells the API *what* to check. JavaScript tells it *how* to check. Use the `editor.validateBlock` filter: ```javascript import { addFilter } from '@wordpress/hooks'; addFilter( - 'validation_api_validate_block', + 'editor.validateBlock', 'my-plugin/image-alt', ( isValid, blockType, attributes, checkName, block ) => { if ( blockType === 'core/image' && checkName === 'alt_text' ) { @@ -84,7 +88,7 @@ addFilter( ### Return Value -Return `true` if the block passes validation, `false` if it fails. The API uses the registered `error_msg` or `warning_msg` based on the effective severity level. +Return `true` if the block passes validation, `false` if it fails. The API uses the registered `errorMsg` or `warningMsg` based on the effective severity level. ### Handling Multiple Checks in One Filter @@ -92,7 +96,7 @@ You can handle all checks for a block type in a single filter callback: ```javascript addFilter( - 'validation_api_validate_block', + 'editor.validateBlock', 'my-plugin/image-checks', ( isValid, blockType, attributes, checkName ) => { if ( blockType !== 'core/image' ) { @@ -143,7 +147,7 @@ The fifth parameter (`block`) gives you access to the full block structure: ```javascript addFilter( - 'validation_api_validate_block', + 'editor.validateBlock', 'my-plugin/nested-check', ( isValid, blockType, attributes, checkName, block ) => { if ( blockType === 'my-plugin/accordion' && checkName === 'has_items' ) { @@ -161,7 +165,7 @@ This is useful for checks that depend on block structure rather than just attrib 1. The user edits a block in the editor 2. The Validation API's block runner iterates over registered checks for that block type -3. For each check, the `validation_api_validate_block` filter fires +3. For each check, the `editor.validateBlock` filter fires 4. Your callback returns `true` (pass) or `false` (fail) 5. Failed checks display the appropriate message based on severity 6. Error-level failures lock publishing; warnings show feedback only diff --git a/docs/guide/check-providers.md b/docs/guide/check-providers.md index fc06ec9..408214b 100644 --- a/docs/guide/check-providers.md +++ b/docs/guide/check-providers.md @@ -1,6 +1,6 @@ # CheckProvider Pattern -For plugins with more than a handful of checks, the `CheckProvider` interface lets you organize registrations across multiple classes. Each class handles one concern — image checks, heading checks, meta checks — and they're all wired together through `validation_api_register_plugin()`. +For plugins with more than a handful of checks, the `CheckProvider` interface lets you organize registrations across multiple classes. Each class handles one concern — image checks, heading checks, meta checks — and they're all wired together through a shared `namespace`. ## The Interface @@ -21,7 +21,8 @@ use ValidationAPI\Contracts\CheckProvider; class ImageChecks implements CheckProvider { public function register(): void { - validation_api_register_block_check( 'core/image', [ + wp_register_block_validation_check( 'core/image', [ + 'namespace' => 'enterprise-content-rules', 'name' => 'alt_text', 'level' => 'error', 'description' => 'Images must have alt text', @@ -29,7 +30,8 @@ class ImageChecks implements CheckProvider { 'warning_msg' => 'Consider adding alt text to this image.', ] ); - validation_api_register_block_check( 'core/image', [ + wp_register_block_validation_check( 'core/image', [ + 'namespace' => 'enterprise-content-rules', 'name' => 'file_size', 'level' => 'warning', 'description' => 'Images should be optimized', @@ -42,28 +44,29 @@ class ImageChecks implements CheckProvider { ## Registering Providers -Pass an array of class names to `validation_api_register_plugin()`: +Instantiate each provider and call `register()`: ```php add_action( 'init', function() { - if ( ! function_exists( 'validation_api_register_plugin' ) ) { + if ( ! function_exists( 'wp_register_block_validation_check' ) ) { return; } - validation_api_register_plugin( - [ 'name' => 'Enterprise Content Rules' ], - [ - ImageChecks::class, - HeadingChecks::class, - ButtonChecks::class, - MetaChecks::class, - EditorChecks::class, - ] - ); + $providers = [ + new ImageChecks(), + new HeadingChecks(), + new ButtonChecks(), + new MetaChecks(), + new EditorChecks(), + ]; + + foreach ( $providers as $provider ) { + $provider->register(); + } } ); ``` -The framework instantiates each class and calls `register()`. All checks are automatically attributed to "Enterprise Content Rules" in the REST API and companion settings. +All checks use the same `namespace` value (e.g., `'enterprise-content-rules'`) to group them together in the REST API and companion settings. ## Mixing Scopes @@ -73,21 +76,24 @@ A single provider can register checks across all three scopes: class AccessibilityChecks implements CheckProvider { public function register(): void { // Block check - validation_api_register_block_check( 'core/image', [ + wp_register_block_validation_check( 'core/image', [ + 'namespace' => 'accessibility', 'name' => 'alt_text', 'level' => 'error', 'error_msg' => 'Images must have alt text.', ] ); // Editor check - validation_api_register_editor_check( 'post', [ + wp_register_editor_validation_check( 'post', [ + 'namespace' => 'accessibility', 'name' => 'heading_hierarchy', 'level' => 'warning', 'error_msg' => 'Heading hierarchy is broken.', ] ); // Meta check - validation_api_register_meta_check( 'post', [ + wp_register_meta_validation_check( 'post', [ + 'namespace' => 'accessibility', 'name' => 'required', 'meta_key' => 'seo_description', 'level' => 'error', @@ -105,7 +111,7 @@ A typical enterprise structure: ``` my-validation-plugin/ -├── my-validation-plugin.php ← Bootstrap: guard + register_plugin +├── my-validation-plugin.php ← Bootstrap: guard + register providers ├── src/ │ ├── Checks/ │ │ ├── ImageChecks.php ← CheckProvider for core/image @@ -127,19 +133,20 @@ my-validation-plugin/ */ add_action( 'init', function() { - if ( ! function_exists( 'validation_api_register_plugin' ) ) { + if ( ! function_exists( 'wp_register_block_validation_check' ) ) { return; } - validation_api_register_plugin( - [ 'name' => 'My Validation Rules' ], - [ - \MyPlugin\Checks\ImageChecks::class, - \MyPlugin\Checks\ButtonChecks::class, - \MyPlugin\Checks\HeadingChecks::class, - \MyPlugin\Checks\SeoMetaChecks::class, - ] - ); + $providers = [ + new \MyPlugin\Checks\ImageChecks(), + new \MyPlugin\Checks\ButtonChecks(), + new \MyPlugin\Checks\HeadingChecks(), + new \MyPlugin\Checks\SeoMetaChecks(), + ]; + + foreach ( $providers as $provider ) { + $provider->register(); + } } ); add_action( 'enqueue_block_editor_assets', function() { @@ -153,33 +160,38 @@ add_action( 'enqueue_block_editor_assets', function() { } ); ``` -## CheckProvider vs. Callback +## CheckProvider vs. Inline Registration Both approaches are supported. Use whichever fits your plugin: -**Callback** — best for small integrations with a few checks: +**Inline** — best for small integrations with a few checks: ```php -validation_api_register_plugin( - [ 'name' => 'Simple Rules' ], - function() { - validation_api_register_block_check( 'core/image', [ ... ] ); - validation_api_register_block_check( 'core/button', [ ... ] ); - } -); +wp_register_block_validation_check( 'core/image', [ + 'namespace' => 'simple-rules', + 'name' => 'alt_text', + 'error_msg' => 'Alt text required.', +] ); + +wp_register_block_validation_check( 'core/button', [ + 'namespace' => 'simple-rules', + 'name' => 'has_link', + 'error_msg' => 'Button needs a link.', +] ); ``` **CheckProvider** — best for larger integrations where you want separation of concerns, testability, and organized file structure: ```php -validation_api_register_plugin( - [ 'name' => 'Enterprise Rules' ], - [ - ImageChecks::class, - ButtonChecks::class, - HeadingChecks::class, - ] -); +$providers = [ + new ImageChecks(), + new ButtonChecks(), + new HeadingChecks(), +]; + +foreach ( $providers as $provider ) { + $provider->register(); +} ``` ## Error Handling diff --git a/docs/guide/editor-checks.md b/docs/guide/editor-checks.md index eef5915..28ea5c9 100644 --- a/docs/guide/editor-checks.md +++ b/docs/guide/editor-checks.md @@ -4,10 +4,11 @@ Editor checks validate the document as a whole. Heading hierarchy, minimum conte ## Registration (PHP) -Register an editor check inside your `validation_api_register_plugin()` callback: +Register an editor check with the `namespace` field to identify your plugin: ```php -validation_api_register_editor_check( 'post', [ +wp_register_editor_validation_check( 'post', [ + 'namespace' => 'my-plugin', 'name' => 'heading_hierarchy', 'level' => 'warning', 'description' => 'Headings should follow a logical hierarchy', @@ -24,6 +25,7 @@ The second argument is an array of check configuration: | Key | Type | Required | Default | Description | |---|---|---|---|---| +| `namespace` | `string` | Yes | — | Identifier for the plugin registering this check | | `name` | `string` | Yes | — | Unique identifier for this check within the post type | | `error_msg` | `string` | Yes | — | Message shown when the check fails at error level | | `warning_msg` | `string` | No | Same as `error_msg` | Message shown at warning level | @@ -39,7 +41,8 @@ Register the same check for multiple post types: ```php // Option 1: Loop foreach ( [ 'post', 'page' ] as $post_type ) { - validation_api_register_editor_check( $post_type, [ + wp_register_editor_validation_check( $post_type, [ + 'namespace' => 'my-plugin', 'name' => 'heading_hierarchy', 'level' => 'warning', 'error_msg' => 'Heading hierarchy is broken.', @@ -51,13 +54,13 @@ The Editor Registry also provides a bulk registration method on the registry cla ## Validation Logic (JavaScript) -Use the `validation_api_validate_editor` filter. Unlike block checks, editor checks receive the full array of blocks: +Use the `editor.validateEditor` filter. Unlike block checks, editor checks receive the full array of blocks: ```javascript import { addFilter } from '@wordpress/hooks'; addFilter( - 'validation_api_validate_editor', + 'editor.validateEditor', 'my-plugin/heading-hierarchy', ( isValid, blocks, postType, checkName, rule ) => { if ( checkName !== 'heading_hierarchy' ) { @@ -129,7 +132,7 @@ function findBlocks( blocks, blockType ) { **Check for required block type:** ```javascript addFilter( - 'validation_api_validate_editor', + 'editor.validateEditor', 'my-plugin/requires-image', ( isValid, blocks, postType, checkName ) => { if ( checkName === 'requires_image' ) { @@ -143,7 +146,7 @@ addFilter( **Count blocks:** ```javascript addFilter( - 'validation_api_validate_editor', + 'editor.validateEditor', 'my-plugin/min-content', ( isValid, blocks, postType, checkName ) => { if ( checkName === 'min_content' ) { diff --git a/docs/guide/examples.md b/docs/guide/examples.md index 94292d1..2b550a5 100644 --- a/docs/guide/examples.md +++ b/docs/guide/examples.md @@ -16,65 +16,66 @@ A full plugin that registers block, meta, and editor checks with both PHP and Ja */ add_action( 'init', function() { - if ( ! function_exists( 'validation_api_register_plugin' ) ) { + if ( ! function_exists( 'wp_register_block_validation_check' ) ) { return; } - validation_api_register_plugin( - [ 'name' => 'Content Quality Rules' ], - function() { - // Block checks - validation_api_register_block_check( 'core/image', [ - 'name' => 'alt_text', - 'level' => 'error', - 'description' => 'Images must have alt text', - 'error_msg' => 'This image is missing alt text.', - 'warning_msg' => 'Consider adding alt text to this image.', - ] ); - - validation_api_register_block_check( 'core/button', [ - 'name' => 'has_link', - 'level' => 'error', - 'description' => 'Buttons must have a link', - 'error_msg' => 'This button has no link.', - 'warning_msg' => 'Consider adding a link to this button.', - ] ); - - validation_api_register_block_check( 'core/button', [ - 'name' => 'has_text', - 'level' => 'error', - 'description' => 'Buttons must have visible text', - 'error_msg' => 'This button has no text.', - ] ); - - // Meta checks - validation_api_register_meta_check( 'post', [ - 'name' => 'required', - 'meta_key' => 'seo_description', - 'level' => 'error', - 'description' => 'Posts need an SEO description', - 'error_msg' => 'SEO description is required.', - 'warning_msg' => 'Consider adding an SEO description.', - ] ); - - // Editor checks - validation_api_register_editor_check( 'post', [ - 'name' => 'heading_hierarchy', - 'level' => 'warning', - 'description' => 'Headings should follow a logical hierarchy', - 'error_msg' => 'Heading hierarchy is broken.', - 'warning_msg' => 'Headings skip levels — consider fixing the hierarchy.', - ] ); - - validation_api_register_editor_check( 'post', [ - 'name' => 'has_image', - 'level' => 'warning', - 'description' => 'Posts should include at least one image', - 'error_msg' => 'This post has no images.', - 'warning_msg' => 'Consider adding an image to this post.', - ] ); - } - ); + // Block checks + wp_register_block_validation_check( 'core/image', [ + 'namespace' => 'content-quality-rules', + 'name' => 'alt_text', + 'level' => 'error', + 'description' => 'Images must have alt text', + 'error_msg' => 'This image is missing alt text.', + 'warning_msg' => 'Consider adding alt text to this image.', + ] ); + + wp_register_block_validation_check( 'core/button', [ + 'namespace' => 'content-quality-rules', + 'name' => 'has_link', + 'level' => 'error', + 'description' => 'Buttons must have a link', + 'error_msg' => 'This button has no link.', + 'warning_msg' => 'Consider adding a link to this button.', + ] ); + + wp_register_block_validation_check( 'core/button', [ + 'namespace' => 'content-quality-rules', + 'name' => 'has_text', + 'level' => 'error', + 'description' => 'Buttons must have visible text', + 'error_msg' => 'This button has no text.', + ] ); + + // Meta checks + wp_register_meta_validation_check( 'post', [ + 'namespace' => 'content-quality-rules', + 'name' => 'required', + 'meta_key' => 'seo_description', + 'level' => 'error', + 'description' => 'Posts need an SEO description', + 'error_msg' => 'SEO description is required.', + 'warning_msg' => 'Consider adding an SEO description.', + ] ); + + // Editor checks + wp_register_editor_validation_check( 'post', [ + 'namespace' => 'content-quality-rules', + 'name' => 'heading_hierarchy', + 'level' => 'warning', + 'description' => 'Headings should follow a logical hierarchy', + 'error_msg' => 'Heading hierarchy is broken.', + 'warning_msg' => 'Headings skip levels — consider fixing the hierarchy.', + ] ); + + wp_register_editor_validation_check( 'post', [ + 'namespace' => 'content-quality-rules', + 'name' => 'has_image', + 'level' => 'warning', + 'description' => 'Posts should include at least one image', + 'error_msg' => 'This post has no images.', + 'warning_msg' => 'Consider adding an image to this post.', + ] ); } ); add_action( 'enqueue_block_editor_assets', function() { @@ -95,7 +96,7 @@ import { addFilter } from '@wordpress/hooks'; // Block validation addFilter( - 'validation_api_validate_block', + 'editor.validateBlock', 'content-quality/blocks', ( isValid, blockType, attributes, checkName ) => { if ( blockType === 'core/image' && checkName === 'alt_text' ) { @@ -117,7 +118,7 @@ addFilter( // Meta validation addFilter( - 'validation_api_validate_meta', + 'editor.validateMeta', 'content-quality/meta', ( isValid, value, postType, metaKey, checkName ) => { if ( metaKey === 'seo_description' && checkName === 'required' ) { @@ -129,7 +130,7 @@ addFilter( // Editor validation addFilter( - 'validation_api_validate_editor', + 'editor.validateEditor', 'content-quality/editor', ( isValid, blocks, postType, checkName ) => { if ( checkName === 'heading_hierarchy' ) { @@ -183,7 +184,8 @@ function getHeadingLevels( blocks ) { Validate attributes on a custom block: ```php -validation_api_register_block_check( 'my-plugin/testimonial', [ +wp_register_block_validation_check( 'my-plugin/testimonial', [ + 'namespace' => 'my-plugin', 'name' => 'has_author', 'level' => 'error', 'description' => 'Testimonials must have an author', @@ -193,7 +195,7 @@ validation_api_register_block_check( 'my-plugin/testimonial', [ ```javascript addFilter( - 'validation_api_validate_block', + 'editor.validateBlock', 'my-plugin/testimonial-author', ( isValid, blockType, attributes, checkName ) => { if ( blockType === 'my-plugin/testimonial' && checkName === 'has_author' ) { @@ -212,37 +214,32 @@ Use `Validator::required()` for simultaneous client-side and server-side enforce use ValidationAPI\Meta\Validator; add_action( 'init', function() { - if ( ! function_exists( 'validation_api_register_plugin' ) ) { + if ( ! function_exists( 'wp_register_meta_validation_check' ) ) { return; } - validation_api_register_plugin( - [ 'name' => 'Event Plugin' ], - function() { - // Register the meta field with server-side validation - register_post_meta( 'event', 'event_date', [ - 'show_in_rest' => true, - 'single' => true, - 'type' => 'string', - 'validate_callback' => Validator::required( 'event', 'event_date', [ - 'error_msg' => 'Events must have a date.', - 'warning_msg' => 'Consider setting an event date.', - 'level' => 'error', - 'description' => 'Event date is required', - ] ), - ] ); - } - ); + // Register the meta field with server-side validation + register_post_meta( 'event', 'event_date', [ + 'show_in_rest' => true, + 'single' => true, + 'type' => 'string', + 'validate_callback' => Validator::required( 'event', 'event_date', [ + 'error_msg' => 'Events must have a date.', + 'warning_msg' => 'Consider setting an event date.', + 'level' => 'error', + 'description' => 'Event date is required', + ] ), + ] ); } ); ``` ## Recipe: Conditional Severity by Post Type -Use the `validation_api_check_level` filter to vary severity by context: +Use the `wp_validation_check_level` filter to vary severity by context: ```php // Alt text is an error on posts, a warning on pages -add_filter( 'validation_api_check_level', function( $level, $context ) { +add_filter( 'wp_validation_check_level', function( $level, $context ) { if ( $context['scope'] === 'block' && $context['block_type'] === 'core/image' && $context['check_name'] === 'alt_text' @@ -263,6 +260,7 @@ use ValidationAPI\Contracts\CheckProvider; class ImageChecks implements CheckProvider { private const BLOCK_TYPE = 'core/image'; + private const NAMESPACE = 'my-validation-plugin'; public function register(): void { $this->register_check( 'alt_text', 'error', [ @@ -279,9 +277,9 @@ class ImageChecks implements CheckProvider { } private function register_check( string $name, string $level, array $args ): void { - validation_api_register_block_check( self::BLOCK_TYPE, array_merge( + wp_register_block_validation_check( self::BLOCK_TYPE, array_merge( $args, - [ 'name' => $name, 'level' => $level ] + [ 'namespace' => self::NAMESPACE, 'name' => $name, 'level' => $level ] ) ); } } @@ -289,11 +287,11 @@ class ImageChecks implements CheckProvider { ## Recipe: Preventing Check Registration -Use the `validation_api_should_register_check` filter to conditionally prevent checks from registering: +Use the `wp_validation_should_register_check` filter to conditionally prevent checks from registering: ```php // Don't register image checks on the 'attachment' post type screen -add_filter( 'validation_api_should_register_check', function( $should, $block_type, $check_name, $args ) { +add_filter( 'wp_validation_should_register_check', function( $should, $block_type, $check_name, $args ) { if ( $block_type === 'core/image' && get_post_type() === 'attachment' ) { return false; } @@ -307,7 +305,7 @@ Use the REST API to see what's registered: ```javascript // In the browser console -wp.apiFetch( { path: '/validation-api/v1/checks' } ).then( console.log ); +wp.apiFetch( { path: '/wp/v2/validation-checks' } ).then( console.log ); ``` -This returns all registered checks grouped by scope, including `_plugin` attribution. Requires `manage_options` capability. +This returns all registered checks grouped by scope, including `_namespace` attribution. Requires `manage_options` capability. diff --git a/docs/guide/meta-checks.md b/docs/guide/meta-checks.md index 3540571..154b2ef 100644 --- a/docs/guide/meta-checks.md +++ b/docs/guide/meta-checks.md @@ -6,10 +6,11 @@ Meta checks have a unique advantage: they integrate with WordPress's `register_p ## Registration (PHP) -Register a meta check inside your `validation_api_register_plugin()` callback: +Register a meta check with the `namespace` field to identify your plugin: ```php -validation_api_register_meta_check( 'post', [ +wp_register_meta_validation_check( 'post', [ + 'namespace' => 'my-plugin', 'name' => 'required', 'meta_key' => 'seo_description', 'level' => 'error', @@ -27,6 +28,7 @@ The second argument is an array of check configuration: | Key | Type | Required | Default | Description | |---|---|---|---|---| +| `namespace` | `string` | Yes | — | Identifier for the plugin registering this check | | `name` | `string` | Yes | — | Unique identifier for this check | | `meta_key` | `string` | Yes | — | The post meta key to validate | | `error_msg` | `string` | Yes | — | Message shown when the check fails at error level | @@ -42,7 +44,8 @@ Register the same check for multiple post types by calling the function for each ```php foreach ( [ 'post', 'page' ] as $post_type ) { - validation_api_register_meta_check( $post_type, [ + wp_register_meta_validation_check( $post_type, [ + 'namespace' => 'my-plugin', 'name' => 'required', 'meta_key' => 'seo_description', 'level' => 'error', @@ -53,13 +56,13 @@ foreach ( [ 'post', 'page' ] as $post_type ) { ## Validation Logic (JavaScript) -Use the `validation_api_validate_meta` filter: +Use the `editor.validateMeta` filter: ```javascript import { addFilter } from '@wordpress/hooks'; addFilter( - 'validation_api_validate_meta', + 'editor.validateMeta', 'my-plugin/seo-description', ( isValid, value, postType, metaKey, checkName ) => { if ( metaKey === 'seo_description' && checkName === 'required' ) { @@ -131,7 +134,7 @@ Validator::required( string $post_type, string $meta_key, array $args = [] ): ca Use `Validator::required()` when you're already calling `register_post_meta()` and want both client and server validation in one step. -Use `validation_api_register_meta_check()` directly when: +Use `wp_register_meta_validation_check()` directly when: - You don't need server-side enforcement - You need a custom validation rule beyond "required" - You're validating meta that's registered elsewhere @@ -140,8 +143,8 @@ Use `validation_api_register_meta_check()` directly when: The meta validation path differs from block validation because meta values come from the post's metadata, not block attributes: -1. The Validation API reads registered meta checks from `window.ValidationAPI.metaValidationRules` +1. The Validation API reads registered meta checks from editor settings (`select('core/editor').getEditorSettings().validationApi.metaValidationRules`) 2. For each meta key with checks, the runner reads the current meta value via `wp.data` -3. The `validation_api_validate_meta` filter fires with the current value +3. The `editor.validateMeta` filter fires with the current value 4. Your callback returns pass/fail 5. Failed checks appear in the sidebar and contribute to publish locking diff --git a/docs/guide/severity.md b/docs/guide/severity.md index e65cee1..1762a3d 100644 --- a/docs/guide/severity.md +++ b/docs/guide/severity.md @@ -28,25 +28,27 @@ If you don't declare a `level`, it defaults to `error`: ```php // These are equivalent: -validation_api_register_block_check( 'core/image', [ +wp_register_block_validation_check( 'core/image', [ + 'namespace' => 'my-plugin', 'name' => 'alt_text', 'level' => 'error', 'error_msg' => 'Alt text is required.', ] ); -validation_api_register_block_check( 'core/image', [ +wp_register_block_validation_check( 'core/image', [ + 'namespace' => 'my-plugin', 'name' => 'alt_text', 'error_msg' => 'Alt text is required.', ] ); ``` -## The validation_api_check_level Filter +## The wp_validation_check_level Filter This is the central mechanism for severity configuration. Every active check (level is not `none`) passes through this filter before validation runs: ```php $effective_level = apply_filters( - 'validation_api_check_level', + 'wp_validation_check_level', $registered_level, $context ); @@ -95,7 +97,7 @@ This is the core architectural decision: the Validation API has no settings stor ### Override a Specific Check ```php -add_filter( 'validation_api_check_level', function( $level, $context ) { +add_filter( 'wp_validation_check_level', function( $level, $context ) { // Downgrade image alt text from error to warning if ( $context['scope'] === 'block' && $context['block_type'] === 'core/image' @@ -110,7 +112,7 @@ add_filter( 'validation_api_check_level', function( $level, $context ) { ### Disable a Check ```php -add_filter( 'validation_api_check_level', function( $level, $context ) { +add_filter( 'wp_validation_check_level', function( $level, $context ) { // Disable heading hierarchy check entirely if ( $context['check_name'] === 'heading_hierarchy' ) { return 'none'; @@ -122,7 +124,7 @@ add_filter( 'validation_api_check_level', function( $level, $context ) { ### Override All Checks from a Scope ```php -add_filter( 'validation_api_check_level', function( $level, $context ) { +add_filter( 'wp_validation_check_level', function( $level, $context ) { // Make all editor checks warnings instead of errors if ( $context['scope'] === 'editor' && $level === 'error' ) { return 'warning'; @@ -134,7 +136,7 @@ add_filter( 'validation_api_check_level', function( $level, $context ) { ### Override Based on Environment ```php -add_filter( 'validation_api_check_level', function( $level, $context ) { +add_filter( 'wp_validation_check_level', function( $level, $context ) { // In staging, downgrade all errors to warnings if ( wp_get_environment_type() === 'staging' && $level === 'error' ) { return 'warning'; @@ -145,11 +147,11 @@ add_filter( 'validation_api_check_level', function( $level, $context ) { ## How the Companion Settings Package Uses This -The [validation-api-settings](https://github.com/troychaplin/validation-api-settings) companion plugin provides an admin UI for overriding check severity. Under the hood, it stores overrides in `wp_options` and hooks into `validation_api_check_level`: +The [validation-api-settings](https://github.com/troychaplin/validation-api-settings) companion plugin provides an admin UI for overriding check severity. Under the hood, it stores overrides in `wp_options` and hooks into `wp_validation_check_level`: ```php // This is what the companion does internally: -add_filter( 'validation_api_check_level', function( $level, $context ) { +add_filter( 'wp_validation_check_level', function( $level, $context ) { $options = get_option( 'validation_api_settings', [] ); $key = $context['block_type'] . '_' . $context['check_name']; return $options[ $key ] ?? $level; diff --git a/docs/technical/README.md b/docs/technical/README.md index d9f9178..b7d5532 100644 --- a/docs/technical/README.md +++ b/docs/technical/README.md @@ -15,9 +15,9 @@ The Validation API is a two-layer system: PHP registries that collect check defi │ │ │ │ │ │ └────────┬────────┴──────────────────┘ │ │ │ │ -│ wp_localize_script │ +│ block_editor_settings_all filter │ │ │ │ -│ window.ValidationAPI │ +│ editorSettings.validationApi │ └──────────────────┼────────────────────────────────────────┘ │ ┌──────────────────┼────────────────────────────────────────┐ @@ -32,8 +32,8 @@ The Validation API is a two-layer system: PHP registries that collect check defi │ │ │ │ ┌───────┴────────┐ │ │ │ Data Store │ │ -│ │ (validation- │ │ -│ │ api) │ │ +│ │ (core/ │ │ +│ │ validation) │ │ │ └───────┬────────┘ │ │ │ │ │ ┌────────────┼────────────┐ │ @@ -54,8 +54,8 @@ The entry point is `validation_api_init_plugin()`, called on `init`. This bootst 1. Instantiates the three registries (Block, Meta, Editor) as singletons 2. Initializes the `Assets` class for script/style loading 3. Registers the REST API controller -4. Fires `validation_api_ready` (with Block Registry) and `validation_api_editor_checks_ready` (with Editor Registry) actions -5. Fires `validation_api_initialized` when setup is complete +4. Fires `wp_validation_ready` (with Block Registry) and `wp_validation_editor_checks_ready` (with Editor Registry) actions +5. Fires `wp_validation_initialized` when setup is complete ### Registries @@ -68,29 +68,23 @@ Each registry is a singleton that stores check definitions: All three follow the same patterns: - Registration methods that validate input and fire `should_register_*` / `check_args` filters - Get methods for retrieving checks by type, name, or all at once -- `get_effective_*_level()` methods that apply the `validation_api_check_level` filter +- `get_effective_*_level()` methods that apply the `wp_validation_check_level` filter -### PluginContext +### Namespace Field -The `PluginContext` class tracks which plugin is currently registering checks. When `validation_api_register_plugin()` is called, it: - -1. Sets the context (`PluginContext::set()`) -2. Executes the callback or CheckProvider classes -3. Clears the context (`PluginContext::clear()`) - -Checks registered during the callback automatically get a `_plugin` attribute with the plugin's name. This attribution appears in the REST API response and is used by the companion settings package. +The `namespace` field in check args tracks which plugin registered each check. All checks with the same `namespace` value are grouped together. This attribution appears in the REST API response and is used by the companion settings package. ### Assets The `ValidationAPI\Core\Assets` class handles: - Enqueuing the editor JavaScript bundle via `enqueue_block_editor_assets` -- Exporting check data via `wp_localize_script` to `window.ValidationAPI` +- Exporting check data via the `block_editor_settings_all` filter to `editorSettings.validationApi` - Editor context detection (post editor, site editor, template editing) ### REST API -The `ValidationAPI\Rest\ChecksController` registers `GET /validation-api/v1/checks`. It requires `manage_options` capability and returns all registered checks across all three scopes, including `_plugin` attribution. +The `ValidationAPI\Rest\ChecksController` registers `GET /wp/v2/validation-checks`. It requires `manage_options` capability and returns all registered checks across all three scopes, including `_namespace` attribution. ### Traits @@ -105,13 +99,13 @@ Two shared traits used by registry classes: Three runners, one per scope. Each subscribes to relevant store changes and re-runs validation when data changes: -- **Block Runner** (`validateBlock.js`) — Watches for block attribute changes. For each block with registered checks, fires the `validation_api_validate_block` filter. -- **Meta Runner** (`validateMeta.js`) — Watches for post meta changes. For each meta key with registered checks, fires the `validation_api_validate_meta` filter. -- **Editor Runner** (`validateEditor.js`) — Watches for block list changes. Fires the `validation_api_validate_editor` filter with the full blocks array. +- **Block Runner** (`validateBlock.js`) — Watches for block attribute changes. For each block with registered checks, fires the `editor.validateBlock` filter. +- **Meta Runner** (`validateMeta.js`) — Watches for post meta changes. For each meta key with registered checks, fires the `editor.validateMeta` filter. +- **Editor Runner** (`validateEditor.js`) — Watches for block list changes. Fires the `editor.validateEditor` filter with the full blocks array. ### Data Store -All validation state is centralized in a custom `@wordpress/data` store registered under the `validation-api` namespace. This eliminates duplicate computation and gives all consumers reactive access via selectors. +All validation state is centralized in a custom `@wordpress/data` store registered under the `core/validation` namespace. This eliminates duplicate computation and gives all consumers reactive access via selectors. **Producers:** @@ -143,16 +137,16 @@ All validation state is centralized in a custom `@wordpress/data` store register Consumers can query the store from the browser console: ```js -wp.data.select('validation-api').getInvalidBlocks() -wp.data.select('validation-api').hasErrors() +wp.data.select('core/validation').getInvalidBlocks() +wp.data.select('core/validation').hasErrors() ``` ### Coordinator The `ValidationAPI` component reads from the data store and manages publish locking: -- If any check fails at `error` level → `wp.data.dispatch('core/editor').lockPostSaving('validation-api')` -- When all errors resolve → `wp.data.dispatch('core/editor').unlockPostSaving('validation-api')` +- If any check fails at `error` level → `wp.data.dispatch('core/editor').lockPostSaving('core-validation')` +- When all errors resolve → `wp.data.dispatch('core/editor').unlockPostSaving('core-validation')` ### UI Components @@ -172,19 +166,19 @@ The JS entry point (`register.js`) renders: ### No Storage -The core plugin has no `wp_options`, no custom tables, no settings pages. Check definitions live in PHP memory (populated on each request), exported to JS via `wp_localize_script`. The `validation_api_check_level` filter is the extension point for runtime configuration. +The core plugin has no `wp_options`, no custom tables, no settings pages. Check definitions live in PHP memory (populated on each request), exported to JS via the `block_editor_settings_all` filter. The `wp_validation_check_level` filter is the extension point for runtime configuration. ### Filter-First Architecture Every significant behavior passes through a filter: -- Check args can be modified before registration (`validation_api_check_args`) -- Checks can be prevented from registering (`validation_api_should_register_check`) -- Severity is overridable at runtime (`validation_api_check_level`) -- Validation results come from JS filters (`validation_api_validate_block`, etc.) +- Check args can be modified before registration (`wp_validation_check_args`) +- Checks can be prevented from registering (`wp_validation_should_register_check`) +- Severity is overridable at runtime (`wp_validation_check_level`) +- Validation results come from JS filters (`editor.validateBlock`, etc.) ### Multi-Context Support -The plugin detects the editor context (`editorContext` in the localized data) and works in: +The plugin detects the editor context (`editorContext` in the settings data) and works in: - Post editor (standard editing) - Post editor in template mode - Site editor (full site editing) diff --git a/docs/technical/api.md b/docs/technical/api.md index 961f9cc..76e7a24 100644 --- a/docs/technical/api.md +++ b/docs/technical/api.md @@ -1,74 +1,47 @@ # API Reference -All public functions, registry methods, and contracts. +All public registration functions, registry methods, and contracts. ## Global Functions -### validation_api_register_plugin() - -Register a plugin and its validation checks with the Validation API. - -```php -validation_api_register_plugin( array $plugin_info, callable|array $checks ): void -``` - -| Parameter | Type | Description | -|---|---|---| -| `$plugin_info` | `array` | Plugin metadata. Required key: `'name'` (string). | -| `$checks` | `callable\|array` | A callable that registers checks, or an array of fully qualified `CheckProvider` class names. | - -Sets up the plugin context, executes check registration, then clears the context. All checks registered inside the callback or providers are attributed to the plugin. - -Triggers `_doing_it_wrong()` if: -- `$plugin_info` is missing the `'name'` key -- `$checks` is neither callable nor array -- A CheckProvider class doesn't exist -- A class doesn't implement `CheckProvider` - -### validation_api_register_block_check() +### wp_register_block_validation_check() Register a validation check for a block type. ```php -validation_api_register_block_check( string $block_type, array $args ): void +wp_register_block_validation_check( string $block_type, array $args ): void ``` | Parameter | Type | Description | |---|---|---| | `$block_type` | `string` | Block type name (e.g., `'core/image'`). | -| `$args` | `array` | Check configuration (see [Check Arguments](#check-arguments)). | +| `$args` | `array` | Check configuration (see [Check Arguments](#check-arguments)). Must include `'namespace'`. | -Must be called within a `validation_api_register_plugin()` context. - -### validation_api_register_meta_check() +### wp_register_meta_validation_check() Register a validation check for a post meta field. ```php -validation_api_register_meta_check( string $post_type, array $args ): void +wp_register_meta_validation_check( string $post_type, array $args ): void ``` | Parameter | Type | Description | |---|---|---| | `$post_type` | `string` | Post type (e.g., `'post'`, `'page'`). | -| `$args` | `array` | Check configuration. Must include `'meta_key'`. See [Check Arguments](#check-arguments). | - -Must be called within a `validation_api_register_plugin()` context. +| `$args` | `array` | Check configuration. Must include `'namespace'` and `'meta_key'`. See [Check Arguments](#check-arguments). | -### validation_api_register_editor_check() +### wp_register_editor_validation_check() Register a validation check for the editor (document-level). ```php -validation_api_register_editor_check( string $post_type, array $args ): void +wp_register_editor_validation_check( string $post_type, array $args ): void ``` | Parameter | Type | Description | |---|---|---| | `$post_type` | `string` | Post type (e.g., `'post'`, `'page'`). | -| `$args` | `array` | Check configuration (see [Check Arguments](#check-arguments)). | - -Must be called within a `validation_api_register_plugin()` context. +| `$args` | `array` | Check configuration (see [Check Arguments](#check-arguments)). Must include `'namespace'`. | ### validation_api_init_plugin() @@ -84,6 +57,7 @@ All registration functions accept an `$args` array with the following keys: | Key | Type | Required | Default | Description | |---|---|---|---|---| +| `namespace` | `string` | Yes | — | Identifier for the plugin registering this check | | `name` | `string` | Yes | — | Unique identifier for this check within its scope | | `error_msg` | `string` | Yes | — | Message shown when the check fails at error level | | `warning_msg` | `string` | No | Same as `error_msg` | Message shown at warning level | @@ -116,7 +90,7 @@ interface CheckProvider { } ``` -Implementations call global registration functions (`validation_api_register_block_check()`, etc.) inside `register()`. All checks are attributed to the parent plugin passed to `validation_api_register_plugin()`. +Implementations call global registration functions (`wp_register_block_validation_check()`, etc.) inside `register()`. All checks use the same `namespace` value to group them together. ## Registry Classes @@ -186,7 +160,7 @@ Returns a `callable` for use as the `validate_callback` parameter in `register_p ## REST API -### GET /validation-api/v1/checks +### GET /wp/v2/validation-checks Returns all registered checks across all three scopes. @@ -205,9 +179,7 @@ Returns all registered checks across all three scopes. "warning_msg": "Consider adding alt text.", "priority": 10, "enabled": true, - "_plugin": { - "name": "My Rules" - } + "_namespace": "my-rules" } } }, diff --git a/docs/technical/companion-package.md b/docs/technical/companion-package.md index b1e7b45..c233bd0 100644 --- a/docs/technical/companion-package.md +++ b/docs/technical/companion-package.md @@ -9,7 +9,7 @@ The Validation API is a pure framework — no settings UI, no storage, no opinio - Three registries (Block, Meta, Editor) - Registration functions and hooks - JS validation runners and UI components -- `validation_api_check_level` filter as the configuration extension point +- `wp_validation_check_level` filter as the configuration extension point - No `wp_options`, no settings page, no storage ### Layer 2: Settings Companion (separate plugin) @@ -17,7 +17,7 @@ The Validation API is a pure framework — no settings UI, no storage, no opinio - Admin settings page built on WordPress DataForm - Reads registered checks from the REST API - Writes severity overrides to `wp_options` -- Hooks into `validation_api_check_level` to apply overrides +- Hooks into `wp_validation_check_level` to apply overrides The core plugin has no knowledge of the companion. It fires the filter; the companion hooks in. Any plugin could replace the companion with a different settings implementation. @@ -28,10 +28,10 @@ The core plugin has no knowledge of the companion. It fires the filter; the comp The companion reads all registered checks via the REST API: ``` -GET /validation-api/v1/checks +GET /wp/v2/validation-checks ``` -This returns every check across all scopes, including `_plugin` attribution. The companion uses this to build its settings form dynamically — no hardcoded check list. +This returns every check across all scopes, including `_namespace` attribution. The companion uses this to build its settings form dynamically — no hardcoded check list. ### DataForm Integration @@ -67,7 +67,7 @@ update_option( 'validation_api_settings', [ The companion registers one filter that bridges `wp_options` to the check level system: ```php -add_filter( 'validation_api_check_level', function( $level, $context ) { +add_filter( 'wp_validation_check_level', function( $level, $context ) { $options = get_option( 'validation_api_settings', [] ); $key = $context['block_type'] . '_' . $context['check_name']; return $options[ $key ] ?? $level; @@ -88,7 +88,7 @@ WordPress core will not accept: WordPress core will accept: - Clean registries with hooks and filters - A severity model overridable via filters -- Standard `wp_localize_script` data flow +- Standard `block_editor_settings_all` data flow - UI components using existing SlotFills The split ensures the core plugin is mergeable while the settings layer stays in plugin-land. diff --git a/docs/technical/data-flow.md b/docs/technical/data-flow.md index 22d35c1..c1636d2 100644 --- a/docs/technical/data-flow.md +++ b/docs/technical/data-flow.md @@ -8,29 +8,18 @@ This document traces how a registered check moves from PHP registration through ```php add_action( 'init', function() { - validation_api_register_plugin( - [ 'name' => 'My Rules' ], - function() { - validation_api_register_block_check( 'core/image', [ - 'name' => 'alt_text', - 'level' => 'error', - 'error_msg' => 'Missing alt text.', - ] ); - } - ); + wp_register_block_validation_check( 'core/image', [ + 'namespace' => 'my-rules', + 'name' => 'alt_text', + 'level' => 'error', + 'error_msg' => 'Missing alt text.', + ] ); } ); ``` -### 2. PluginContext Scoping - -`validation_api_register_plugin()` wraps the callback in a context scope: +### 2. Namespace Attribution -``` -PluginContext::set( [ 'name' => 'My Rules' ] ) - → callback executes - → validation_api_register_block_check() called -PluginContext::clear() -``` +The `namespace` field in the check args identifies which plugin registered the check. All checks sharing the same `namespace` are grouped together in the REST API and companion settings. ### 3. Registry Storage @@ -44,15 +33,15 @@ $this->checks['core/image']['alt_text'] = [ 'priority' => 10, 'enabled' => true, 'description' => '', - '_plugin' => [ 'name' => 'My Rules' ], + '_namespace' => 'my-rules', ]; ``` Before storage, two filters fire: -- `validation_api_check_args` — allows modifying the check config -- `validation_api_should_register_check` — allows preventing registration +- `wp_validation_check_args` — allows modifying the check config +- `wp_validation_should_register_check` — allows preventing registration -After storage, the `validation_api_check_registered` action fires. +After storage, the `wp_validation_check_registered` action fires. ### 4. Effective Level Resolution @@ -60,7 +49,7 @@ When the Assets class exports data, each check's level is resolved through: ```php $effective_level = apply_filters( - 'validation_api_check_level', + 'wp_validation_check_level', $registered_level, [ 'scope' => 'block', @@ -74,19 +63,20 @@ If the companion settings package (or any filter) overrides the level, the expor ## Export Phase (PHP → JS) -### 5. wp_localize_script +### 5. Editor Settings via block_editor_settings_all -On `enqueue_block_editor_assets`, the Assets class exports all registry data to `window.ValidationAPI`: +On `block_editor_settings_all`, the Assets class exports all registry data to editor settings, accessible via `select('core/editor').getEditorSettings().validationApi`: ```javascript -window.ValidationAPI = { +// editorSettings.validationApi +{ editorContext: 'post-editor', validationRules: { 'core/image': { 'alt_text': { - error_msg: 'Missing alt text.', - warning_msg: 'Missing alt text.', + errorMsg: 'Missing alt text.', + warningMsg: 'Missing alt text.', level: 'error', // effective level after filters priority: 10, enabled: true, @@ -98,7 +88,7 @@ window.ValidationAPI = { metaValidationRules: { /* post_type → meta_key → check_name → config */ }, editorValidationRules: { /* post_type → check_name → config */ }, registeredBlockTypes: [ 'core/image' ], -}; +} ``` This is a one-time export. The JS layer reads this data and uses it for the entire editing session. @@ -107,7 +97,7 @@ This is a one-time export. The JS layer reads this data and uses it for the enti ### 6. Runner Initialization -When the editor loads, each runner reads its rules from `window.ValidationAPI`: +When the editor loads, each runner reads its rules from the editor settings: - Block Runner reads `validationRules` - Meta Runner reads `metaValidationRules` @@ -131,7 +121,7 @@ for ( const [ checkName, rule ] of Object.entries( checks ) ) { if ( rule.level === 'none' || ! rule.enabled ) continue; const isValid = applyFilters( - 'validation_api_validate_block', + 'editor.validateBlock', true, // default: valid blockType, // e.g., 'core/image' attributes, // block's current attributes @@ -145,11 +135,11 @@ for ( const [ checkName, rule ] of Object.entries( checks ) ) { ### 9. Data Store Dispatch -The `ValidationProvider` component calls all three validation hooks and dispatches results into the `validation-api` data store: +The `ValidationProvider` component calls all three validation hooks and dispatches results into the `core/validation` data store: ```javascript const invalidBlocks = GetInvalidBlocks(); -dispatch( 'validation-api' ).setInvalidBlocks( invalidBlocks ); +dispatch( 'core/validation' ).setInvalidBlocks( invalidBlocks ); ``` This is the single place where validation is computed. All downstream consumers read from the store via selectors, eliminating duplicate computation. @@ -163,12 +153,12 @@ Additionally, the `withErrorHandling` HOC dispatches per-block validation result The `ValidationAPI` component reads from the store and manages save locking: ```javascript -const hasBlockErrors = select( 'validation-api' ).hasErrors(); +const hasBlockErrors = select( 'core/validation' ).hasErrors(); if ( hasBlockErrors ) { - dispatch( 'core/editor' ).lockPostSaving( 'validation-api' ); + dispatch( 'core/editor' ).lockPostSaving( 'core-validation' ); } else { - dispatch( 'core/editor' ).unlockPostSaving( 'validation-api' ); + dispatch( 'core/editor' ).unlockPostSaving( 'core-validation' ); } ``` @@ -186,23 +176,23 @@ Two HOCs work together for block-level feedback: The `ValidationSidebar` reads from the store and renders: - Issues grouped by severity (errors first, then warnings) -- Each issue shows the message from `error_msg` or `warning_msg` based on level +- Each issue shows the message from `errorMsg` or `warningMsg` based on level - Click-to-navigate: clicking an issue selects and scrolls to the block ## Summary: The Full Path ``` PHP registration - → validation_api_check_args filter - → validation_api_should_register_check filter + → wp_validation_check_args filter + → wp_validation_should_register_check filter → Registry storage - → validation_api_check_level filter (on export) - → wp_localize_script → window.ValidationAPI + → wp_validation_check_level filter (on export) + → block_editor_settings_all → editorSettings.validationApi JS validation → wp.data change detection - → validation_api_validate_block filter (or _meta / _editor) - → ValidationProvider dispatches to validation-api store + → editor.validateBlock filter (or .validateMeta / .validateEditor) + → ValidationProvider dispatches to core/validation store UI rendering (all read from the store) → ValidationAPI: lockPostSaving / unlockPostSaving, body classes diff --git a/docs/technical/decisions.md b/docs/technical/decisions.md index 334d0e8..596773e 100644 --- a/docs/technical/decisions.md +++ b/docs/technical/decisions.md @@ -4,11 +4,11 @@ All resolved design decisions for the Validation API. This is a new identity and ## #1 — Hook Prefix -**Decision: `validation_api_*`** +**Decision: `wp_validation_*`** -All PHP actions/filters and JS filters use the `validation_api_` prefix. The old `ba11yc_` prefix tied the plugin to "Block Accessibility Checks", which was too narrow for a general validation framework. +All PHP actions/filters use the `wp_validation_` prefix. JS filters use `editor.*` namespacing. The old `ba11yc_` prefix tied the plugin to "Block Accessibility Checks", which was too narrow for a general validation framework. -The prefix is descriptive, follows WordPress conventions (`{plugin_slug}_*`), and signals that this is a general-purpose validation API — not an accessibility-specific tool. +The prefix is descriptive, follows WordPress conventions (`wp_{feature}_*`), and signals that this is a general-purpose validation API — not an accessibility-specific tool. ## #2 — PHP Namespace @@ -24,11 +24,11 @@ Namespace structure: - `ValidationAPI\Contracts\*` — Interfaces (CheckProvider) - `ValidationAPI\Rest\*` — REST API controllers -## #3 — Global JS Object +## #3 — Data Export Mechanism -**Decision: `window.ValidationAPI`** +**Decision: `block_editor_settings_all` filter → `editorSettings.validationApi`** -The `wp_localize_script` data is exposed as `window.ValidationAPI`. The old `window.BlockAccessibilityChecks` was both verbose and identity-specific. +Check data is exported to the editor via the `block_editor_settings_all` filter, accessible through `select('core/editor').getEditorSettings().validationApi`. This replaces the previous `wp_localize_script` / `window.ValidationAPI` approach and aligns with how WordPress core delivers editor configuration. ## #4 — Severity Model @@ -36,7 +36,7 @@ The `wp_localize_script` data is exposed as `window.ValidationAPI`. The old `win The old model had four explicit types: `error`, `warning`, `settings`, `none`. The `settings` type existed to mark checks as "configurable via the settings page." -The insight: **every check is inherently configurable** because every check passes through `validation_api_check_level`. You don't need to declare something as "configurable" — that's just the default behavior. The `settings` type was redundant. +The insight: **every check is inherently configurable** because every check passes through `wp_validation_check_level`. You don't need to declare something as "configurable" — that's just the default behavior. The `settings` type was redundant. New model: @@ -47,24 +47,24 @@ New model: | `none` | Check disabled. Filter does **not** fire. Skipped entirely. | | *(omitted)* | Defaults to `error`. Filter fires, can override. | -The `validation_api_check_level` filter is the settings mechanism: +The `wp_validation_check_level` filter is the settings mechanism: ```php -apply_filters( 'validation_api_check_level', $registered_level, $context ); +apply_filters( 'wp_validation_check_level', $registered_level, $context ); ``` The companion settings package hooks into this filter and reads from `wp_options`. The core plugin has no storage — it just fires the filter. This is the Gutenberg-merge-friendly pattern: zero storage opinions in the framework. ## #5 — Plugin Detection -**Decision: Removed entirely.** +**Decision: Replaced with `namespace` field.** The old plugin used `debug_backtrace()` to auto-detect which plugin registered a check. This was: - A performance concern (stack inspection on every registration) - Fragile (sensitive to call stack depth, mu-plugins, closures) - Only useful for grouping in the settings UI -The replacement is `validation_api_register_plugin()`, which requires plugins to declare their identity explicitly. This is cleaner, faster, and more reliable. The `_plugin` attribution appears in the REST API and companion settings. +The replacement is the `namespace` field in check args, which requires plugins to declare their identity explicitly. This is cleaner, faster, and more reliable. The `_namespace` attribution appears in the REST API and companion settings. ## #6 — Text Domain @@ -80,7 +80,7 @@ This is a new identity, not an upgrade path from the old plugin. Starting at `1. ## #8 — Registration Pattern -**Decision: Global functions wrapped in `validation_api_register_plugin()`** +**Decision: Flat global functions with `namespace` field** The old pattern required hooking into `ba11yc_ready` and calling registry methods directly: @@ -91,17 +91,19 @@ add_action( 'ba11yc_ready', function( $registry ) { } ); ``` -The new pattern uses global functions within a plugin scope: +The new pattern uses flat global functions with a `namespace` field: ```php // New -validation_api_register_plugin( [ 'name' => 'My Plugin' ], function() { - validation_api_register_block_check( 'core/image', [ ... ] ); -} ); +wp_register_block_validation_check( 'core/image', [ + 'namespace' => 'my-plugin', + 'name' => 'alt_text', + 'error_msg' => 'Alt text required.', +] ); ``` Benefits: -- Plugin identity is declared once, not inferred +- Plugin identity is declared per-check via `namespace`, not inferred - No need to know about registry instances - The `function_exists` guard is clean and obvious - CheckProvider classes can call the same global functions @@ -110,18 +112,18 @@ Benefits: **Decision: Removed entirely.** -The old `category` parameter (`'accessibility'`, `'validation'`) was only used for grouping in the settings UI. With the settings UI moved to the companion package, the core API has no need for categories. The companion can implement its own grouping logic (by plugin name, by scope, etc.) without the core API being aware of it. +The old `category` parameter (`'accessibility'`, `'validation'`) was only used for grouping in the settings UI. With the settings UI moved to the companion package, the core API has no need for categories. The companion can implement its own grouping logic (by namespace, by scope, etc.) without the core API being aware of it. ## Summary | # | Question | Decision | Rationale | |---|---|---|---| -| 1 | Hook prefix | `validation_api_*` | General-purpose identity | +| 1 | Hook prefix | `wp_validation_*` (PHP), `editor.*` (JS) | Core-aligned naming | | 2 | PHP namespace | `ValidationAPI\*` | Matches new identity | -| 3 | JS global | `window.ValidationAPI` | Concise, matches identity | +| 3 | Data export | `block_editor_settings_all` filter | Core-aligned pattern | | 4 | Severity model | 3 levels, filter-first | All checks are configurable by default | -| 5 | Plugin detection | Removed | Explicit `register_plugin()` is cleaner | +| 5 | Plugin detection | `namespace` field | Explicit declaration is cleaner | | 6 | Text domain | `validation-api` | Matches plugin slug | | 7 | Version | `1.0.0` | Clean break, new identity | -| 8 | Registration | Global functions + plugin scope | No registry knowledge needed | +| 8 | Registration | Flat global functions + `namespace` | No wrapper function needed | | 9 | Category param | Removed | Companion handles grouping | diff --git a/docs/technical/hooks.md b/docs/technical/hooks.md index 80929d0..2eb18dd 100644 --- a/docs/technical/hooks.md +++ b/docs/technical/hooks.md @@ -4,24 +4,24 @@ Complete list of PHP actions, PHP filters, and JavaScript filters provided by th ## PHP Actions -### validation_api_initialized +### wp_validation_initialized Fires after plugin initialization completes. ```php -do_action( 'validation_api_initialized', Plugin $plugin ); +do_action( 'wp_validation_initialized', Plugin $plugin ); ``` | Parameter | Type | Description | |---|---|---| | `$plugin` | `ValidationAPI\Core\Plugin` | The plugin instance | -### validation_api_ready +### wp_validation_ready -Fires after the Block Registry is ready. Use this to interact with the block registry directly (most integrations should use `validation_api_register_plugin()` instead). +Fires after the Block Registry is ready. Use this to interact with the block registry directly (most integrations should use `wp_register_block_validation_check()` instead). ```php -do_action( 'validation_api_ready', BlockChecksRegistry $registry, Plugin $plugin ); +do_action( 'wp_validation_ready', BlockChecksRegistry $registry, Plugin $plugin ); ``` | Parameter | Type | Description | @@ -29,12 +29,12 @@ do_action( 'validation_api_ready', BlockChecksRegistry $registry, Plugin $plugin | `$registry` | `ValidationAPI\Block\Registry` | The block checks registry | | `$plugin` | `ValidationAPI\Core\Plugin` | The plugin instance | -### validation_api_editor_checks_ready +### wp_validation_editor_checks_ready Fires after the Editor Registry is ready. ```php -do_action( 'validation_api_editor_checks_ready', EditorChecksRegistry $registry, Plugin $plugin ); +do_action( 'wp_validation_editor_checks_ready', EditorChecksRegistry $registry, Plugin $plugin ); ``` | Parameter | Type | Description | @@ -42,12 +42,12 @@ do_action( 'validation_api_editor_checks_ready', EditorChecksRegistry $registry, | `$registry` | `ValidationAPI\Editor\Registry` | The editor checks registry | | `$plugin` | `ValidationAPI\Core\Plugin` | The plugin instance | -### validation_api_check_registered +### wp_validation_check_registered Fires when a block check is successfully registered. ```php -do_action( 'validation_api_check_registered', string $block_type, string $check_name, array $check_args ); +do_action( 'wp_validation_check_registered', string $block_type, string $check_name, array $check_args ); ``` | Parameter | Type | Description | @@ -56,12 +56,12 @@ do_action( 'validation_api_check_registered', string $block_type, string $check_ | `$check_name` | `string` | Check identifier | | `$check_args` | `array` | The final check configuration | -### validation_api_check_unregistered +### wp_validation_check_unregistered Fires when a block check is unregistered. ```php -do_action( 'validation_api_check_unregistered', string $block_type, string $check_name ); +do_action( 'wp_validation_check_unregistered', string $block_type, string $check_name ); ``` | Parameter | Type | Description | @@ -69,12 +69,12 @@ do_action( 'validation_api_check_unregistered', string $block_type, string $chec | `$block_type` | `string` | Block type name | | `$check_name` | `string` | Check identifier | -### validation_api_check_toggled +### wp_validation_check_toggled Fires when a block check is enabled or disabled. ```php -do_action( 'validation_api_check_toggled', string $block_type, string $check_name, bool $enabled ); +do_action( 'wp_validation_check_toggled', string $block_type, string $check_name, bool $enabled ); ``` | Parameter | Type | Description | @@ -83,12 +83,12 @@ do_action( 'validation_api_check_toggled', string $block_type, string $check_nam | `$check_name` | `string` | Check identifier | | `$enabled` | `bool` | Whether the check is now enabled | -### validation_api_editor_check_registered +### wp_validation_editor_check_registered Fires when an editor check is successfully registered. ```php -do_action( 'validation_api_editor_check_registered', string $post_type, string $check_name, array $check_args ); +do_action( 'wp_validation_editor_check_registered', string $post_type, string $check_name, array $check_args ); ``` | Parameter | Type | Description | @@ -97,12 +97,12 @@ do_action( 'validation_api_editor_check_registered', string $post_type, string $ | `$check_name` | `string` | Check identifier | | `$check_args` | `array` | The final check configuration | -### validation_api_meta_check_registered +### wp_validation_meta_check_registered Fires when a meta check is successfully registered. ```php -do_action( 'validation_api_meta_check_registered', string $post_type, string $meta_key, string $check_name, array $check_args ); +do_action( 'wp_validation_meta_check_registered', string $post_type, string $meta_key, string $check_name, array $check_args ); ``` | Parameter | Type | Description | @@ -114,12 +114,12 @@ do_action( 'validation_api_meta_check_registered', string $post_type, string $me ## PHP Filters -### validation_api_check_args +### wp_validation_check_args Modify block check arguments before registration. ```php -$check_args = apply_filters( 'validation_api_check_args', array $check_args, string $block_type, string $check_name ); +$check_args = apply_filters( 'wp_validation_check_args', array $check_args, string $block_type, string $check_name ); ``` | Parameter | Type | Description | @@ -130,12 +130,12 @@ $check_args = apply_filters( 'validation_api_check_args', array $check_args, str **Return:** `array` — Modified check arguments. -### validation_api_editor_check_args +### wp_validation_editor_check_args Modify editor check arguments before registration. ```php -$check_args = apply_filters( 'validation_api_editor_check_args', array $check_args, string $post_type, string $check_name ); +$check_args = apply_filters( 'wp_validation_editor_check_args', array $check_args, string $post_type, string $check_name ); ``` | Parameter | Type | Description | @@ -146,12 +146,12 @@ $check_args = apply_filters( 'validation_api_editor_check_args', array $check_ar **Return:** `array` — Modified check arguments. -### validation_api_meta_check_args +### wp_validation_meta_check_args Modify meta check arguments before registration. ```php -$check_args = apply_filters( 'validation_api_meta_check_args', array $check_args, string $post_type, string $meta_key, string $check_name ); +$check_args = apply_filters( 'wp_validation_meta_check_args', array $check_args, string $post_type, string $meta_key, string $check_name ); ``` | Parameter | Type | Description | @@ -163,12 +163,12 @@ $check_args = apply_filters( 'validation_api_meta_check_args', array $check_args **Return:** `array` — Modified check arguments. -### validation_api_should_register_check +### wp_validation_should_register_check Prevent a block check from being registered. ```php -$should = apply_filters( 'validation_api_should_register_check', bool $should, string $block_type, string $check_name, array $check_args ); +$should = apply_filters( 'wp_validation_should_register_check', bool $should, string $block_type, string $check_name, array $check_args ); ``` | Parameter | Type | Description | @@ -180,12 +180,12 @@ $should = apply_filters( 'validation_api_should_register_check', bool $should, s **Return:** `bool` — `false` to prevent registration. -### validation_api_should_register_editor_check +### wp_validation_should_register_editor_check Prevent an editor check from being registered. ```php -$should = apply_filters( 'validation_api_should_register_editor_check', bool $should, string $post_type, string $check_name, array $check_args ); +$should = apply_filters( 'wp_validation_should_register_editor_check', bool $should, string $post_type, string $check_name, array $check_args ); ``` | Parameter | Type | Description | @@ -197,12 +197,12 @@ $should = apply_filters( 'validation_api_should_register_editor_check', bool $sh **Return:** `bool` — `false` to prevent registration. -### validation_api_should_register_meta_check +### wp_validation_should_register_meta_check Prevent a meta check from being registered. ```php -$should = apply_filters( 'validation_api_should_register_meta_check', bool $should, string $post_type, string $meta_key, string $check_name, array $check_args ); +$should = apply_filters( 'wp_validation_should_register_meta_check', bool $should, string $post_type, string $meta_key, string $check_name, array $check_args ); ``` | Parameter | Type | Description | @@ -215,12 +215,12 @@ $should = apply_filters( 'validation_api_should_register_meta_check', bool $shou **Return:** `bool` — `false` to prevent registration. -### validation_api_check_level +### wp_validation_check_level Override the effective severity level of any check at runtime. This is the central configuration mechanism. ```php -$level = apply_filters( 'validation_api_check_level', string $level, array $context ); +$level = apply_filters( 'wp_validation_check_level', string $level, array $context ); ``` | Parameter | Type | Description | @@ -245,12 +245,12 @@ $level = apply_filters( 'validation_api_check_level', string $level, array $cont [ 'scope' => 'editor', 'post_type' => 'post', 'check_name' => 'heading_hierarchy' ] ``` -### validation_api_validate_meta +### wp_validation_validate_meta Server-side meta validation filter. Fires during `register_post_meta()` validate_callback when using `Validator::required()`. ```php -$is_valid = apply_filters( 'validation_api_validate_meta', bool $is_valid, mixed $value, string $post_type, string $meta_key, string $check_name, array $config ); +$is_valid = apply_filters( 'wp_validation_validate_meta', bool $is_valid, mixed $value, string $post_type, string $meta_key, string $check_name, array $config ); ``` | Parameter | Type | Description | @@ -268,13 +268,13 @@ $is_valid = apply_filters( 'validation_api_validate_meta', bool $is_valid, mixed All JS filters use `@wordpress/hooks` (`wp.hooks`). Register with `addFilter()`, imported from `@wordpress/hooks`. -### validation_api_validate_block +### editor.validateBlock Validate a block's attributes against a registered check. ```javascript const isValid = applyFilters( - 'validation_api_validate_block', + 'editor.validateBlock', true, blockType, attributes, @@ -293,13 +293,13 @@ const isValid = applyFilters( **Return:** `boolean` — `true` if valid, `false` if invalid. -### validation_api_validate_meta +### editor.validateMeta Validate a post meta value against a registered check. ```javascript const isValid = applyFilters( - 'validation_api_validate_meta', + 'editor.validateMeta', true, value, postType, @@ -318,13 +318,13 @@ const isValid = applyFilters( **Return:** `boolean` — `true` if valid, `false` if invalid. -### validation_api_validate_editor +### editor.validateEditor Validate the editor's block content against a registered check. ```javascript const isValid = applyFilters( - 'validation_api_validate_editor', + 'editor.validateEditor', true, blocks, postType, diff --git a/includes/Block/Registry.php b/includes/Block/Registry.php index 67307ce..e8a84bf 100644 --- a/includes/Block/Registry.php +++ b/includes/Block/Registry.php @@ -10,7 +10,6 @@ namespace ValidationAPI\Block; -use ValidationAPI\Core\PluginContext; use ValidationAPI\Core\Traits\Logger; /** @@ -112,10 +111,10 @@ public function register_check( string $block_type, string $check_name, array $c } // Allow developers to filter check arguments before registration. - $check_args = \apply_filters( 'validation_api_check_args', $check_args, $block_type, $check_name ); + $check_args = \apply_filters( 'wp_validation_check_args', $check_args, $block_type, $check_name ); // Allow developers to prevent specific checks from being registered. - if ( ! \apply_filters( 'validation_api_should_register_check', true, $block_type, $check_name, $check_args ) ) { + if ( ! \apply_filters( 'wp_validation_should_register_check', true, $block_type, $check_name, $check_args ) ) { $this->log_debug( "Check registration prevented by filter: {$block_type}/{$check_name}" ); return false; } @@ -130,10 +129,10 @@ public function register_check( string $block_type, string $check_name, array $c $this->checks[ $block_type ] = array(); } - // Stamp plugin attribution from active context. - $plugin_context = PluginContext::get(); - if ( null !== $plugin_context ) { - $check_args['_plugin'] = $plugin_context; + // Stamp namespace attribution from registration args. + if ( ! empty( $check_args['namespace'] ) ) { + $check_args['_namespace'] = $check_args['namespace']; + unset( $check_args['namespace'] ); } // Store the check. @@ -143,7 +142,7 @@ public function register_check( string $block_type, string $check_name, array $c \uasort( $this->checks[ $block_type ], array( $this, 'sort_checks_by_priority' ) ); // Action hook for developers to know when a validation check is registered. - \do_action( 'validation_api_check_registered', $block_type, $check_name, $check_args ); + \do_action( 'wp_validation_check_registered', $block_type, $check_name, $check_args ); return true; @@ -172,7 +171,7 @@ public function unregister_check( string $block_type, string $check_name ): bool } // Action hook for developers to know when a validation check is unregistered. - \do_action( 'validation_api_check_unregistered', $block_type, $check_name ); + \do_action( 'wp_validation_check_unregistered', $block_type, $check_name ); return true; } @@ -193,7 +192,7 @@ public function set_check_enabled( string $block_type, string $check_name, bool $this->checks[ $block_type ][ $check_name ]['enabled'] = (bool) $enabled; // Action hook for developers to know when a validation check is enabled/disabled. - \do_action( 'validation_api_check_toggled', $block_type, $check_name, $enabled ); + \do_action( 'wp_validation_check_toggled', $block_type, $check_name, $enabled ); return true; } @@ -266,7 +265,7 @@ private function sort_checks_by_priority( $a, $b ) { /** * Get the effective check level for a specific check * - * Passes the registered level through the validation_api_check_level filter, + * Passes the registered level through the wp_validation_check_level filter, * allowing external plugins (e.g. a settings companion) to override the level * at runtime. Checks set to 'none' are skipped without firing the filter. * @@ -289,7 +288,7 @@ public function get_effective_check_level( string $block_type, string $check_nam } return \apply_filters( - 'validation_api_check_level', + 'wp_validation_check_level', $check_type, array( 'scope' => 'block', diff --git a/includes/Core/Assets.php b/includes/Core/Assets.php index 8122c28..dab2aaf 100644 --- a/includes/Core/Assets.php +++ b/includes/Core/Assets.php @@ -69,6 +69,9 @@ class Assets { public function __construct( string $plugin_file, I18n $translations ) { $this->plugin_file = $plugin_file; $this->translations = $translations; + + // Inject validation config into editor settings instead of using wp_localize_script. + add_filter( 'block_editor_settings_all', array( $this, 'inject_editor_settings' ), 10, 2 ); } /** @@ -104,8 +107,27 @@ private function enqueue_block_scripts() { VALIDATION_API_VERSION, true ); + } + + /** + * Inject validation configuration into the block editor settings. + * + * Uses the `block_editor_settings_all` filter to pass validation rules + * and editor context through Gutenberg's standard editor settings mechanism, + * replacing the previous `wp_localize_script` approach. + * + * The config is available in JS via: + * `select('core/editor').getEditorSettings().validationApi` + * + * @param array $settings The editor settings array. + * @param \WP_Block_Editor_Context $context The block editor context. + * @return array Modified editor settings with validation config. + */ + public function inject_editor_settings( array $settings, $context ): array { + if ( ! $this->should_load_validation() ) { + return $settings; + } - // Get the registries to expose validation rules to JavaScript. $registry = BlockRegistry::get_instance(); $meta_registry = MetaRegistry::get_instance(); $editor_registry = EditorRegistry::get_instance(); @@ -114,17 +136,15 @@ private function enqueue_block_scripts() { $editor_validation_rules = $this->prepare_editor_validation_rules_for_js( $editor_registry ); $registered_block_types = $registry->get_registered_block_types(); - \wp_localize_script( - self::VALIDATION_SCRIPT_HANDLE, - 'ValidationAPI', - array( - 'editorContext' => $this->get_editor_context(), - 'validationRules' => $validation_rules, - 'metaValidationRules' => $meta_validation_rules, - 'editorValidationRules' => $editor_validation_rules, - 'registeredBlockTypes' => $registered_block_types, - ) + $settings['validationApi'] = array( + 'editorContext' => $this->get_editor_context(), + 'validationRules' => $validation_rules, + 'metaValidationRules' => $meta_validation_rules, + 'editorValidationRules' => $editor_validation_rules, + 'registeredBlockTypes' => $registered_block_types, ); + + return $settings; } /** diff --git a/includes/Core/Plugin.php b/includes/Core/Plugin.php index d054a1a..020c250 100644 --- a/includes/Core/Plugin.php +++ b/includes/Core/Plugin.php @@ -78,13 +78,13 @@ public function init(): void { $this->init_rest_api(); // Allow other plugins to hook into our initialization. - \do_action( 'validation_api_initialized', $this ); + \do_action( 'wp_validation_initialized', $this ); // Allow developers to access the registry and add custom checks. - \do_action( 'validation_api_ready', $this->get_service( 'block_checks_registry' ), $this ); + \do_action( 'wp_validation_ready', $this->get_service( 'block_checks_registry' ), $this ); // Allow developers to register editor checks. - \do_action( 'validation_api_editor_checks_ready', $this->get_service( 'editor_checks_registry' ), $this ); + \do_action( 'wp_validation_editor_checks_ready', $this->get_service( 'editor_checks_registry' ), $this ); } catch ( \Exception $e ) { $this->log_error( 'Failed to initialize plugin: ' . $e->getMessage() ); diff --git a/includes/Core/PluginContext.php b/includes/Core/PluginContext.php deleted file mode 100644 index dd8f917..0000000 --- a/includes/Core/PluginContext.php +++ /dev/null @@ -1,57 +0,0 @@ -log_debug( "Editor check registration prevented by filter: {$post_type}/{$check_name}" ); return false; } @@ -132,10 +131,10 @@ public function register_editor_check( string $post_type, string $check_name, ar $this->editor_checks[ $post_type ] = array(); } - // Stamp plugin attribution from active context. - $plugin_context = PluginContext::get(); - if ( null !== $plugin_context ) { - $check_args['_plugin'] = $plugin_context; + // Stamp namespace attribution from registration args. + if ( ! empty( $check_args['namespace'] ) ) { + $check_args['_namespace'] = $check_args['namespace']; + unset( $check_args['namespace'] ); } // Store the check. @@ -145,7 +144,7 @@ public function register_editor_check( string $post_type, string $check_name, ar \uasort( $this->editor_checks[ $post_type ], array( $this, 'sort_checks_by_priority' ) ); // Action hook for developers to know when a check is registered. - \do_action( 'validation_api_editor_check_registered', $post_type, $check_name, $check_args ); + \do_action( 'wp_validation_editor_check_registered', $post_type, $check_name, $check_args ); return true; @@ -210,7 +209,7 @@ public function get_editor_check_config( string $post_type, string $check_name ) /** * Get the effective check level for a specific editor check * - * Passes the registered level through the validation_api_check_level filter, + * Passes the registered level through the wp_validation_check_level filter, * allowing external plugins (e.g. a settings companion) to override the level * at runtime. Checks set to 'none' are skipped without firing the filter. * @@ -233,7 +232,7 @@ public function get_effective_editor_check_level( string $post_type, string $che } return \apply_filters( - 'validation_api_check_level', + 'wp_validation_check_level', $check_type, array( 'scope' => 'editor', diff --git a/includes/Meta/Registry.php b/includes/Meta/Registry.php index ac7176a..8f2dfb7 100644 --- a/includes/Meta/Registry.php +++ b/includes/Meta/Registry.php @@ -10,7 +10,6 @@ namespace ValidationAPI\Meta; -use ValidationAPI\Core\PluginContext; use ValidationAPI\Core\Traits\Logger; /** @@ -125,10 +124,10 @@ public function register_meta_check( string $post_type, string $meta_key, string } // Allow developers to filter check arguments before registration. - $check_args = \apply_filters( 'validation_api_meta_check_args', $check_args, $post_type, $meta_key, $check_name ); + $check_args = \apply_filters( 'wp_validation_meta_check_args', $check_args, $post_type, $meta_key, $check_name ); // Allow developers to prevent specific checks from being registered. - if ( ! \apply_filters( 'validation_api_should_register_meta_check', true, $post_type, $meta_key, $check_name, $check_args ) ) { + if ( ! \apply_filters( 'wp_validation_should_register_meta_check', true, $post_type, $meta_key, $check_name, $check_args ) ) { $this->log_debug( "Meta check registration prevented by filter: {$post_type}/{$meta_key}/{$check_name}" ); return false; } @@ -143,10 +142,10 @@ public function register_meta_check( string $post_type, string $meta_key, string $this->meta_checks[ $post_type ][ $meta_key ] = array(); } - // Stamp plugin attribution from active context. - $plugin_context = PluginContext::get(); - if ( null !== $plugin_context ) { - $check_args['_plugin'] = $plugin_context; + // Stamp namespace attribution from registration args. + if ( ! empty( $check_args['namespace'] ) ) { + $check_args['_namespace'] = $check_args['namespace']; + unset( $check_args['namespace'] ); } // Store the check. @@ -156,7 +155,7 @@ public function register_meta_check( string $post_type, string $meta_key, string \uasort( $this->meta_checks[ $post_type ][ $meta_key ], array( $this, 'sort_checks_by_priority' ) ); // Action hook for developers to know when a check is registered. - \do_action( 'validation_api_meta_check_registered', $post_type, $meta_key, $check_name, $check_args ); + \do_action( 'wp_validation_meta_check_registered', $post_type, $meta_key, $check_name, $check_args ); return true; @@ -204,7 +203,7 @@ public function get_meta_check_config( string $post_type, string $meta_key, stri /** * Get the effective check level for a specific meta check * - * Passes the registered level through the validation_api_check_level filter, + * Passes the registered level through the wp_validation_check_level filter, * allowing external plugins (e.g. a settings companion) to override the level * at runtime. Checks set to 'none' are skipped without firing the filter. * @@ -228,7 +227,7 @@ public function get_effective_meta_check_level( string $post_type, string $meta_ } return \apply_filters( - 'validation_api_check_level', + 'wp_validation_check_level', $check_type, array( 'scope' => 'meta', diff --git a/includes/Meta/Validator.php b/includes/Meta/Validator.php index 71097ff..8bb0510 100644 --- a/includes/Meta/Validator.php +++ b/includes/Meta/Validator.php @@ -79,7 +79,7 @@ public static function required( string $post_type, string $meta_key, array $arg // Run validation through filter system. $is_valid = \apply_filters( - 'validation_api_validate_meta', + 'wp_validation_validate_meta', true, $value, $post_type, @@ -96,7 +96,7 @@ public static function required( string $post_type, string $meta_key, array $arg // Return error only if validation fails and level is 'error'. if ( ! $is_valid && 'error' === $level ) { return new \WP_Error( - 'validation_api_validation_failed', + 'wp_validation_failed', $config['error_msg'], array( 'status' => 400 ) ); diff --git a/includes/Rest/ChecksController.php b/includes/Rest/ChecksController.php index b961ad9..51b7f60 100644 --- a/includes/Rest/ChecksController.php +++ b/includes/Rest/ChecksController.php @@ -31,14 +31,14 @@ class ChecksController extends WP_REST_Controller { * * @var string */ - protected $namespace = 'validation-api/v1'; + protected $namespace = 'wp/v2'; /** * The base for this controller's routes. * * @var string */ - protected $rest_base = 'checks'; + protected $rest_base = 'validation-checks'; /** * Register the routes for this controller. @@ -182,7 +182,7 @@ private function format_check( array $check_args ): array { 'priority' => $check_args['priority'] ?? 10, 'enabled' => $check_args['enabled'] ?? true, 'configurable' => $check_args['configurable'] ?? true, - '_plugin' => $check_args['_plugin'] ?? null, + '_namespace' => $check_args['_namespace'] ?? null, ); } @@ -224,15 +224,9 @@ public function get_item_schema(): array { 'type' => 'boolean', 'description' => __( 'Whether the check is enabled.', 'validation-api' ), ), - '_plugin' => array( - 'type' => array( 'object', 'null' ), - 'description' => __( 'The plugin that registered this check, or null if unattributed.', 'validation-api' ), - 'properties' => array( - 'name' => array( - 'type' => 'string', - 'description' => __( 'Display name of the registering plugin.', 'validation-api' ), - ), - ), + '_namespace' => array( + 'type' => array( 'string', 'null' ), + 'description' => __( 'The namespace of the plugin that registered this check, or null if unattributed.', 'validation-api' ), ), ), ); diff --git a/src/editor/components/ValidationSidebar.js b/src/editor/components/ValidationSidebar.js index 4375227..d023d32 100644 --- a/src/editor/components/ValidationSidebar.js +++ b/src/editor/components/ValidationSidebar.js @@ -59,7 +59,7 @@ function deduplicateBlockIssues(blocks, severity) { issues.forEach(issue => { const message = - severity === 'error' ? issue.error_msg : issue.warning_msg || issue.error_msg; + severity === 'error' ? issue.errorMsg : issue.warningMsg || issue.errorMsg; const key = `${block.name}|${message}`; if (!issueMap.has(key)) { @@ -99,7 +99,7 @@ function deduplicateMetaIssues(metaArray, severity) { issues.forEach(issue => { const message = - severity === 'error' ? issue.error_msg : issue.warning_msg || issue.error_msg; + severity === 'error' ? issue.errorMsg : issue.warningMsg || issue.errorMsg; const key = `${meta.metaKey}|${message}`; if (!issueMap.has(key)) { @@ -127,11 +127,7 @@ function deduplicateEditorIssues(issues, severity) { const issueMap = new Map(); issues.forEach(issue => { - // Handle both camelCase (errorMsg) and snake_case (error_msg) for compatibility - const message = - severity === 'error' - ? issue.errorMsg || issue.error_msg - : issue.warningMsg || issue.warning_msg || issue.errorMsg || issue.error_msg; + const message = severity === 'error' ? issue.errorMsg : issue.warningMsg || issue.errorMsg; const key = message; if (!issueMap.has(key)) { diff --git a/src/editor/components/ValidationToolbarButton.js b/src/editor/components/ValidationToolbarButton.js index 10ea73b..28fcc6b 100644 --- a/src/editor/components/ValidationToolbarButton.js +++ b/src/editor/components/ValidationToolbarButton.js @@ -65,7 +65,7 @@ export function ValidationToolbarButton({ issues }) {