Files
structural_pages/docs/DESIGN.md
Quintino A. G. Souza 0c8f0fc778 Implement ParentEntityHandler plugin system for extensible entity support
Replace hardcoded entity type checks with a plugin-based architecture using
PHP 8 attributes. This allows adding new parent entity types without modifying
core module files.

Changes:
- Add ParentEntityHandler attribute, interface, base class, and manager
- Create built-in handlers for taxonomy_term, user, and node entities
- Move Group support to site_structure_group submodule (fixes class not found
  error when Group module is not installed)
- Refactor SiteStructureSettingsForm to use handler manager
- Refactor SiteStructureMenuBlock to use handler manager
- Refactor SectionBreadcrumbBuilder to use handler manager
- Update site_structure.module to use handler manager for clearsSiteSection
- Update documentation and translations

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 08:35:04 -03:00

483 lines
19 KiB
Markdown

# 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
<?php
namespace Drupal\my_module\Plugin\ParentEntityHandler;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\site_structure\Attribute\ParentEntityHandler;
use Drupal\site_structure\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 | Module required for availability |
| `clears_site_section` | bool | Whether to clear field_site_section |
| `sort_field` | string | Field for sorting entities |
| `route_parameter` | string|null | Route parameter name (defaults to entity_type_id) |
| `bundle_restrictions` | array | Specific bundles to handle |
| `weight` | int | Handler priority (lower = first) |
---
## Group Integration
Group support is provided via the `site_structure_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_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. |