chore(Internal HTTP API): #3591669 Heuristics for the content-entity-reference selection UI
Closes #3591669 (closed)
What this MR does
Applies heuristics to the content-entity-reference selection UI ("Typed Data browser" picker) so it only offers data a Code Component Developer can meaningfully consume, and mirrors the same rules at save time so storage rejects what the picker hides.
- Image field (
ApiUiContentEntityReferenceControllers::buildFieldEntry()): the picker listed the same image URL twice —srcand thesrc_with_alternate_widthsit is cloned from — plussrcset_candidate_uri_template, a raw RFC 6570 URI template. It now offers only the developer-facingsrc, relabelled "Image URL" inImageItemOverride, and excludes the two implementation-detail computed properties by name. Done picker-side only, soPropSourceSuggesterkeeps offering them (it has different needs). - Translation/revision metadata (
ApiUiContentEntityReferenceControllers::listFields()): a newisConsideredIrrelevant()heuristic hidesdefault_langcode,revision_log,revision_translation_affected, and thecontent_translation_source/content_translation_outdatedbase fields.revision_translation_affectedis resolved via$entity_type->getKey('revision_translation_affected')— a default entity key likedefault_langcode, not somethinggetRevisionMetadataKey()resolves. - Formatted text fields (
text,text_long,text_with_summary): hide the rawvalue/format/summary, keeping only the computedprocessed/summary_processed. - Raw, non-navigable URIs: any
uri-typed property lacking aUriSchemeConstraintrestricted to http/https is hidden — it is not guaranteed to be browser-navigable. This started as alink-field-specific check (rawuri, e.g.entity:node/1) and was generalized into a shape-based rule, which also caught a leak the by-class check missed: the file entity's own raw stream-wrapperuri(e.g.public://image.jpg). - Datetime fields: computed
DrupalDateTime-object properties (date,start_date,end_date) crashed rendering with apreg_match()TypeError when picked. Fixed as a consequence of item 6 below (they are explicitly marked internal, so the corrected internal-property rule now hides them); no datetime-specific guard was needed once that was fixed. isInternal()computed-property blind spot:DataDefinitionInterface::isInternal()cannot distinguish an explicit->setInternal(TRUE)from the value it defaults to for computed properties — soisInternal() && !isComputed()silently failed to exclude a property that is both computed and explicitly internal (e.g.date, viaDateTimeItemOverride). Fixed by inspecting the raw definition array (TypedDataHelper::isExplicitlyInternal()/isEffectivelyInternal()), converging the picker,EntityFieldPropSourceMatcher, and the storage-level validator onto shared helpers. A new PHPStan rule (RequireTypedDataHelperForInternalCheckRule) forbids directisInternal()/['internal']checks outsideTypedDataHelpergoing forward.- Storage-level enforcement:
EntityFieldExpressionMustNotTargetInternalPropertyConstrainthad the same blind spot as item 6, so a hand-crafted expression targetingdatewas never actually rejected at save time before this fix. A new, equivalent constraint,EntityFieldExpressionMustNotTargetRawUriProperty, mirrors item 4's rule at save time.
Each concern is in a separate commit for reviewability.
Testing steps
- Apply the branch, enable the
canvas_dev_ermodule (the feature flag for content-entity-reference props) and clear caches. - In the code editor, add a
Content entity referenceprop and browse into a bundle that has an image field. - Confirm the image field offers a single "Image URL" property — not "Resolved image URL with ?alternateWidths query parameter", a duplicate, or the srcset template.
- Browse into a translatable, revisionable content type (e.g. an Article with translation enabled).
- Confirm "Default translation", "Revision translation affected", "Translation source", "Translation outdated" and "Revision log message" are not offered, while real fields (including the entity's own
langcode) still are. - Browse into a bundle with a formatted text field (
text/text_long/text_with_summary); confirm onlyprocessed/summary_processedare offered, not the rawvalue/format/summary. - Browse into a bundle with a
linkfield; confirm onlyurl/title/optionsare offered, not the rawuri. - Browse into an image field's referenced
fileentity; confirm only the computedurlis offered for the file's ownurifield, not the raw stream-wrappervalue. - Browse into a bundle with a datetime or daterange field; confirm the computed
date/start_date/end_dateare not offered. - Automated coverage:
ApiUiContentEntityReferenceControllersTest,JavaScriptComponentValidationTest(internal-property and raw-URIentityFieldsrejection cases),PropExpressionKernelTest.
Notes for reviewers
- Hiding the image twins is done in the controller, not at the field-type level: marking them internal there would also hide them from
PropSourceSuggester, which still relies onsrc_with_alternate_widthsandsrcset_candidate_uri_template.'is source for'can't drive it either — the matcher reads it the other way around (it hidessrc). - The image twins are excluded by name (not "keep only
src") so any future image property stays offered by default. src_with_alternate_widthsis slated for removal in #3591648.PropSourceSuggesterisn't touched by the raw-URI generalization (item 4): it doesn't yet hidecontent_translation_source/content_translation_outdatedeither, and the two surfaces'isConsideredIrrelevant()methods were deliberately not forced into one shared implementation — they serve different audiences and may diverge further. Left as a follow-up, not in scope here.
AI usage
AI-Generated: Yes (used Claude Code to plan, implement and write tests).
Edited by Christian López Espínola