Renames the module from site_structure to structural_pages and pluralizes the taxonomy vocabulary machine name from site_section to site_sections, updating all config, PHP, translations, and documentation references while preserving field_site_section, clears_site_section, and $site_section_id. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
19 KiB
Structural Pages - Design Document
Overview
The structural_pages module implements a hierarchical editorial structure and contextual navigation for institutional sites in Drupal 11. The module was designed for higher education institutions but is generic enough for other institutional contexts.
Problem
In Drupal, content is added in a flat manner (non-hierarchical), and menus are traditionally used to organize content into hierarchical structures. This creates user experience problems because content editors typically do not have permission to administer menus.
Solution
The module implements two complementary structures:
- Taxonomic Structure (primary): Organization based on hierarchical taxonomy vocabulary
- Parent-Child Structure (subsidiary): Direct hierarchy between pages for specific cases, supporting multiple entity types as parents
Conceptual Model
┌─────────────────────────────────────────────────────────────────────┐
│ PRIMARY STRUCTURE: site_sections Taxonomy │
│ │
│ - Governs the overall organization of site information │
│ - Defines breadcrumbs and URLs (via Pathauto) │
│ - Example: Undergraduate > Courses > Software Engineering │
│ - Applied to content type: section_page │
├─────────────────────────────────────────────────────────────────────┤
│ SUBSIDIARY STRUCTURE: Parent-Child (field_parent_page) │
│ │
│ - For specific cases: manuals, guides, sequential documentation │
│ - Provides contextual navigation (Child pages View) │
│ - Automatically inherits site section from parent (node/taxonomy) │
│ - For user/group parents, context is the entity itself │
│ - Applied to content type: content_page │
│ - Supports multiple parent types: content_page, section_page, │
│ taxonomy terms, users, groups (configurable) │
├─────────────────────────────────────────────────────────────────────┤
│ page (core Drupal) │
│ │
│ - Remains untouched for generic use │
└─────────────────────────────────────────────────────────────────────┘
Components
1. Taxonomy Vocabulary: site_sections
| Property | Value |
|---|---|
| Machine name | site_sections |
| Name | Site Sections |
| Hierarchical | Yes |
| Description | Main hierarchical structure for site organization |
Usage: This vocabulary defines site sections. Terms can be nested to create hierarchies (e.g., "Undergraduate" > "Courses" > "Engineering").
2. Content Type: section_page
| Property | Value |
|---|---|
| Machine name | section_page |
| Name | Section Page |
| Description | Pages organized by site section |
Fields:
| Field | Type | Required | Cardinality | Description |
|---|---|---|---|---|
title |
String | Yes | 1 | Page title (core) |
body |
Text (formatted, long, with summary) | No | 1 | Page content |
field_site_section |
Entity reference (taxonomy) | Yes | 1 | Site section |
3. Content Type: content_page
| Property | Value |
|---|---|
| Machine name | content_page |
| Name | Content Page |
| Description | Pages with hierarchical parent-child structure |
Fields:
| Field | Type | Required | Cardinality | Description |
|---|---|---|---|---|
title |
String | Yes | 1 | Page title (core) |
body |
Text (formatted, long, with summary) | No | 1 | Page content |
field_parent_page |
Dynamic entity reference | No | 1 | Parent entity (configurable types) |
field_site_section |
Entity reference (taxonomy) | Conditional | 1 | Site section (inherited or manual) |
Rules for field_site_section in content_page:
- If
field_parent_pagepoints to ataxonomy_term: uses the term itself as site section - If
field_parent_pagepoints to anode: inheritsfield_site_sectionfrom parent - If
field_parent_pageis empty (root page): must be filled manually
4. Dynamic Parent Reference
The field_parent_page uses the Dynamic Entity Reference module to support multiple entity types as parents. This is configurable via the admin interface.
Default allowed targets:
node:content_page- Other content pagesnode:section_page- Section pagestaxonomy_term:site_sections- Site section termsuser:user- User accountsgroup:*- All group types (requires Group module)
Context behavior by parent type:
- Node/Taxonomy: Content pages inherit
field_site_sectionfrom the parent - User: Content pages are associated with the user profile page (
/user/{id}). Thefield_site_sectionis cleared as the context is the user itself. - Group: Content pages are associated with the group. The
field_site_sectionis cleared as the context is the group itself.
Configuration: /admin/config/local-modules/structural-pages
5. Inheritance Logic
Implemented via hook_entity_presave():
When a content_page is saved:
IF field_parent_page is filled:
IF parent is user or group:
Clear field_site_section (context is the entity itself)
ELSE IF parent is taxonomy_term (site_sections):
Set field_site_section to the parent term ID
ELSE IF parent is node with field_site_section:
Copy field_site_section from parent node
ELSE:
Validate that field_site_section was filled manually
Additional validations:
- Prevent self-reference (page cannot be parent of itself)
- Prevent circularity (A → B → C → A) - only for node parents
6. Breadcrumb Builder
Custom service that overrides Drupal's default breadcrumb for section_page and content_page.
Logic:
- Determine the root parent context by traversing the parent chain
- Build breadcrumb based on context type:
- User context: Home > User Name > Parent Nodes > Current Page
- Group context: Home > Group Name > Parent Nodes > Current Page
- Taxonomy context: Home > Term Hierarchy > Parent Nodes > Current Page
- For content_page, also add node parents to the breadcrumb
Examples:
- Page "Chapter 1" has parent "User Guide" which is a section_page in "Documentation"
- Breadcrumb: Home > Documentation > User Guide > Chapter 1
- Page "My Notes" has parent user "John Doe"
- Breadcrumb: Home > John Doe > My Notes
- Page "Meeting Minutes" has parent group "Research Team"
- Breadcrumb: Home > Research Team > Meeting Minutes
7. Pathauto Tokens
Custom token for hierarchical URL generation.
| Token | Description | Example |
|---|---|---|
[node:site-section-path] |
Path based on taxonomy hierarchy | undergraduate/courses |
Suggested URL pattern: [node:site-section-path]/[node:title]
Result: /undergraduate/courses/software-engineering
8. View: child_pages
View that lists child pages of the current content_page.
| Property | Value |
|---|---|
| Display | Block |
| Filter | field_parent_page = Current node ID (contextual) |
| Sort | Title (A-Z) or weight (if implemented) |
| Usage | Sidebar navigation in guides/manuals |
9. Settings Form
Configuration form at /admin/config/local-modules/structural-pages that allows administrators to:
- Select which node bundles can be used as parents
- Select which taxonomy vocabularies can be used as parents
- Changes automatically update the field configuration
10. Block: Structural Pages Menu
Dynamic menu block that renders hierarchical navigation based on the content structure.
Features:
- Automatically detects the ancestor entity (taxonomy term, user, or group)
- Renders all content_page hierarchies under the ancestor
- Supports configurable depth limit
- Highlights active trail (current page and its ancestors)
- Proper cache invalidation by node and route
Configuration options:
| Option | Default | Description |
|---|---|---|
| Maximum depth | 3 | Number of levels to display (1-10) |
| Show ancestor title | Yes | Display ancestor entity title as header |
| Expand active trail | Yes | Highlight the path to current page |
Menu structure example:
Documentation (ancestor: taxonomy term)
├── Getting Started ← level 1 (content_page → term)
│ ├── Installation ← level 2 (content_page → node)
│ └── Configuration ← level 2
├── User Guide ← level 1 (content_page → term)
│ ├── Basic Usage ← level 2
│ └── Advanced Topics ← level 2
│ └── API Integration ← level 3 (content_page → node)
└── FAQ ← level 1 (content_page → term)
Usage:
- Navigate to Structure > Block layout
- Place "Structural Pages Menu" block in desired region
- Configure visibility (e.g., show only on content_page nodes)
- Adjust depth and display options as needed
File Structure
structural_pages/
├── structural_pages.info.yml # Metadata and dependencies
├── structural_pages.module # Hooks (presave, tokens, theme)
├── structural_pages.install # Installation hooks
├── structural_pages.services.yml # Service registration
├── structural_pages.routing.yml # Route definitions
├── structural_pages.links.menu.yml # Admin menu links
├── structural_pages.libraries.yml # Asset libraries (CSS)
│
├── config/
│ ├── install/
│ │ ├── structural_pages.settings.yml # Default settings
│ │ ├── taxonomy.vocabulary.site_sections.yml
│ │ │
│ │ ├── node.type.section_page.yml
│ │ ├── field.storage.node.field_site_section.yml
│ │ ├── field.field.node.section_page.field_site_section.yml
│ │ ├── core.entity_form_display.node.section_page.default.yml
│ │ ├── core.entity_view_display.node.section_page.default.yml
│ │ │
│ │ ├── node.type.content_page.yml
│ │ ├── field.storage.node.field_parent_page.yml # dynamic_entity_reference
│ │ ├── field.field.node.content_page.field_parent_page.yml
│ │ ├── field.field.node.content_page.field_site_section.yml
│ │ ├── core.entity_form_display.node.content_page.default.yml
│ │ ├── core.entity_view_display.node.content_page.default.yml
│ │ │
│ │ ├── pathauto.pattern.section_page.yml
│ │ ├── pathauto.pattern.content_page.yml
│ │ ├── pathauto.pattern.site_sections_term.yml
│ │ │
│ │ └── views.view.child_pages.yml
│ │
│ └── schema/
│ └── structural_pages.schema.yml # Config schema
│
├── css/
│ └── structural-pages-menu.css # Menu block styles
│
├── templates/
│ └── structural-pages-menu.html.twig # Menu block template
│
├── translations/
│ └── pt-br.po # Portuguese (Brazil) translation
│
├── src/
│ ├── Attribute/
│ │ └── ParentEntityHandler.php # Plugin attribute definition
│ │
│ ├── ParentEntityHandler/
│ │ ├── ParentEntityHandlerInterface.php # Handler interface
│ │ ├── ParentEntityHandlerManagerInterface.php # Manager interface
│ │ ├── ParentEntityHandlerBase.php # Base implementation
│ │ └── ParentEntityHandlerManager.php # Plugin manager
│ │
│ ├── Breadcrumb/
│ │ └── SectionBreadcrumbBuilder.php
│ │
│ ├── Form/
│ │ └── StructuralPagesSettingsForm.php
│ │
│ └── Plugin/
│ ├── Block/
│ │ └── StructuralPagesMenuBlock.php # Dynamic menu block
│ │
│ └── ParentEntityHandler/ # Built-in handlers
│ ├── TaxonomyTermHandler.php # taxonomy_term handler
│ ├── UserHandler.php # user handler
│ └── NodeHandler.php # node handler
│
├── modules/
│ └── structural_pages_group/ # Group integration submodule
│ ├── structural_pages_group.info.yml
│ └── src/Plugin/ParentEntityHandler/
│ └── GroupHandler.php # group handler
│
└── docs/
└── DESIGN.md # This document
Dependencies
Core Modules (Drupal 11)
nodetaxonomyviewsfieldtextuser
Contrib Modules
token- For custom tokenspathauto- For automatic URL generationdynamic_entity_reference- For multi-type parent references
Optional Modules
group- For group entity support as parent type
Editor Workflow
Creating a Section Page (section_page)
- Navigate to Content > Add content > Section Page
- Fill in title and body
- Select the appropriate site section
- Save
- URL and breadcrumb are generated automatically
Creating a Content Page (content_page)
-
Create root page (anchored to taxonomy term):
- Add content > Content Page
- In "Parent Page", select a site_sections term
- Site section is set automatically from the term
- Save
-
Create root page (anchored to section_page):
- Add content > Content Page
- In "Parent Page", select a section_page
- Site section is inherited from the section_page
- Save
-
Create child pages:
- Add content > Content Page
- In "Parent Page", select another content_page
- Site section is inherited automatically
- Save
-
The child pages View appears automatically on parent pages
Administration
Configuring Allowed Parent Types
- Navigate to
/admin/config/local-modules/structural-pages - Select which content types (nodes) can be used as parents
- Select which taxonomy vocabularies can be used as parents
- Save configuration
- Field configuration is updated automatically
Parent Entity Handler Plugin System
The module uses a plugin system to support different entity types as parents for content_page nodes. This allows for extensibility without modifying the core module.
Architecture
The plugin system consists of:
- Attribute:
Drupal\structural_pages\Attribute\ParentEntityHandler- PHP 8 attribute for defining handlers - Interface:
ParentEntityHandlerInterface- Contract for all handlers - Base class:
ParentEntityHandlerBase- Common implementation - Manager:
ParentEntityHandlerManager- Plugin discovery and aggregation
Built-in Handlers
| Handler | Entity Type | Clears Site Section | Sort Field | Bundle Restrictions |
|---|---|---|---|---|
| TaxonomyTermHandler | taxonomy_term | No | name | site_sections |
| UserHandler | user | Yes | name | - |
| NodeHandler | node | No | title | content_page, section_page |
Creating Custom Handlers
To add support for a new entity type, create a handler plugin:
<?php
namespace Drupal\my_module\Plugin\ParentEntityHandler;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\structural_pages\Attribute\ParentEntityHandler;
use Drupal\structural_pages\ParentEntityHandler\ParentEntityHandlerBase;
#[ParentEntityHandler(
id: 'my_entity',
label: new TranslatableMarkup('My Entities'),
entity_type_id: 'my_entity',
clears_site_section: FALSE,
sort_field: 'title',
)]
class MyEntityHandler extends ParentEntityHandlerBase {
// Override methods as needed
}
Handler Attributes
| Attribute | Type | Description |
|---|---|---|
id |
string | Unique plugin ID |
label |
TranslatableMarkup | Human-readable label |
entity_type_id |
string | Entity type this handler manages |
provider_module |
string | null |
clears_site_section |
bool | Whether to clear field_site_section |
sort_field |
string | Field for sorting entities |
route_parameter |
string | null |
bundle_restrictions |
array | Specific bundles to handle |
weight |
int | Handler priority (lower = first) |
Group Integration
Group support is provided via the structural_pages_group submodule, which adds a handler for group entities. This submodule requires the Group module.
Features:
- Content pages can have groups as parent entities
- When a content_page has a group as parent, the context is the group itself
- Breadcrumbs show: Home > Group Name > Parent Nodes > Current Page
field_site_sectionis cleared for group-parented content (context is the group)- Per-section permissions can be implemented via Groups
Installation: Enable the structural_pages_group submodule after installing the Group module.
Configuration: Enable group types in the settings form at /admin/config/local-modules/structural-pages
Installation Verification
drush en structural_pages -y- Verify vocabulary at
/admin/structure/taxonomy - Verify content types at
/admin/structure/types - Create test hierarchical terms
- Create
section_pageand verify breadcrumb/URL - Create root
content_pagewith taxonomy term as parent - Create child
content_page, verify inheritance - Verify child pages View
- Test settings form at
/admin/config/local-modules/structural-pages
Changelog
| Version | Date | Description |
|---|---|---|
| 1.0.0 | - | Initial version |
| 1.1.0 | - | Added dynamic_entity_reference support for multi-type parents |
| 1.2.0 | - | Added user and group entity support as parent types |
| 1.3.0 | - | Added Structural Pages Menu block for dynamic hierarchical navigation |
| 2.0.0 | - | Refactored to plugin system for parent entity handlers. Group support moved to submodule. |