mirror of
https://gitlab.unicamp.br/infimecc_drupal11_modules/structural_pages.git
synced 2026-03-09 18:07:42 -03:00
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>
483 lines
19 KiB
Markdown
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. |
|