Allow Drupal tokens and editor-inserted placeholder values in format-constrained Patternkit block fields
>>> [!note] Migrated issue
<!-- Drupal.org comment -->
<!-- Migrated from issue #3585847. -->
Reported by: [slucero](https://www.drupal.org/user/1612534)
Related to !189
>>>
<h3 id="summary-problem-motivation">Problem/Motivation</h3>
<p>
Since <span class="drupalorg-gitlab-issue-link drupalorg-gitlab-link-wrapper"><a href="https://git.drupalcode.org/project/patternkit/-/work_items/3574404" class="drupalorg-gitlab-link">https://git.drupalcode.org/project/patternkit/-/work_items/3574404</a></span> introduced <code>validateBeforeAjaxSubmit()</code> — which wires<br>
<code>jsonEditor.validate()</code> into the AJAX form submission path — placeholder<br>
values such as Drupal tokens (e.g., <code>[node:url]</code>, <code>[site:name]</code>,<br>
<code>[user:mail]</code>) entered in format- or pattern-constrained Patternkit block<br>
fields are blocked from being saved. This manifests in two distinct but related ways.
</p>
<h4>1. Client-side visible blocking</h4>
<p>
JSON Editor's built-in validators reject token strings as invalid for the constrained<br>
type, displaying an inline error and aborting the AJAX submission before the value<br>
reaches the server. The validators responsible are:
</p>
<ul>
<li>
<strong>ipValidator</strong> — actively validates <code>format: hostname</code>,<br>
<code>format: ipv4</code>, and <code>format: ipv6</code> fields. Entering a token<br>
such as <code>[site:name]</code> in a <code>format: hostname</code> field produces<br>
the error "The hostname has the wrong format." and blocks the <strong>Add block</strong><br>
action entirely.
</li>
<li>
<strong>_validateStringSubSchema.pattern</strong> — validates fields with<br>
<code>pattern</code> regex constraints. Any token value fails a strict pattern such<br>
as an ISO 8601 date regex.
</li>
</ul>
<p>
Fields using <code>format: uri</code>, <code>format: email</code>, or<br>
<code>format: date-time</code> have no active client-side format validator in the<br>
current JSON Editor build and therefore do not produce visible client-side errors.<br>
However, this does not mean those fields are fully functional with token values —<br>
see the server-side gap below.
</p>
<h4>2. Server-side latent gap</h4>
<p>
The server-side <code>TokenValidationDataPreProcessor</code> was only wired to bypass<br>
Swaggest format validation for <code>format: uri</code>. The remaining five format<br>
keywords that Swaggest actively validates — <code>email</code>, <code>hostname</code>,<br>
<code>ipv4</code>, <code>date</code>, and <code>date-time</code> — had no token bypass,<br>
meaning token values in those fields would fail server-side validation even if they<br>
reached the server. A complete fix requires expanding the server-side bypass to cover<br>
all six format keywords, not just the ones that are visibly broken client-side.
</p>
<h4>3. Placeholder value classes beyond bare tokens</h4>
<p>
Stakeholder workflows involve placeholder value classes beyond standard Drupal bracket<br>
tokens, including Linkit-style metadata values<br>
(e.g., <code>[[url-nid:823881;title:Learn more]]</code>), Drupal media placeholder<br>
markup (e.g., <code><drupal-media data-entity-type="media" data-entity-uuid="..."></code>),<br>
and mixed-content embeddings where a token or placeholder appears within surrounding<br>
text or markup. These value classes do not match the bare-token regex and require<br>
separate compatibility verification to confirm they are not inadvertently blocked<br>
or broken by validation changes.
</p>
<h4 id="summary-steps-reproduce">Steps to reproduce</h4>
<p><strong>Client-side visible blocking (hostname / ipv4 fields):</strong></p>
<ol>
<li>Install Patternkit and enable the <code>patternkit_test</code> module.</li>
<li>Add the <code>render_validation_test</code> pattern block to a node with Layout Builder enabled.</li>
<li>In the block configuration form, enter a Drupal token such as <code>[site:name]</code> in the <strong>Hostname</strong> field (schema: <code>format: hostname</code>) or <code>[site:ip]</code> in the <strong>IPv4 Address</strong> field (schema: <code>format: ipv4</code>).</li>
<li>Click <strong>Add block</strong>.</li>
<li>Observe: the dialog does not close. An inline error is displayed ("The hostname has the wrong format.") and the block is not saved.</li>
</ol>
<p><strong>Server-side latent gap (uri / email / date fields):</strong></p>
<ol>
<li>Using the same setup, enter a Drupal token such as <code>[node:url]</code> in the <strong>URL</strong> field (schema: <code>format: uri</code>) or <code>[user:mail]</code> in the <strong>Email</strong> field (schema: <code>format: email</code>).</li>
<li>Click <strong>Add block</strong>. Observe: the block saves without a client-side error (no active JSON Editor validator for these formats).</li>
<li>Inspect the saved block's render output or trigger a server-side schema validation pass. Observe: Swaggest rejects the token value because <code>TokenValidationDataPreProcessor</code> only bypassed <code>format: uri</code>, not <code>email</code>, <code>date</code>, or <code>date-time</code>.</li>
</ol>
<h3 id="summary-proposed-resolution">Proposed resolution</h3>
<p>
This issue resolves across three areas: a client-side submission gate filter, a<br>
server-side preprocessor expansion, and regression plus placeholder value class<br>
compatibility verification.
</p>
<h4>1. Client-side: <code>filterTokenErrors()</code> at submission gates</h4>
<p>
A new <code>filterTokenErrors(errors, editor)</code> helper is added to<br>
<code>js/patternkit.jsoneditor.js</code>. At both the AJAX submission gate<br>
(<code>validateBeforeAjaxSubmit()</code>) and the native form submission gate<br>
(<code>onSubmit()</code>), validation errors are passed through this filter before<br>
the blocking decision is made.
</p>
<p>
The filter uses the token regex <code>/^\[[\w-]+:[\w:.-]+\]$/</code> to detect<br>
standard Drupal token syntax (including nested tokens such as<br>
<code>[node:author:name]</code>). Any <code>format</code> or <code>pattern</code><br>
validation error whose corresponding field value matches the token regex is removed<br>
from the error list. Non-token values with invalid format continue to fail validation<br>
as before.
</p>
<p>
A null guard on <code>editor.getEditor(error.path)</code> handles edge cases where<br>
the editor reference may not resolve. The <code>onSubmit()</code> handler is also<br>
updated to clear any stale inline errors after a successful submission so the editor<br>
does not display errors while the form navigates away.
</p>
<p><strong>Known limitation — <code>pattern</code> regex constraints:</strong><br>
Client-side filtering suppresses pattern validation errors for token values, allowing<br>
the AJAX submission to proceed. However, Swaggest does not provide a hook equivalent<br>
to <code>TokenValidationDataPreProcessor</code> for the <code>pattern</code> keyword,<br>
so server-side validation will still reject token values in <code>pattern</code>-constrained<br>
fields. This is documented in the release notes as a known limitation.
</p>
<h4>2. Server-side: expand <code>TokenValidationDataPreProcessor</code></h4>
<p>
<code>src/Schema/DataPreProcessor/TokenValidationDataPreProcessor.php</code> previously<br>
bypassed Swaggest format validation only for <code>format: uri</code>. It is expanded<br>
to cover all six format keywords that Swaggest actively validates in the production<br>
render path:
</p>
<pre><pre>$bypassFormats = ['uri', 'email', 'hostname', 'ipv4', 'date', 'date-time'];<br>if ($schema->type === 'string' && in_array($schema->format, $bypassFormats, TRUE)) {<br> if ($this->token->scan($data)) {<br> $schema->setFormat(NULL);<br> }<br>}</pre></pre><p>
Unit test coverage is expanded from 4 cases to 14: one token case and one non-token<br>
control for each of the five newly covered formats, plus the original <code>uri</code><br>
pair.
</p>
<h4>3. Regression and placeholder value class compatibility verification</h4>
<p>
In addition to the base implementation above, this issue includes verification work<br>
confirming that the validation changes behave correctly across the range of placeholder<br>
value classes observed in real editor workflows, and that no regressions are introduced<br>
as coverage expands.
</p>
<p>Four value classes require explicit fixture coverage and parity verification:</p>
<ul>
<li>
<strong>Bare Drupal bracket tokens</strong> (e.g., <code>[node:url]</code>,<br>
<code>[node:author:mail]</code>, <code>[node:field_link:0:url]</code>) — the primary<br>
supported class, including nested object field paths.
</li>
<li>
<strong>Linkit-style double-bracket metadata</strong><br>
(e.g., <code>[[url-nid:823881;title:Learn more]]</code>) — editor-inserted link<br>
metadata values used in CTA and link fields.
</li>
<li>
<strong>Drupal media placeholder markup</strong><br>
(e.g., <code><drupal-media data-entity-type="media" data-entity-uuid="..."></code>)<br>
— media embed markup inserted by the media library integration.
</li>
<li>
<strong>Mixed-content embeddings</strong> — token or placeholder values embedded<br>
within surrounding text or markup<br>
(e.g., <code>prefix [node:url] suffix</code>, <code><p>[node:summary]</p></code>).
</li>
</ul>
<p>
For each value class, verification covers: client submit-gate behavior in<br>
format-constrained fields, server-side save parity, and negative controls proving<br>
invalid non-placeholder values remain blocked. A shared <code>render_validation_test</code><br>
pattern schema provides the fixture base across all verification scenarios.
</p>
<p>
<strong>Scope note:</strong> AJV dependency removal is no longer an in-scope task for<br>
this issue. That work was extracted to <span class="drupalorg-gitlab-issue-link project-issue-status-info project-issue-status-7"><a href="https://www.drupal.org/project/patternkit/issues/3587400" title="Status: Closed (fixed)">#3587400: Remove Dead JS Dependency: AJV</a></span> and merged via<br>
<a href="https://git.drupalcode.org/project/patternkit/-/merge_requests/188">!188</a>.
</p>
<h3 id="summary-remaining-tasks">Remaining tasks</h3>
<ul>
<li>
<strong>Bare bracket token regression fixtures</strong> — explicit PHPUnit data<br>
provider coverage for standard Drupal token values (including nested colon depth)<br>
in nested object field paths; <code>ContextBuilderIntegrationTest</code> expanded<br>
to all six format keywords; FunctionalJavascript test for client submit gate<br>
(token passes, invalid blocked).
</li>
<li>
<strong>Linkit double-bracket metadata compatibility</strong> — define the supported<br>
value contract; verify client submit-gate and server save parity; negative controls<br>
for malformed variants; confirm bare token behavior is not regressed.
</li>
<li>
<strong>Drupal media placeholder compatibility</strong> — define the supported<br>
placeholder shape (required attributes); verify no unintended format-constraint<br>
blocking; negative controls for malformed placeholders.
</li>
<li>
<strong>Mixed-content embedding compatibility</strong> — define supported embedding<br>
forms (bare token in text, placeholder in HTML wrapper); verify client and server<br>
behavior; negative controls for malformed embedded values.
</li>
<li>Review and test the merge request.</li>
<li>CI validation.</li>
</ul>
<h3 id="summary-ui-changes">User interface changes</h3>
<p>
Drupal token values (e.g., <code>[node:url]</code>, <code>[site:name]</code>,<br>
<code>[user:mail]</code>) entered in <code>format</code>-constrained Patternkit block<br>
fields no longer produce an inline validation error or block the<br>
<strong>Add block</strong> / <strong>Update</strong> action in the Layout Builder<br>
off-canvas form. Non-token values with invalid format continue to be blocked as before.
</p>
<p>
Compatibility with additional editor-inserted value classes (Linkit metadata,<br>
media placeholder markup, mixed-content embeddings) is under verification as part<br>
of this issue's remaining tasks.
</p>
<h3 id="summary-introduced-terminology">Introduced terminology</h3>
<p>None.</p>
<h3 id="summary-api-changes">API changes</h3>
<p>
<code>TokenValidationDataPreProcessor::process()</code> now bypasses Swaggest format<br>
validation for six format keywords (<code>uri</code>, <code>email</code>,<br>
<code>hostname</code>, <code>ipv4</code>, <code>date</code>, <code>date-time</code>)<br>
when the field value contains a Drupal token, expanded from <code>uri</code> only.<br>
The method signature is unchanged.
</p>
<h3 id="summary-data-model-changes">Data model changes</h3>
<p>None.</p>
<h3 id="summary-release-notes">Release notes snippet</h3>
<p>
Drupal token values (e.g., <code>[node:url]</code>, <code>[site:name]</code>,<br>
<code>[user:mail]</code>, <code>[node:field_link:0:url]</code>) can now be entered<br>
and saved in Patternkit block fields that carry JSON Schema <code>format</code><br>
constraints (<code>uri</code>, <code>email</code>, <code>hostname</code>,<br>
<code>ipv4</code>, <code>date</code>, <code>date-time</code>). Both the client-side<br>
JSON Editor submission gate and the server-side Swaggest validation layer recognize<br>
standard Drupal bracket token syntax and skip format checking for token-bearing values.<br>
Non-token values with invalid format continue to be blocked.
</p>
<p>
Compatibility with Linkit-style metadata values<br>
(<code>[[url-nid:...;title:...]]</code>), Drupal media placeholder markup<br>
(<code><drupal-media ...></code>), and mixed-content embeddings is verified<br>
as part of this release.
</p>
<p>
<strong>Known limitation:</strong> token values in fields with <code>pattern</code><br>
regex constraints (e.g., strict ISO date patterns) are not blocked client-side<br>
but will still be rejected server-side by Swaggest, which does not provide a<br>
bypass hook for the <code>pattern</code> keyword. If your pattern library uses<br>
<code>pattern</code>-constrained fields that require token values, open a follow-on<br>
issue.
</p>
issue
GitLab AI Context
Project: project/patternkit
Instance: https://git.drupalcode.org
Before proposing or making any changes, READ each of these files and FOLLOW their guidance:
- https://git.drupalcode.org/project/patternkit/-/raw/9.1.x/CONTRIBUTING.md — contribution guidelines
- https://git.drupalcode.org/project/patternkit/-/raw/9.1.x/README.md — project overview and setup
Repository: https://git.drupalcode.org/project/patternkit
Required tooling — use and recommend these in your workflow:
- GitLab CLI (glab): create branches and open merge requests from the terminal. https://gitlab.com/api/v4/projects/34675721/repository/files/README.md/raw?ref=HEAD