Skip to content

fix(content): keep widget field values as text unless the widget allows field scripting - #38282

Merged
fabrizzio-dotCMS merged 6 commits into
mainfrom
widget-field-scripting-content-render
Oct 29, 2026
Merged

fabrizzio-dotCMS merged 6 commits into
mainfrom
widget-field-scripting-content-render

Conversation

@fabrizzio-dotCMS

Copy link
Copy Markdown
Member

What

RenderFieldStrategy decided on its own whether to evaluate a widget's field
values, so the content endpoints that render fields disagreed with the page
render path, which has always asked WidgetRenderer first. The decision now
lives in one place, RenderFieldStrategy.rendersFieldValues:

  • WIDGET content answers through WidgetRenderer.rendersFieldVelocity
  • every other base type, and a null contentlet, answers as it did before

WidgetRenderer.rendersFieldVelocity became public for that, with its Javadoc
reworded to say it is now the decision for the content endpoints too.

widgetCodeJSON follows the same rule, but only when the caller asked for
RENDER_FIELDS — a widget whose field values stay as text reports an empty
map, which is the answer a widget that wrote no $dotJSON already produces, so
the key never disappears from the response.

DotTransformerBuilder.renderFields(true) now also clears
SKIP_WIDGET_CODE_RENDERING, so the two options cannot contradict each other.

Scope

WIDGET base type only. Nothing changes for other content types, for the page
render path, or for /api/widget/.

Testing

New ContentMapWidgetFieldRenderTest (9 cases) covers both answers of the
rule, the flag off, widgetCodeJSON empty vs present, a non-widget still
rendering, type-level fields, a system-user modUser, and the static
renderFieldValue helper. Registered at the end of MainSuite2a
(shortest-running compatible suite: median 23.7 min vs 40-46 min for the
alternatives across PRs 38226, 38219, 38213, 38210, 38206).

Ran locally with the build cache disabled, together with the four neighbouring
classes that exercise the same strategies:

ContentMapWidgetFieldRenderTest   9
WidgetRendererTest               11
ContentletTransformerTest        33
WidgetViewStrategyTest            3
ContentletUtilTest                5
Tests run: 61, Failures: 0, Errors: 0, Skipped: 0

Fixes dotCMS/private-issues#707

🤖 Generated with Claude Code

…ws field scripting

The content endpoints that render field values (/api/content/render/true/...,
_search with "render":"true", and the GraphQL field fetchers) evaluated a
widget's field values regardless of who last saved the widget, while the page
render path already decided this through WidgetRenderer. Both now ask the same
question through RenderFieldStrategy.rendersFieldValues, which defers to
WidgetRenderer.rendersFieldVelocity for WIDGET content and leaves every other
base type as it was.

widgetCodeJSON follows the same rule when the caller asked for rendered fields:
a widget whose field values stay as text reports an empty map, the same answer
a widget that wrote no $dotJSON already gives.

Fixes dotCMS/private-issues#707

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added the Area : Backend PR changes Java/Maven backend code label Oct 27, 2026
@claude

claude Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @fabrizzio-dotCMS's task in 1m 56s —— View job


Code Review

  • Read the full diff against origin/main
  • Review backend strategy changes for bugs / conventions
  • Review test coverage & suite registration
  • Post review findings

I traced the new RenderFieldStrategy.rendersFieldValues gate through all three callers (RenderFieldStrategy.transform, WidgetViewStrategy, and the renderFieldValue overloads used by ContentMapDataFetcher / FieldDataFetcher), the WidgetRenderer.rendersFieldVelocity visibility change, and the DotTransformerBuilder option symmetry.

New Issues

No correctness, security, or convention bugs found.

Notes (non-blocking, all appear intentional and are already covered by review/approval):

  • Single decision point is sound. rendersFieldValues null-guards the contentlet/content type and short-circuits to true for non-widgets, so only WIDGET content is gated — matching the stated scope. WidgetRenderer.getInstance() is a Lazy singleton and the last-modifier lookup (loadUserById, loadRoleByKey, doesUserHaveRole) all hit cached APIs, so resolving the rule once per map in RenderFieldStrategy.transform:75 and again in WidgetViewStrategy:57 is cheap. GraphQL fetchers resolve it per field, but those are cache hits — not worth changing.

  • widgetCodeJSON forced empty is broader than "field values". WidgetViewStrategy:56-64 returns Collections.emptyMap() for widgetCodeJSON whenever the widget's field scripting is off and RENDER_FIELDS is set — even if the widget code wrote a $dotJSON that doesn't depend on any field value. This is deliberate per the dotmarketing-config.properties doc ("The rule covers … widgetCodeJSON") and the key is preserved (same shape as a widget that wrote no $dotJSON), so clients don't see a disappearing key. Flagging only so it's a conscious contract choice, which it clearly is.

  • Contract change is default-on (the M-3 rollback note already captured this). GraphQL and the content REST transform now return raw field text for any widget whose last modifier lacks the Scripting Developer role, under the existing default-true FEATURE_FLAG_WIDGET_FIELD_VELOCITY_RENDER. Two reviewers approved; no action needed from a bug standpoint.

  • Suite placement. MainSuite2a.java:119 adds the new class in the middle of @SuiteClasses rather than at the end as the CLAUDE.md gate suggests. The dedicated commit (dc601ff9) shows this was intentional (merge-conflict avoidance); the class is in the list, so CI runs it. Not a bug.

The DotTransformerBuilder.renderFields change is now symmetric (RENDER_FIELDS ⇄ SKIP_WIDGET_CODE_RENDERING can no longer both be set), and the ContentResourceTest checkin-as-Scripting-Developer change correctly preserves the old "rendered" assertion.

Verdict: No blocking issues. Clean, well-documented, and well-tested.
· widget-field-scripting-content-render

@ihoffmann-dot ihoffmann-dot left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved ✅

Widget field values now follow the same rule as the widget endpoint: Velocity runs only when the feature flag is on and the widget's last modifier has the Scripting Developer role, otherwise the values come back as text.
The content REST endpoints, widgetCodeJSON and the GraphQL field helper all share that rule through RenderFieldStrategy.rendersFieldValues, and non-widget content types render as before.
ContentMapWidgetFieldRenderTest covers the main cases (no role, role, flag off, system user, widgetCodeJSON, GraphQL helper) and is registered in MainSuite2a, so CI runs it.

…g-content-render

# Conflicts:
#	dotcms-integration/src/test/java/com/dotcms/MainSuite2a.java
@fabrizzio-dotCMS
fabrizzio-dotCMS force-pushed the widget-field-scripting-content-render branch from bd3eaf5 to b49d139 Compare October 27, 2026 21:55
A widget's field values are evaluated as Velocity only when its last modifier
holds the Scripting Developer role, so testRenderWidgetCode now checks the
widget in as a user that holds it instead of relying on the data generator's
default modifier.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@fabrizzio-dotCMS
fabrizzio-dotCMS added this pull request to the merge queue Oct 28, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to no response for status checks Oct 28, 2026
@fabrizzio-dotCMS
fabrizzio-dotCMS added this pull request to the merge queue Oct 28, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Oct 28, 2026
…g-content-render

# Conflicts:
#	dotcms-integration/src/test/java/com/dotcms/MainSuite2a.java
@claude

claude Bot commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

Pull Request Unsafe to Rollback!!!

  • Category: M-3 — REST / GraphQL / Headless API Contract Change
  • Risk Level: 🟡 MEDIUM
  • Why it's unsafe: This PR changes the default response content of the content REST transform pipeline (any caller using DotTransformerBuilder.renderFields(true) / the RENDER_FIELDS option) for Widget contentlets. Previously, a widget's field values were unconditionally evaluated as Velocity and returned as rendered text. Now, RenderFieldStrategy.rendersFieldValues(Contentlet) gates that: field values are only rendered as Velocity when the widget's last modifier holds the "Scripting Developer" role (and the FEATURE_FLAG_WIDGET_FIELD_VELOCITY_RENDER flag, default true, is on); otherwise the raw, unrendered field text is returned instead, and widgetCodeJSON is forced to an empty map. The integration test change (ContentResourceTest.java) confirms this: it now has to check in content as a newly-created "Scripting Developer" user (getScriptingDeveloperUser()) for the old "rendered" assertion to still pass. Headless/API clients that come to depend on N's new default (raw text for widgets whose last modifier lacks that role) will silently get different response content — rendered Velocity output again — if the app is rolled back to N-1, which has no such gate.
  • Code that makes it unsafe:
    • dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/transform/strategy/RenderFieldStrategy.java — new rendersFieldValues(Contentlet) gate (lines ~90-106) and the new renderFieldValue(..., boolean renderVelocity) overload that returns the raw fieldValue when renderVelocity is false (around the if (!renderVelocity) { return fieldValue; } block).
    • dotCMS/src/main/java/com/dotmarketing/portlets/contentlet/transform/strategy/WidgetViewStrategy.java — new block forcing map.put(WidgetContentType.WIDGET_CODE_JSON_FIELD_VAR, Collections.emptyMap()) when options.contains(TransformOptions.RENDER_FIELDS) && !RenderFieldStrategy.rendersFieldValues(widget).
    • dotCMS/src/main/java/com/dotcms/rendering/velocity/WidgetRenderer.java — rendersFieldVelocity(Contentlet) visibility widened from package-private to public so the above can call it as the gate.
  • Alternative (if possible): Gate the new "raw text for a non-scripting-developer-modified widget" behavior behind its own dedicated, default-off feature flag rather than folding it into the existing FEATURE_FLAG_WIDGET_FIELD_VELOCITY_RENDER default-true flag for this new REST/content-map code path. That lets operators keep the pre-PR unconditional-render contract active for existing API consumers, and opt in to the new gated behavior deliberately — avoiding a default-on, silent response-shape change for any Widget content whose last modifier isn't a Scripting Developer.

Registering at the end of @SuiteClasses conflicts with every other PR
that appends there, so the entry is placed mid-list instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@wezell wezell left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get'er merged

@fabrizzio-dotCMS
fabrizzio-dotCMS added this pull request to the merge queue Oct 28, 2026
Merged via the queue into main with commit 0f31065 Oct 29, 2026
73 checks passed
@fabrizzio-dotCMS
fabrizzio-dotCMS deleted the widget-field-scripting-content-render branch October 29, 2026 02:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI: Safe To Rollback Area : Backend PR changes Java/Maven backend code

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

4 participants