This developer note documents the BricksMembers Structure Content Editor runtime: route rendering, selected-post payloads, per-post save ownership, native Content Fields handling, compatible custom field handling, and the guardrails that keep content editing separate from hierarchy mutation.
Scope
The Structure Content Editor is the in-admin content editing mode at admin.php?page=brm_structure_{structure_id}&mode=content. It lets structure editors select a post from a navigation-only tree, edit that post’s title/body/settings, and save without entering Structure view Edit Mode.
It does not own structure CRUD, parent assignment, ordering, staged commit, duplication, trash, restore, delete, or projection rebuild orchestration.
Runtime Owners
src/Admin/Pages/Routes/StructuresPage.phproutes the structure page and selects the content-editor template formode=content.src/Admin/Pages/Templates/contract/structure-content-editor.phprenders the route-local shell, initial JSON state, tabs, navigation tree wrapper, center content area, custom-field section, and right settings panel targets.src/Structures/ContentEditor/StructureContentEditorDataProvider.phpbuilds the page data and selected-post payload.src/Structures/Rendering/StructureAdminRenderer.phprenders the simplified content-navigation tree and the row action that opens the selected post in Content mode.assets/admin/js/structures/structures-content-editor.jsowns client-side selection, dirty state, panel rendering, save payload assembly, left navigation resizing, custom-field initialization, and media picker interaction.src/Structures/Ajax/StructureContentEditorAjax.phpowns selected-item reads and compatible custom-field saves.src/Modules/ContentFields/ContentFieldValueService.phpowns native Content Fields value writes when the Content Editor saves thecontent_fieldspayload.src/Structures/Ajax/StructureQuickEditAjax.phpremains the normal per-post field write endpoint for title, slug, content, author, media, access, module settings, taxonomies, quiz, downloads, submissions, video policy, and drip override.src/Admin/AdminWysiwyg.phpandassets/admin/js/admin-wysiwyg.jsown the reusable inline classic editor.
Route Contract
admin.php?page=brm_structure_{structure_id}opens the hierarchy/structure editing view.admin.php?page=brm_structure_{structure_id}&mode=contentopens the Content Editor and selects the first post when nopost_idis provided.admin.php?page=brm_structure_{structure_id}&mode=content&post_id=7414opens the Content Editor with a specific selected post.brm_structure_content_editor_enabledcan disable the content editor for a specific structure when page data is built.
Selected Item Payload
StructureContentEditorDataProvider::build_item_payload() returns the selected post payload used by both initial page state and brm_get_structure_content_item.
- Core post fields:
post_id,post_type,post_title,post_name,post_content,post_status,post_author, and assignableauthors. - Editor routing:
body_mode(classic,gutenberg, orbricks),edit_wordpress_url,edit_bricks_url, andpreview_url. - Media and video: authored video value, saved video provider, connected providers, duration, video image, and featured image.
- Access and module data: required levels, level options, parent/children flags, enrollment rule, effective enrollment requirement, submissions, assigned quiz, assigned downloads, video watch policy, and drip override.
- Taxonomy data: hierarchical taxonomy term IDs plus non-hierarchical tag names.
- Compatible custom fields:
acf.activeand rendered field-group HTML when an ACF-compatible runtime is available. - Native Content Fields:
content_fields.active, matching scalar field definitions, current values, and save eligibility when the Content Fields module is active and a field group matches the selected post.
AJAX Endpoints
brm_get_structure_content_item
Reads one selected-post payload. The handler verifies the shared structure editor nonce, requires edit_posts, and also checks edit_post for the requested post ID.
brm_save_structure_content_acf
Saves compatible custom-field values. The handler requires the same nonce and capabilities as the read endpoint. It only calls update_field() for field keys discovered from field groups assigned to the selected post, including nested subfields and flexible-content layouts.
This endpoint is for ACF-compatible runtime fields only. It must not save BRM-owned Content Fields.
brm_save_structure_content_fields
Saves BricksMembers-owned Content Fields for the selected post. The handler requires the same nonce and capabilities as the read endpoint, then delegates the submitted content_fields array to ContentFieldValueService::save_values_for_post(). Required-field validation and value sanitization stay in the Content Fields value owner.
Normal post and module settings continue through brm_quick_edit_post_fields. Do not add another write endpoint for the same fields.
Content Body Modes
- classic – the inline BricksMembers WYSIWYG loads and saves
post_content. - gutenberg – the content editor shows a WordPress edit fallback. The inline classic editor is not used for block content.
- bricks – the content editor shows the Bricks edit fallback. The inline classic editor is not used for Bricks body data.
Security and Escaping
- Structure content editor AJAX uses
StructureAjaxVerifier::verify_editor_request(), which enforces the BRM AJAX nonce,edit_posts, POST-only requests, and the shared AJAX rate-limit context. - Each selected-post request additionally checks
Security::current_user_can( 'edit_post', $post_id ). - The template serializes initial state with
wp_json_encode()and JSON hex flags. - Navigation tree HTML is rendered server-side and passed through a merged
wp_kses_allowed_html( 'post' )and admin-icon allowlist before output. - Client-rendered admin strings must come from localized PHP data. Do not add hardcoded user-facing English strings in the JavaScript.
Implementation Invariants
- The Content Editor tree is navigation-only.
- The right settings sidebar stays fixed at the route CSS variable width. Left-navigation resizing changes only the left and center columns.
- Compatible custom fields render below the body in the center column, not inside the settings sidebar.
- Native Content Fields render in their own Content Fields section below the body. Keep the Content Fields save path separate from the ACF-compatible save path.
- The visible custom-field label is generic. Do not hardcode ACF, SCF, or another provider name into the admin label.
- Module-gated panels should only render when the relevant module and post type support are active.
- Do not route internal code through
brm_*public API wrappers for this surface.