Files
structural_pages/docs/DESIGN.md
Quintino A. G. Souza 88b9605408 Rename module site_structure → structural_pages and vocabulary site_section → site_sections
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>
2026-02-05 08:10:32 -03:00

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:

  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_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_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_sections - 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/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:

  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/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:

  1. Navigate to Structure > Block layout
  2. Place "Structural Pages 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

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)

  • 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_sections 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/structural-pages
  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\structural_pages\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_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_section is 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

  1. drush en structural_pages -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/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.