# Site Structure - Design Document ## Overview The `site_structure` 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: 1. **Taxonomic Structure** (primary): Organization based on hierarchical taxonomy vocabulary 2. **Parent-Child Structure** (subsidiary): Direct hierarchy between pages for specific cases, supporting multiple entity types as parents --- ## Conceptual Model ``` ┌─────────────────────────────────────────────────────────────────────┐ │ PRIMARY STRUCTURE: site_section 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_section` | Property | Value | |----------|-------| | Machine name | `site_section` | | Name | Site Section | | 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_page` points to a `taxonomy_term`: uses the term itself as site section - If `field_parent_page` points to a `node`: inherits `field_site_section` from parent - If `field_parent_page` is 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 pages - `node:section_page` - Section pages - `taxonomy_term:site_section` - Site section terms - `user:user` - User accounts - `group:*` - All group types (requires Group module) **Context behavior by parent type**: - **Node/Taxonomy**: Content pages inherit `field_site_section` from the parent - **User**: Content pages are associated with the user profile page (`/user/{id}`). The `field_site_section` is cleared as the context is the user itself. - **Group**: Content pages are associated with the group. The `field_site_section` is cleared as the context is the group itself. **Configuration**: `/admin/config/local-modules/site-structure` ### 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_section): 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**: 1. Determine the root parent context by traversing the parent chain 2. 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 3. 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/site-structure` 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: Site Structure 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**: 1. Navigate to Structure > Block layout 2. Place "Site Structure Menu" block in desired region 3. Configure visibility (e.g., show only on content_page nodes) 4. Adjust depth and display options as needed --- ## File Structure ``` site_structure/ ├── site_structure.info.yml # Metadata and dependencies ├── site_structure.module # Hooks (presave, tokens, theme) ├── site_structure.install # Installation hooks ├── site_structure.services.yml # Service registration ├── site_structure.routing.yml # Route definitions ├── site_structure.links.menu.yml # Admin menu links ├── site_structure.libraries.yml # Asset libraries (CSS) │ ├── config/ │ ├── install/ │ │ ├── site_structure.settings.yml # Default settings │ │ ├── taxonomy.vocabulary.site_section.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_section_term.yml │ │ │ │ │ └── views.view.child_pages.yml │ │ │ └── schema/ │ └── site_structure.schema.yml # Config schema │ ├── css/ │ └── site-structure-menu.css # Menu block styles │ ├── templates/ │ └── site-structure-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/ │ │ └── SiteStructureSettingsForm.php │ │ │ └── Plugin/ │ ├── Block/ │ │ └── SiteStructureMenuBlock.php # Dynamic menu block │ │ │ └── ParentEntityHandler/ # Built-in handlers │ ├── TaxonomyTermHandler.php # taxonomy_term handler │ ├── UserHandler.php # user handler │ └── NodeHandler.php # node handler │ ├── modules/ │ └── site_structure_group/ # Group integration submodule │ ├── site_structure_group.info.yml │ └── src/Plugin/ParentEntityHandler/ │ └── GroupHandler.php # group handler │ └── docs/ └── DESIGN.md # This document ``` --- ## Dependencies ### Core Modules (Drupal 11) - `node` - `taxonomy` - `views` - `field` - `text` - `user` ### Contrib Modules - `token` - For custom tokens - `pathauto` - For automatic URL generation - `dynamic_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) 1. Navigate to Content > Add content > Section Page 2. Fill in title and body 3. Select the appropriate site section 4. Save 5. URL and breadcrumb are generated automatically ### Creating a Content Page (content_page) 1. **Create root page (anchored to taxonomy term)**: - Add content > Content Page - In "Parent Page", select a site_section term - Site section is set automatically from the term - Save 2. **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 3. **Create child pages**: - Add content > Content Page - In "Parent Page", select another content_page - Site section is inherited automatically - Save 4. The child pages View appears automatically on parent pages --- ## Administration ### Configuring Allowed Parent Types 1. Navigate to `/admin/config/local-modules/site-structure` 2. Select which content types (nodes) can be used as parents 3. Select which taxonomy vocabularies can be used as parents 4. Save configuration 5. 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: 1. **Attribute**: `Drupal\site_structure\Attribute\ParentEntityHandler` - PHP 8 attribute for defining handlers 2. **Interface**: `ParentEntityHandlerInterface` - Contract for all handlers 3. **Base class**: `ParentEntityHandlerBase` - Common implementation 4. **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_section | | 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 Group Name > Parent Nodes > Current Page - `field_site_section` is cleared for group-parented content (context is the group) - Per-section permissions can be implemented via Groups **Installation**: Enable the `site_structure_group` submodule after installing the Group module. **Configuration**: Enable group types in the settings form at `/admin/config/local-modules/site-structure` --- ## Installation Verification 1. `drush en site_structure -y` 2. Verify vocabulary at `/admin/structure/taxonomy` 3. Verify content types at `/admin/structure/types` 4. Create test hierarchical terms 5. Create `section_page` and verify breadcrumb/URL 6. Create root `content_page` with taxonomy term as parent 7. Create child `content_page`, verify inheritance 8. Verify child pages View 9. Test settings form at `/admin/config/local-modules/site-structure` --- ## 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 Site Structure Menu block for dynamic hierarchical navigation | | 2.0.0 | - | Refactored to plugin system for parent entity handlers. Group support moved to submodule. |