mirror of
https://gitlab.unicamp.br/infimecc_drupal11_modules/structural_pages.git
synced 2026-03-08 01:27:42 -03:00
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>
This commit is contained in:
@@ -276,6 +276,15 @@ site_structure/
|
||||
│ └── 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
|
||||
│ │
|
||||
@@ -283,8 +292,19 @@ site_structure/
|
||||
│ │ └── SiteStructureSettingsForm.php
|
||||
│ │
|
||||
│ └── Plugin/
|
||||
│ └── Block/
|
||||
│ └── SiteStructureMenuBlock.php # Dynamic menu block
|
||||
│ ├── 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
|
||||
@@ -358,16 +378,81 @@ site_structure/
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
The module supports the Group module for content organization:
|
||||
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`
|
||||
|
||||
---
|
||||
@@ -394,3 +479,4 @@ The module supports the Group module for content organization:
|
||||
| 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. |
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
name: 'Site Structure Group Integration'
|
||||
type: module
|
||||
description: 'Provides Group entity support as parent type for Site Structure module.'
|
||||
package: 'Site Structure'
|
||||
core_version_requirement: ^10.3 || ^11
|
||||
dependencies:
|
||||
- site_structure:site_structure
|
||||
- group:group
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\site_structure_group\Plugin\ParentEntityHandler;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\group\Entity\GroupInterface;
|
||||
use Drupal\site_structure\Attribute\ParentEntityHandler;
|
||||
use Drupal\site_structure\ParentEntityHandler\ParentEntityHandlerBase;
|
||||
|
||||
/**
|
||||
* Handler for group entities.
|
||||
*
|
||||
* This handler is provided by the site_structure_group submodule and is only
|
||||
* available when the Group module is installed.
|
||||
*/
|
||||
#[ParentEntityHandler(
|
||||
id: 'group',
|
||||
label: new TranslatableMarkup('Groups (group)'),
|
||||
entity_type_id: 'group',
|
||||
provider_module: 'group',
|
||||
clears_site_section: TRUE,
|
||||
sort_field: 'label',
|
||||
weight: 15,
|
||||
)]
|
||||
class GroupHandler extends ParentEntityHandlerBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEntityFromRoute(RouteMatchInterface $route_match): ?EntityInterface {
|
||||
$group = $route_match->getParameter('group');
|
||||
|
||||
if (!$group instanceof GroupInterface) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return $group;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handlesEntity(EntityInterface $entity): bool {
|
||||
return $entity instanceof GroupInterface;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -51,9 +51,11 @@ function site_structure_entity_presave(EntityInterface $entity): void {
|
||||
}
|
||||
|
||||
// Handle site section based on parent type.
|
||||
// For user and group, we don't set field_site_section - the context is the entity itself.
|
||||
if (in_array($parent_entity_type, ['user', 'group'])) {
|
||||
// Clear field_site_section as the context is the user/group, not a site section.
|
||||
// Some entity types (like user and group) act as context containers themselves.
|
||||
/** @var \Drupal\site_structure\ParentEntityHandler\ParentEntityHandlerManagerInterface $handler_manager */
|
||||
$handler_manager = \Drupal::service('plugin.manager.parent_entity_handler');
|
||||
if ($handler_manager->clearsSiteSection($parent_entity_type)) {
|
||||
// Clear field_site_section as the context is the parent entity, not a site section.
|
||||
$entity->set('field_site_section', NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
services:
|
||||
plugin.manager.parent_entity_handler:
|
||||
class: Drupal\site_structure\ParentEntityHandler\ParentEntityHandlerManager
|
||||
arguments:
|
||||
- '@container.namespaces'
|
||||
- '@cache.discovery'
|
||||
- '@module_handler'
|
||||
- '@entity_type.manager'
|
||||
|
||||
site_structure.breadcrumb.section:
|
||||
class: Drupal\site_structure\Breadcrumb\SectionBreadcrumbBuilder
|
||||
arguments: ['@entity_type.manager']
|
||||
arguments:
|
||||
- '@entity_type.manager'
|
||||
- '@plugin.manager.parent_entity_handler'
|
||||
tags:
|
||||
- { name: breadcrumb_builder, priority: 100 }
|
||||
|
||||
66
src/Attribute/ParentEntityHandler.php
Normal file
66
src/Attribute/ParentEntityHandler.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\site_structure\Attribute;
|
||||
|
||||
use Drupal\Component\Plugin\Attribute\Plugin;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Defines a ParentEntityHandler plugin attribute.
|
||||
*
|
||||
* Plugin namespace: Plugin\ParentEntityHandler.
|
||||
*
|
||||
* @see \Drupal\site_structure\ParentEntityHandler\ParentEntityHandlerInterface
|
||||
* @see \Drupal\site_structure\ParentEntityHandler\ParentEntityHandlerBase
|
||||
* @see \Drupal\site_structure\ParentEntityHandler\ParentEntityHandlerManager
|
||||
* @see plugin_api
|
||||
*/
|
||||
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||
class ParentEntityHandler extends Plugin {
|
||||
|
||||
/**
|
||||
* Constructs a ParentEntityHandler attribute.
|
||||
*
|
||||
* @param string $id
|
||||
* The plugin ID.
|
||||
* @param \Drupal\Core\StringTranslation\TranslatableMarkup $label
|
||||
* The human-readable label of the handler.
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID this handler manages.
|
||||
* @param string|null $provider_module
|
||||
* (optional) The module that must be installed for this handler to be
|
||||
* available. If NULL, the handler is always available.
|
||||
* @param bool $clears_site_section
|
||||
* (optional) Whether this handler clears field_site_section when used as
|
||||
* parent. Defaults to FALSE.
|
||||
* @param string $sort_field
|
||||
* (optional) The field to use for sorting entities of this type.
|
||||
* Defaults to 'title'.
|
||||
* @param string|null $route_parameter
|
||||
* (optional) The route parameter name used to detect entities on routes.
|
||||
* Defaults to the entity_type_id if not specified.
|
||||
* @param array $bundle_restrictions
|
||||
* (optional) An array of bundle IDs that this handler applies to. If empty,
|
||||
* the handler applies to all bundles.
|
||||
* @param int $weight
|
||||
* (optional) The weight of the handler. Lower weights are processed first.
|
||||
* Defaults to 0.
|
||||
* @param class-string|null $deriver
|
||||
* (optional) The deriver class.
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string $id,
|
||||
public readonly TranslatableMarkup $label,
|
||||
public readonly string $entity_type_id,
|
||||
public readonly ?string $provider_module = NULL,
|
||||
public readonly bool $clears_site_section = FALSE,
|
||||
public readonly string $sort_field = 'title',
|
||||
public readonly ?string $route_parameter = NULL,
|
||||
public readonly array $bundle_restrictions = [],
|
||||
public readonly int $weight = 0,
|
||||
public readonly ?string $deriver = NULL,
|
||||
) {}
|
||||
|
||||
}
|
||||
@@ -6,12 +6,12 @@ namespace Drupal\site_structure\Breadcrumb;
|
||||
|
||||
use Drupal\Core\Breadcrumb\Breadcrumb;
|
||||
use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Link;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Drupal\site_structure\ParentEntityHandler\ParentEntityHandlerManagerInterface;
|
||||
|
||||
/**
|
||||
* Provides a breadcrumb builder for section_page and content_page content types.
|
||||
@@ -27,6 +27,13 @@ class SectionBreadcrumbBuilder implements BreadcrumbBuilderInterface {
|
||||
*/
|
||||
protected EntityTypeManagerInterface $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The parent entity handler manager.
|
||||
*
|
||||
* @var \Drupal\site_structure\ParentEntityHandler\ParentEntityHandlerManagerInterface
|
||||
*/
|
||||
protected ParentEntityHandlerManagerInterface $handlerManager;
|
||||
|
||||
/**
|
||||
* Content types that this builder applies to.
|
||||
*
|
||||
@@ -39,9 +46,15 @@ class SectionBreadcrumbBuilder implements BreadcrumbBuilderInterface {
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\site_structure\ParentEntityHandler\ParentEntityHandlerManagerInterface $handler_manager
|
||||
* The parent entity handler manager.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
|
||||
public function __construct(
|
||||
EntityTypeManagerInterface $entity_type_manager,
|
||||
ParentEntityHandlerManagerInterface $handler_manager,
|
||||
) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->handlerManager = $handler_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,25 +92,8 @@ class SectionBreadcrumbBuilder implements BreadcrumbBuilderInterface {
|
||||
$context = $this->getParentContext($node);
|
||||
|
||||
if ($context) {
|
||||
switch ($context['type']) {
|
||||
case 'user':
|
||||
// User context: Home > User Name > ... > Current Page.
|
||||
$this->addUserBreadcrumb($breadcrumb, $context['entity']);
|
||||
break;
|
||||
|
||||
case 'group':
|
||||
// Group context: Home > Group Name > ... > Current Page.
|
||||
$this->addGroupBreadcrumb($breadcrumb, $context['entity']);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Node or taxonomy context: use site section.
|
||||
if ($node->hasField('field_site_section') && !$node->get('field_site_section')->isEmpty()) {
|
||||
$term_id = $node->get('field_site_section')->target_id;
|
||||
$this->addTaxonomyBreadcrumbs($breadcrumb, $term_id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Use the handler manager to build breadcrumbs for the context entity.
|
||||
$this->handlerManager->buildBreadcrumbForEntity($breadcrumb, $context['entity']);
|
||||
}
|
||||
else {
|
||||
// No parent context, check for site section directly.
|
||||
@@ -193,32 +189,6 @@ class SectionBreadcrumbBuilder implements BreadcrumbBuilderInterface {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds user breadcrumb.
|
||||
*
|
||||
* @param \Drupal\Core\Breadcrumb\Breadcrumb $breadcrumb
|
||||
* The breadcrumb object.
|
||||
* @param \Drupal\Core\Entity\EntityInterface $user
|
||||
* The user entity.
|
||||
*/
|
||||
protected function addUserBreadcrumb(Breadcrumb $breadcrumb, EntityInterface $user): void {
|
||||
$breadcrumb->addCacheableDependency($user);
|
||||
$breadcrumb->addLink($user->toLink());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds group breadcrumb.
|
||||
*
|
||||
* @param \Drupal\Core\Breadcrumb\Breadcrumb $breadcrumb
|
||||
* The breadcrumb object.
|
||||
* @param \Drupal\Core\Entity\EntityInterface $group
|
||||
* The group entity.
|
||||
*/
|
||||
protected function addGroupBreadcrumb(Breadcrumb $breadcrumb, EntityInterface $group): void {
|
||||
$breadcrumb->addCacheableDependency($group);
|
||||
$breadcrumb->addLink($group->toLink());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds breadcrumbs based on taxonomy hierarchy.
|
||||
*
|
||||
|
||||
@@ -7,9 +7,9 @@ namespace Drupal\site_structure\Form;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Form\ConfigFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\site_structure\ParentEntityHandler\ParentEntityHandlerManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
@@ -32,23 +32,11 @@ class SiteStructureSettingsForm extends ConfigFormBase {
|
||||
protected EntityTypeBundleInfoInterface $entityTypeBundleInfo;
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
* The parent entity handler manager.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
* @var \Drupal\site_structure\ParentEntityHandler\ParentEntityHandlerManagerInterface
|
||||
*/
|
||||
protected ModuleHandlerInterface $moduleHandler;
|
||||
|
||||
/**
|
||||
* Entity types that can be used as parents.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected array $supportedEntityTypes = [
|
||||
'node' => 'Content Types (node)',
|
||||
'taxonomy_term' => 'Taxonomy Vocabularies (taxonomy_term)',
|
||||
'user' => 'Users (user)',
|
||||
'group' => 'Groups (group)',
|
||||
];
|
||||
protected ParentEntityHandlerManagerInterface $handlerManager;
|
||||
|
||||
/**
|
||||
* Constructs a SiteStructureSettingsForm object.
|
||||
@@ -59,19 +47,19 @@ class SiteStructureSettingsForm extends ConfigFormBase {
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
|
||||
* The entity type bundle info service.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
* @param \Drupal\site_structure\ParentEntityHandler\ParentEntityHandlerManagerInterface $handler_manager
|
||||
* The parent entity handler manager.
|
||||
*/
|
||||
public function __construct(
|
||||
ConfigFactoryInterface $config_factory,
|
||||
EntityTypeManagerInterface $entity_type_manager,
|
||||
EntityTypeBundleInfoInterface $entity_type_bundle_info,
|
||||
ModuleHandlerInterface $module_handler,
|
||||
ParentEntityHandlerManagerInterface $handler_manager,
|
||||
) {
|
||||
parent::__construct($config_factory);
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->entityTypeBundleInfo = $entity_type_bundle_info;
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->handlerManager = $handler_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,7 +70,7 @@ class SiteStructureSettingsForm extends ConfigFormBase {
|
||||
$container->get('config.factory'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('entity_type.bundle.info'),
|
||||
$container->get('module_handler'),
|
||||
$container->get('plugin.manager.parent_entity_handler'),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -137,20 +125,12 @@ class SiteStructureSettingsForm extends ConfigFormBase {
|
||||
'#tree' => TRUE,
|
||||
];
|
||||
|
||||
foreach ($this->supportedEntityTypes as $entity_type => $label) {
|
||||
// Check if entity type exists.
|
||||
if (!$this->entityTypeManager->hasDefinition($entity_type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Special handling for 'group' - check if module is enabled.
|
||||
if ($entity_type === 'group' && !$this->moduleHandler->moduleExists('group')) {
|
||||
continue;
|
||||
}
|
||||
$supportedEntityTypes = $this->handlerManager->getSupportedEntityTypes();
|
||||
|
||||
foreach ($supportedEntityTypes as $entity_type => $label) {
|
||||
$form['allowed_parent_targets'][$entity_type] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t($label),
|
||||
'#title' => $label,
|
||||
'#open' => TRUE,
|
||||
];
|
||||
|
||||
@@ -207,8 +187,9 @@ class SiteStructureSettingsForm extends ConfigFormBase {
|
||||
public function submitForm(array &$form, FormStateInterface $form_state): void {
|
||||
$targets = [];
|
||||
$values = $form_state->getValue('allowed_parent_targets');
|
||||
$supportedEntityTypes = $this->handlerManager->getSupportedEntityTypes();
|
||||
|
||||
foreach ($this->supportedEntityTypes as $entity_type => $label) {
|
||||
foreach ($supportedEntityTypes as $entity_type => $label) {
|
||||
if (empty($values[$entity_type])) {
|
||||
continue;
|
||||
}
|
||||
@@ -276,12 +257,7 @@ class SiteStructureSettingsForm extends ConfigFormBase {
|
||||
}
|
||||
|
||||
if (!isset($entity_type_settings[$entity_type])) {
|
||||
$sort_field = match($entity_type) {
|
||||
'taxonomy_term' => 'name',
|
||||
'user' => 'name',
|
||||
'group' => 'label',
|
||||
default => 'title',
|
||||
};
|
||||
$sort_field = $this->handlerManager->getSortField($entity_type);
|
||||
|
||||
$entity_type_settings[$entity_type] = [
|
||||
'handler' => 'default:' . $entity_type,
|
||||
|
||||
198
src/ParentEntityHandler/ParentEntityHandlerBase.php
Normal file
198
src/ParentEntityHandler/ParentEntityHandlerBase.php
Normal file
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\site_structure\ParentEntityHandler;
|
||||
|
||||
use Drupal\Core\Breadcrumb\Breadcrumb;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Base class for parent entity handler plugins.
|
||||
*
|
||||
* Provides default implementations for common handler functionality.
|
||||
*/
|
||||
abstract class ParentEntityHandlerBase extends PluginBase implements ParentEntityHandlerInterface, ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected EntityTypeManagerInterface $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected ModuleHandlerInterface $moduleHandler;
|
||||
|
||||
/**
|
||||
* Constructs a ParentEntityHandlerBase object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
*/
|
||||
public function __construct(
|
||||
array $configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
EntityTypeManagerInterface $entity_type_manager,
|
||||
ModuleHandlerInterface $module_handler,
|
||||
) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->moduleHandler = $module_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('module_handler'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEntityTypeId(): string {
|
||||
return $this->pluginDefinition['entity_type_id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLabel(): string {
|
||||
return (string) $this->pluginDefinition['label'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isAvailable(): bool {
|
||||
// Check if provider module is required and installed.
|
||||
$provider = $this->pluginDefinition['provider_module'] ?? NULL;
|
||||
if ($provider && !$this->moduleHandler->moduleExists($provider)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Check if entity type exists.
|
||||
return $this->entityTypeManager->hasDefinition($this->getEntityTypeId());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clearsSiteSection(): bool {
|
||||
return $this->pluginDefinition['clears_site_section'] ?? FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSortField(): string {
|
||||
return $this->pluginDefinition['sort_field'] ?? 'title';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRouteParameter(): string {
|
||||
return $this->pluginDefinition['route_parameter'] ?? $this->getEntityTypeId();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getBundleRestrictions(): array {
|
||||
return $this->pluginDefinition['bundle_restrictions'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEntityFromRoute(RouteMatchInterface $route_match): ?EntityInterface {
|
||||
$parameter = $this->getRouteParameter();
|
||||
$entity = $route_match->getParameter($parameter);
|
||||
|
||||
if (!$entity instanceof EntityInterface) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!$this->handlesEntity($entity)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handlesEntity(EntityInterface $entity): bool {
|
||||
if ($entity->getEntityTypeId() !== $this->getEntityTypeId()) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$bundle_restrictions = $this->getBundleRestrictions();
|
||||
if (!empty($bundle_restrictions)) {
|
||||
return in_array($entity->bundle(), $bundle_restrictions, TRUE);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildBreadcrumb(Breadcrumb $breadcrumb, EntityInterface $entity): void {
|
||||
$breadcrumb->addCacheableDependency($entity);
|
||||
$breadcrumb->addLink($entity->toLink());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSiteSectionId(EntityInterface $entity): int|string|null {
|
||||
// Default implementation returns NULL.
|
||||
// Subclasses should override for entity types that can provide a site section.
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an entity by ID.
|
||||
*
|
||||
* @param int|string $entity_id
|
||||
* The entity ID.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|null
|
||||
* The loaded entity, or NULL if not found.
|
||||
*/
|
||||
protected function loadEntity(int|string $entity_id): ?EntityInterface {
|
||||
return $this->entityTypeManager
|
||||
->getStorage($this->getEntityTypeId())
|
||||
->load($entity_id);
|
||||
}
|
||||
|
||||
}
|
||||
124
src/ParentEntityHandler/ParentEntityHandlerInterface.php
Normal file
124
src/ParentEntityHandler/ParentEntityHandlerInterface.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\site_structure\ParentEntityHandler;
|
||||
|
||||
use Drupal\Core\Breadcrumb\Breadcrumb;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
* Interface for parent entity handler plugins.
|
||||
*
|
||||
* Parent entity handlers define how different entity types behave when used
|
||||
* as parent entities for content_page nodes in the site structure hierarchy.
|
||||
*/
|
||||
interface ParentEntityHandlerInterface {
|
||||
|
||||
/**
|
||||
* Gets the entity type ID this handler manages.
|
||||
*
|
||||
* @return string
|
||||
* The entity type ID.
|
||||
*/
|
||||
public function getEntityTypeId(): string;
|
||||
|
||||
/**
|
||||
* Gets the human-readable label of this handler.
|
||||
*
|
||||
* @return string
|
||||
* The handler label.
|
||||
*/
|
||||
public function getLabel(): string;
|
||||
|
||||
/**
|
||||
* Checks if this handler is available.
|
||||
*
|
||||
* A handler is available if its provider module is installed and the
|
||||
* entity type exists.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the handler is available, FALSE otherwise.
|
||||
*/
|
||||
public function isAvailable(): bool;
|
||||
|
||||
/**
|
||||
* Checks if this handler clears field_site_section when used as parent.
|
||||
*
|
||||
* Some entity types (like users and groups) act as context containers
|
||||
* themselves, so content pages under them don't inherit a site section.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if field_site_section should be cleared, FALSE otherwise.
|
||||
*/
|
||||
public function clearsSiteSection(): bool;
|
||||
|
||||
/**
|
||||
* Gets the field to use for sorting entities of this type.
|
||||
*
|
||||
* @return string
|
||||
* The sort field name.
|
||||
*/
|
||||
public function getSortField(): string;
|
||||
|
||||
/**
|
||||
* Gets the route parameter name for detecting entities on routes.
|
||||
*
|
||||
* @return string
|
||||
* The route parameter name.
|
||||
*/
|
||||
public function getRouteParameter(): string;
|
||||
|
||||
/**
|
||||
* Gets the bundle restrictions for this handler.
|
||||
*
|
||||
* @return array
|
||||
* An array of bundle IDs, or empty array for no restrictions.
|
||||
*/
|
||||
public function getBundleRestrictions(): array;
|
||||
|
||||
/**
|
||||
* Gets an entity from the current route.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The current route match.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|null
|
||||
* The entity from the route, or NULL if not found or not applicable.
|
||||
*/
|
||||
public function getEntityFromRoute(RouteMatchInterface $route_match): ?EntityInterface;
|
||||
|
||||
/**
|
||||
* Checks if this handler handles the given entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to check.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if this handler handles the entity, FALSE otherwise.
|
||||
*/
|
||||
public function handlesEntity(EntityInterface $entity): bool;
|
||||
|
||||
/**
|
||||
* Builds breadcrumb entries for the given entity.
|
||||
*
|
||||
* @param \Drupal\Core\Breadcrumb\Breadcrumb $breadcrumb
|
||||
* The breadcrumb to add entries to.
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to build breadcrumb for.
|
||||
*/
|
||||
public function buildBreadcrumb(Breadcrumb $breadcrumb, EntityInterface $entity): void;
|
||||
|
||||
/**
|
||||
* Gets the site section ID from an entity of this type.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity.
|
||||
*
|
||||
* @return int|string|null
|
||||
* The site section term ID, or NULL if not applicable.
|
||||
*/
|
||||
public function getSiteSectionId(EntityInterface $entity): int|string|null;
|
||||
|
||||
}
|
||||
207
src/ParentEntityHandler/ParentEntityHandlerManager.php
Normal file
207
src/ParentEntityHandler/ParentEntityHandlerManager.php
Normal file
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\site_structure\ParentEntityHandler;
|
||||
|
||||
use Drupal\Core\Breadcrumb\Breadcrumb;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\site_structure\Attribute\ParentEntityHandler;
|
||||
|
||||
/**
|
||||
* Plugin manager for parent entity handlers.
|
||||
*
|
||||
* @see \Drupal\site_structure\Attribute\ParentEntityHandler
|
||||
* @see \Drupal\site_structure\ParentEntityHandler\ParentEntityHandlerInterface
|
||||
* @see \Drupal\site_structure\ParentEntityHandler\ParentEntityHandlerBase
|
||||
*/
|
||||
class ParentEntityHandlerManager extends DefaultPluginManager implements ParentEntityHandlerManagerInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected EntityTypeManagerInterface $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Static cache for handler instances.
|
||||
*
|
||||
* @var \Drupal\site_structure\ParentEntityHandler\ParentEntityHandlerInterface[]|null
|
||||
*/
|
||||
protected ?array $handlerInstances = NULL;
|
||||
|
||||
/**
|
||||
* Constructs a ParentEntityHandlerManager object.
|
||||
*
|
||||
* @param \Traversable $namespaces
|
||||
* An object that implements \Traversable which contains the root paths
|
||||
* keyed by the corresponding namespace to look for plugin implementations.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* The cache backend.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(
|
||||
\Traversable $namespaces,
|
||||
CacheBackendInterface $cache_backend,
|
||||
ModuleHandlerInterface $module_handler,
|
||||
EntityTypeManagerInterface $entity_type_manager,
|
||||
) {
|
||||
parent::__construct(
|
||||
'Plugin/ParentEntityHandler',
|
||||
$namespaces,
|
||||
$module_handler,
|
||||
ParentEntityHandlerInterface::class,
|
||||
ParentEntityHandler::class,
|
||||
);
|
||||
|
||||
$this->alterInfo('parent_entity_handler_info');
|
||||
$this->setCacheBackend($cache_backend, 'parent_entity_handler_plugins');
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAvailableHandlers(): array {
|
||||
if ($this->handlerInstances !== NULL) {
|
||||
return $this->handlerInstances;
|
||||
}
|
||||
|
||||
$this->handlerInstances = [];
|
||||
$definitions = $this->getDefinitions();
|
||||
|
||||
// Sort by weight.
|
||||
uasort($definitions, function ($a, $b) {
|
||||
return ($a['weight'] ?? 0) <=> ($b['weight'] ?? 0);
|
||||
});
|
||||
|
||||
foreach ($definitions as $plugin_id => $definition) {
|
||||
$handler = $this->createInstance($plugin_id);
|
||||
if ($handler instanceof ParentEntityHandlerInterface && $handler->isAvailable()) {
|
||||
$this->handlerInstances[$plugin_id] = $handler;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->handlerInstances;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHandlerForEntityType(string $entity_type_id): ?ParentEntityHandlerInterface {
|
||||
foreach ($this->getAvailableHandlers() as $handler) {
|
||||
if ($handler->getEntityTypeId() === $entity_type_id) {
|
||||
return $handler;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHandlerForEntity(EntityInterface $entity): ?ParentEntityHandlerInterface {
|
||||
foreach ($this->getAvailableHandlers() as $handler) {
|
||||
if ($handler->handlesEntity($entity)) {
|
||||
return $handler;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSupportedEntityTypes(): array {
|
||||
$types = [];
|
||||
|
||||
foreach ($this->getAvailableHandlers() as $handler) {
|
||||
$types[$handler->getEntityTypeId()] = $handler->getLabel();
|
||||
}
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clearsSiteSection(string $entity_type_id): bool {
|
||||
$handler = $this->getHandlerForEntityType($entity_type_id);
|
||||
return $handler ? $handler->clearsSiteSection() : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSortField(string $entity_type_id): string {
|
||||
$handler = $this->getHandlerForEntityType($entity_type_id);
|
||||
return $handler ? $handler->getSortField() : 'title';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEntityFromRoute(RouteMatchInterface $route_match): ?EntityInterface {
|
||||
foreach ($this->getAvailableHandlers() as $handler) {
|
||||
$entity = $handler->getEntityFromRoute($route_match);
|
||||
if ($entity) {
|
||||
return $entity;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildBreadcrumbForEntity(Breadcrumb $breadcrumb, EntityInterface $entity): bool {
|
||||
$handler = $this->getHandlerForEntity($entity);
|
||||
if ($handler) {
|
||||
$handler->buildBreadcrumb($breadcrumb, $entity);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSiteSectionFromParent(string $entity_type_id, int|string $entity_id): int|string|null {
|
||||
$handler = $this->getHandlerForEntityType($entity_type_id);
|
||||
if (!$handler) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$entity = $this->entityTypeManager
|
||||
->getStorage($entity_type_id)
|
||||
->load($entity_id);
|
||||
|
||||
if (!$entity) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return $handler->getSiteSectionId($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clearCachedDefinitions(): void {
|
||||
parent::clearCachedDefinitions();
|
||||
$this->handlerInstances = NULL;
|
||||
}
|
||||
|
||||
}
|
||||
121
src/ParentEntityHandler/ParentEntityHandlerManagerInterface.php
Normal file
121
src/ParentEntityHandler/ParentEntityHandlerManagerInterface.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\site_structure\ParentEntityHandler;
|
||||
|
||||
use Drupal\Core\Breadcrumb\Breadcrumb;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
* Interface for the parent entity handler plugin manager.
|
||||
*
|
||||
* This manager provides centralized access to all registered parent entity
|
||||
* handlers and their aggregated functionality.
|
||||
*/
|
||||
interface ParentEntityHandlerManagerInterface {
|
||||
|
||||
/**
|
||||
* Gets all available handlers.
|
||||
*
|
||||
* Handlers are available if their provider module is installed and the
|
||||
* entity type exists.
|
||||
*
|
||||
* @return \Drupal\site_structure\ParentEntityHandler\ParentEntityHandlerInterface[]
|
||||
* An array of available handler instances, keyed by plugin ID.
|
||||
*/
|
||||
public function getAvailableHandlers(): array;
|
||||
|
||||
/**
|
||||
* Gets a handler for the given entity type.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID.
|
||||
*
|
||||
* @return \Drupal\site_structure\ParentEntityHandler\ParentEntityHandlerInterface|null
|
||||
* The handler for the entity type, or NULL if none found.
|
||||
*/
|
||||
public function getHandlerForEntityType(string $entity_type_id): ?ParentEntityHandlerInterface;
|
||||
|
||||
/**
|
||||
* Gets a handler that can handle the given entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity.
|
||||
*
|
||||
* @return \Drupal\site_structure\ParentEntityHandler\ParentEntityHandlerInterface|null
|
||||
* The handler for the entity, or NULL if none found.
|
||||
*/
|
||||
public function getHandlerForEntity(EntityInterface $entity): ?ParentEntityHandlerInterface;
|
||||
|
||||
/**
|
||||
* Gets supported entity types for the settings form.
|
||||
*
|
||||
* @return array
|
||||
* An associative array of entity_type_id => label for available handlers.
|
||||
*/
|
||||
public function getSupportedEntityTypes(): array;
|
||||
|
||||
/**
|
||||
* Checks if the given entity type clears site section.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if field_site_section should be cleared, FALSE otherwise.
|
||||
*/
|
||||
public function clearsSiteSection(string $entity_type_id): bool;
|
||||
|
||||
/**
|
||||
* Gets the sort field for the given entity type.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID.
|
||||
*
|
||||
* @return string
|
||||
* The sort field name, or 'title' as default.
|
||||
*/
|
||||
public function getSortField(string $entity_type_id): string;
|
||||
|
||||
/**
|
||||
* Gets an entity from the current route using available handlers.
|
||||
*
|
||||
* Iterates through available handlers to find an entity on the current route.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The current route match.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|null
|
||||
* The entity from the route, or NULL if not found.
|
||||
*/
|
||||
public function getEntityFromRoute(RouteMatchInterface $route_match): ?EntityInterface;
|
||||
|
||||
/**
|
||||
* Builds breadcrumb for the given entity using the appropriate handler.
|
||||
*
|
||||
* @param \Drupal\Core\Breadcrumb\Breadcrumb $breadcrumb
|
||||
* The breadcrumb to add entries to.
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to build breadcrumb for.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if a handler was found and built the breadcrumb, FALSE otherwise.
|
||||
*/
|
||||
public function buildBreadcrumbForEntity(Breadcrumb $breadcrumb, EntityInterface $entity): bool;
|
||||
|
||||
/**
|
||||
* Gets the site section ID from a parent entity.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The parent entity type ID.
|
||||
* @param int|string $entity_id
|
||||
* The parent entity ID.
|
||||
*
|
||||
* @return int|string|null
|
||||
* The site section term ID, or NULL if not applicable.
|
||||
*/
|
||||
public function getSiteSectionFromParent(string $entity_type_id, int|string $entity_id): int|string|null;
|
||||
|
||||
}
|
||||
@@ -8,13 +8,11 @@ use Drupal\Core\Block\BlockBase;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\group\Entity\GroupInterface;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Drupal\taxonomy\TermInterface;
|
||||
use Drupal\user\UserInterface;
|
||||
use Drupal\site_structure\ParentEntityHandler\ParentEntityHandlerManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
@@ -50,6 +48,13 @@ class SiteStructureMenuBlock extends BlockBase implements ContainerFactoryPlugin
|
||||
*/
|
||||
protected RouteMatchInterface $routeMatch;
|
||||
|
||||
/**
|
||||
* The parent entity handler manager.
|
||||
*
|
||||
* @var \Drupal\site_structure\ParentEntityHandler\ParentEntityHandlerManagerInterface
|
||||
*/
|
||||
protected ParentEntityHandlerManagerInterface $handlerManager;
|
||||
|
||||
/**
|
||||
* Cache tags collected during tree building.
|
||||
*
|
||||
@@ -66,10 +71,12 @@ class SiteStructureMenuBlock extends BlockBase implements ContainerFactoryPlugin
|
||||
$plugin_definition,
|
||||
EntityTypeManagerInterface $entity_type_manager,
|
||||
RouteMatchInterface $route_match,
|
||||
ParentEntityHandlerManagerInterface $handler_manager,
|
||||
) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->routeMatch = $route_match;
|
||||
$this->handlerManager = $handler_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,6 +89,7 @@ class SiteStructureMenuBlock extends BlockBase implements ContainerFactoryPlugin
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('current_route_match'),
|
||||
$container->get('plugin.manager.parent_entity_handler'),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -265,31 +273,13 @@ class SiteStructureMenuBlock extends BlockBase implements ContainerFactoryPlugin
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets ancestor entity from current route (term, user, or group page).
|
||||
* Gets ancestor entity from current route using available handlers.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|null
|
||||
* The ancestor entity or NULL.
|
||||
*/
|
||||
protected function getAncestorFromRoute(): ?EntityInterface {
|
||||
// Check taxonomy term.
|
||||
$term = $this->routeMatch->getParameter('taxonomy_term');
|
||||
if ($term instanceof TermInterface && $term->bundle() === 'site_section') {
|
||||
return $term;
|
||||
}
|
||||
|
||||
// Check user.
|
||||
$user = $this->routeMatch->getParameter('user');
|
||||
if ($user instanceof UserInterface) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
// Check group (if module exists).
|
||||
$group = $this->routeMatch->getParameter('group');
|
||||
if ($group instanceof GroupInterface) {
|
||||
return $group;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
return $this->handlerManager->getEntityFromRoute($this->routeMatch);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
72
src/Plugin/ParentEntityHandler/NodeHandler.php
Normal file
72
src/Plugin/ParentEntityHandler/NodeHandler.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\site_structure\Plugin\ParentEntityHandler;
|
||||
|
||||
use Drupal\Core\Breadcrumb\Breadcrumb;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Drupal\site_structure\Attribute\ParentEntityHandler;
|
||||
use Drupal\site_structure\ParentEntityHandler\ParentEntityHandlerBase;
|
||||
|
||||
/**
|
||||
* Handler for node entities.
|
||||
*/
|
||||
#[ParentEntityHandler(
|
||||
id: 'node',
|
||||
label: new TranslatableMarkup('Content Types (node)'),
|
||||
entity_type_id: 'node',
|
||||
clears_site_section: FALSE,
|
||||
sort_field: 'title',
|
||||
bundle_restrictions: ['content_page', 'section_page'],
|
||||
weight: 5,
|
||||
)]
|
||||
class NodeHandler extends ParentEntityHandlerBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEntityFromRoute(RouteMatchInterface $route_match): ?EntityInterface {
|
||||
$node = $route_match->getParameter('node');
|
||||
|
||||
if (!$node instanceof NodeInterface) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!$this->handlesEntity($node)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildBreadcrumb(Breadcrumb $breadcrumb, EntityInterface $entity): void {
|
||||
// Node breadcrumbs are handled separately through the parent chain.
|
||||
// This method is not typically called for nodes.
|
||||
$breadcrumb->addCacheableDependency($entity);
|
||||
$breadcrumb->addLink($entity->toLink());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSiteSectionId(EntityInterface $entity): int|string|null {
|
||||
if (!$entity instanceof NodeInterface) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Get site section from the node if it has one.
|
||||
if ($entity->hasField('field_site_section') && !$entity->get('field_site_section')->isEmpty()) {
|
||||
return $entity->get('field_site_section')->target_id;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}
|
||||
80
src/Plugin/ParentEntityHandler/TaxonomyTermHandler.php
Normal file
80
src/Plugin/ParentEntityHandler/TaxonomyTermHandler.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\site_structure\Plugin\ParentEntityHandler;
|
||||
|
||||
use Drupal\Core\Breadcrumb\Breadcrumb;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\site_structure\Attribute\ParentEntityHandler;
|
||||
use Drupal\site_structure\ParentEntityHandler\ParentEntityHandlerBase;
|
||||
use Drupal\taxonomy\TermInterface;
|
||||
|
||||
/**
|
||||
* Handler for taxonomy term entities.
|
||||
*/
|
||||
#[ParentEntityHandler(
|
||||
id: 'taxonomy_term',
|
||||
label: new TranslatableMarkup('Taxonomy Vocabularies (taxonomy_term)'),
|
||||
entity_type_id: 'taxonomy_term',
|
||||
clears_site_section: FALSE,
|
||||
sort_field: 'name',
|
||||
bundle_restrictions: ['site_section'],
|
||||
weight: 0,
|
||||
)]
|
||||
class TaxonomyTermHandler extends ParentEntityHandlerBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEntityFromRoute(\Drupal\Core\Routing\RouteMatchInterface $route_match): ?EntityInterface {
|
||||
$term = $route_match->getParameter('taxonomy_term');
|
||||
|
||||
if (!$term instanceof TermInterface) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!$this->handlesEntity($term)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return $term;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildBreadcrumb(Breadcrumb $breadcrumb, EntityInterface $entity): void {
|
||||
if (!$entity instanceof TermInterface) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add all ancestors in the taxonomy hierarchy.
|
||||
$term_storage = $this->entityTypeManager->getStorage('taxonomy_term');
|
||||
$ancestors = $term_storage->loadAllParents($entity->id());
|
||||
$ancestors = array_reverse($ancestors);
|
||||
|
||||
foreach ($ancestors as $ancestor) {
|
||||
$breadcrumb->addCacheableDependency($ancestor);
|
||||
$breadcrumb->addLink($ancestor->toLink());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSiteSectionId(EntityInterface $entity): int|string|null {
|
||||
if (!$entity instanceof TermInterface) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Only site_section terms can be site sections.
|
||||
if ($entity->bundle() !== 'site_section') {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return $entity->id();
|
||||
}
|
||||
|
||||
}
|
||||
47
src/Plugin/ParentEntityHandler/UserHandler.php
Normal file
47
src/Plugin/ParentEntityHandler/UserHandler.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\site_structure\Plugin\ParentEntityHandler;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\site_structure\Attribute\ParentEntityHandler;
|
||||
use Drupal\site_structure\ParentEntityHandler\ParentEntityHandlerBase;
|
||||
use Drupal\user\UserInterface;
|
||||
|
||||
/**
|
||||
* Handler for user entities.
|
||||
*/
|
||||
#[ParentEntityHandler(
|
||||
id: 'user',
|
||||
label: new TranslatableMarkup('Users (user)'),
|
||||
entity_type_id: 'user',
|
||||
clears_site_section: TRUE,
|
||||
sort_field: 'name',
|
||||
weight: 10,
|
||||
)]
|
||||
class UserHandler extends ParentEntityHandlerBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEntityFromRoute(RouteMatchInterface $route_match): ?EntityInterface {
|
||||
$user = $route_match->getParameter('user');
|
||||
|
||||
if (!$user instanceof UserInterface) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handlesEntity(EntityInterface $entity): bool {
|
||||
return $entity instanceof UserInterface;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -336,3 +336,31 @@ msgstr "Expandir automaticamente os itens de menu na trilha ativa."
|
||||
#: templates/site-structure-menu.html.twig
|
||||
msgid "Site structure navigation"
|
||||
msgstr "Navegação da estrutura do site"
|
||||
|
||||
#: src/Plugin/ParentEntityHandler/TaxonomyTermHandler.php
|
||||
#: src/Form/SiteStructureSettingsForm.php
|
||||
msgid "Taxonomy Vocabularies (taxonomy_term)"
|
||||
msgstr "Vocabulários de Taxonomia (taxonomy_term)"
|
||||
|
||||
#: src/Plugin/ParentEntityHandler/UserHandler.php
|
||||
#: src/Form/SiteStructureSettingsForm.php
|
||||
msgid "Users (user)"
|
||||
msgstr "Usuários (user)"
|
||||
|
||||
#: src/Plugin/ParentEntityHandler/NodeHandler.php
|
||||
#: src/Form/SiteStructureSettingsForm.php
|
||||
msgid "Content Types (node)"
|
||||
msgstr "Tipos de Conteúdo (node)"
|
||||
|
||||
#: modules/site_structure_group/src/Plugin/ParentEntityHandler/GroupHandler.php
|
||||
#: src/Form/SiteStructureSettingsForm.php
|
||||
msgid "Groups (group)"
|
||||
msgstr "Grupos (group)"
|
||||
|
||||
#: modules/site_structure_group/site_structure_group.info.yml
|
||||
msgid "Site Structure Group Integration"
|
||||
msgstr "Integração de Grupos do Site Structure"
|
||||
|
||||
#: modules/site_structure_group/site_structure_group.info.yml
|
||||
msgid "Provides Group entity support as parent type for Site Structure module."
|
||||
msgstr "Fornece suporte a entidades Grupo como tipo pai para o módulo Site Structure."
|
||||
|
||||
Reference in New Issue
Block a user