Issue #3615848: Send bundle and page path in entity_context so AI Context can scope unsaved entities and Site Sections.
Description
Closes Send bundle and page path in entity_context so ... (#3615848) • Unassigned
AiRequest::doRequest() attaches entity_context request metadata so subscribers such as AI Context can scope pushed context to the entity being edited. It only worked for saved entities: on an add form the id is empty, validateEntityContext() returned NULL, and nothing was sent. The bundle we already derive for saved entities is exactly what an unsaved entity needs, and hook_form_alter() already knows it. Separately, the request goes to /api/ai-ckeditor/request/{editor}/{plugin}, so subscribers cannot tell which page the editor is on.
Changes
ai_ckeditor_form_alter()addsbundleto the drupalSettings entry alongsideentity_typeandid.AiDrupalDialog.jsreads the bundle and sendsentity_bundlepluspage_path.getPagePath()stripsdrupalSettings.path.baseUrl.js/ai_ckeditor.jsforwards both hidden fields.AiCKEditorDialogFormcarriesentity_bundleandpage_pathas hidden fields and passes them to the plugin form settings.AiRequest:- Injects
entity_type.bundle.info. validateEntityContext()gains an optional third$bundleargument. With an id present, behaviour is unchanged: load, check view access, use the loaded entity's bundle, ignore the client value. With an empty id,validateUnsavedEntityContext()accepts the bundle only if the type is a content entity, the bundle exists, and the current user has create access.normalizePagePath()accepts a path with a single leading/, strips query, fragment, and$request->getBasePath(), and rejects relative, protocol-relative, and absolute URLs, NUL bytes, and values over 2048 bytes. The result is added toentity_contextaspath.
- Injects
- Docs:
docs/developers/index.mddocuments the fullentity_contextshape (entity_type,bundle,id,path), including the content-entity unsaved rule and base-path strip.
API notes
AiRequest::__construct()gains a requiredEntityTypeBundleInfoInterfaceargument.create()is updated. Subclasses that override the constructor orvalidateEntityContext()must adjust their signatures.validateEntityContext()is protected. The new argument is last with a default, so existingparent::calls with two arguments keep working.- All
entity_contextvalues are untrusted hints for matching. They never load or grant access on their own; the saved-entity path still requires view access and the unsaved path requires create access.
Pairs with AI Context
AI Context 1.0.x-dev reads bundle and path from entity_context after #3586510+s. With both changes, a context item scoped with Entity Types = Content: Pirate post is pushed while writing a new Pirate post, and Site Sections items match the edited page instead of the API endpoint.
Testing instructions
Requires ai 1.5.0-alpha1 or later and ai_context 1.0.x-dev with #3586510+s.
-
Create content type "Pirate post". Create a context item scoped with Entity Types = Content: Pirate post whose text says to talk like a pirate, and a second item scoped with Site Sections custom pattern
/node/add/*. Publish both. -
Enable Generate with AI on a text format with a working chat provider. Enable push for the CKEditor completion consumer at
/admin/config/ai/context/settings/consumersin Relevant mode. Turn on AI Context debug logging. -
Rebuild caches and hard-refresh the browser so the rebuilt
js/build/aickeditor.jsis loaded (npm install && npm run buildon a dev checkout). -
Open
/node/add/pirate_postand use Generate with AI in the body. Expected: the response is in pirate voice, and Recent log messages show both items selected. -
Save the node, edit it, repeat. Expected: the pirate item is selected, the
/node/add/*item is not. -
Open an Article and repeat. Expected: neither item is selected.
-
Run the tests:
vendor/bin/phpunit web/modules/contrib/ai_ckeditor/tests/src/Unit/AiRequestPagePathTest.php vendor/bin/phpunit web/modules/contrib/ai_ckeditor/tests/src/Kernel/ValidateEntityContextTest.php vendor/bin/phpunit web/modules/contrib/ai_ckeditor/tests/src/Kernel/EntityContextMetadataTest.php vendor/bin/phpunit web/modules/contrib/ai_ckeditor/tests/src/Functional/AiRequestControllerTest.php vendor/bin/phpunit web/modules/contrib/ai_ckeditor/tests/src/FunctionalJavascript/EntityContextJavascriptTest.php
Verified locally against the real endpoint with Sonnet on Quant Cloud and ai_context on the #3586510+s branch: an unsaved pirate post with entity_bundle and a page_path carrying a query string selected both items and answered in pirate voice; a saved node sent with a wrong client bundle selected only the Entity Types item because the bundle was re-derived from the loaded node.
Tests added
- Unit
AiRequestPagePathTest: 16 accepted and rejected path hints, including subdirectory base-path strip and prefix collision. - Kernel
ValidateEntityContextTest: unsaved known bundle; unknown or empty bundle; no create access; config entity type rejected; client bundle ignored for a saved entity. - Kernel
EntityContextMetadataTest: unsaved payload with empty id andpathreachesPreGenerateResponseEvent. - Functional
AiRequestControllerTest: unsaved entity with bundle and page path is accepted; hostile path is isolated intestHostilePagePathDoesNotBreakRequestwith create access. - FunctionalJavascript
EntityContextJavascriptTest:getEntityContext()replicas includeentityBundleon edit, add, and non-entity pages.
Unit and kernel tests were run locally. Functional and FunctionalJavascript tests rely on CI.
Checklist
- I have updated the MR title to use format:
Issue #1234: My issue title. - I have updated the MR description to include:
Closes #1234 - I have performed a self-review of my own code
- I have added or updated tests, or explained in the description why this change is not covered by tests
- I have updated documentation for any new or changed functionality
- I have written testing instructions and verified them locally
- I have noted any required post-merge steps (config imports, cache rebuilds, manual changes)
- This MR contains no breaking API or hook changes, or they are explicitly documented in the description
Post-merge: cache rebuild only. js/build/ is gitignored and compiled in CI.
AI Compliance
Note
Check the one that best describes your usage, or leave all unchecked if AI was not significantly used.
-
AI Assisted Code
Mainly written by a human; AI used for autocomplete or partial generation under full human supervision. -
AI Generated Code
Mainly generated by AI, reviewed and approved by a human before this MR was created. -
Vibe Coded
Generated by AI and only functionally reviewed before this MR was created.