diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 2990a41..b9a4533 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -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 + 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. | diff --git a/modules/site_structure_group/site_structure_group.info.yml b/modules/site_structure_group/site_structure_group.info.yml new file mode 100644 index 0000000..1f0406e --- /dev/null +++ b/modules/site_structure_group/site_structure_group.info.yml @@ -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 diff --git a/modules/site_structure_group/src/Plugin/ParentEntityHandler/GroupHandler.php b/modules/site_structure_group/src/Plugin/ParentEntityHandler/GroupHandler.php new file mode 100644 index 0000000..d033082 --- /dev/null +++ b/modules/site_structure_group/src/Plugin/ParentEntityHandler/GroupHandler.php @@ -0,0 +1,51 @@ +getParameter('group'); + + if (!$group instanceof GroupInterface) { + return NULL; + } + + return $group; + } + + /** + * {@inheritdoc} + */ + public function handlesEntity(EntityInterface $entity): bool { + return $entity instanceof GroupInterface; + } + +} diff --git a/site_structure.module b/site_structure.module index 7310e6b..39ad5a5 100644 --- a/site_structure.module +++ b/site_structure.module @@ -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; } diff --git a/site_structure.services.yml b/site_structure.services.yml index 12732c7..5301808 100644 --- a/site_structure.services.yml +++ b/site_structure.services.yml @@ -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 } diff --git a/src/Attribute/ParentEntityHandler.php b/src/Attribute/ParentEntityHandler.php new file mode 100644 index 0000000..4aed5e0 --- /dev/null +++ b/src/Attribute/ParentEntityHandler.php @@ -0,0 +1,66 @@ +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. * diff --git a/src/Form/SiteStructureSettingsForm.php b/src/Form/SiteStructureSettingsForm.php index 22cd7ce..ba04496 100644 --- a/src/Form/SiteStructureSettingsForm.php +++ b/src/Form/SiteStructureSettingsForm.php @@ -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, diff --git a/src/ParentEntityHandler/ParentEntityHandlerBase.php b/src/ParentEntityHandler/ParentEntityHandlerBase.php new file mode 100644 index 0000000..defcc74 --- /dev/null +++ b/src/ParentEntityHandler/ParentEntityHandlerBase.php @@ -0,0 +1,198 @@ +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); + } + +} diff --git a/src/ParentEntityHandler/ParentEntityHandlerInterface.php b/src/ParentEntityHandler/ParentEntityHandlerInterface.php new file mode 100644 index 0000000..af612d3 --- /dev/null +++ b/src/ParentEntityHandler/ParentEntityHandlerInterface.php @@ -0,0 +1,124 @@ +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; + } + +} diff --git a/src/ParentEntityHandler/ParentEntityHandlerManagerInterface.php b/src/ParentEntityHandler/ParentEntityHandlerManagerInterface.php new file mode 100644 index 0000000..3287024 --- /dev/null +++ b/src/ParentEntityHandler/ParentEntityHandlerManagerInterface.php @@ -0,0 +1,121 @@ + 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; + +} diff --git a/src/Plugin/Block/SiteStructureMenuBlock.php b/src/Plugin/Block/SiteStructureMenuBlock.php index b116b54..85b8715 100644 --- a/src/Plugin/Block/SiteStructureMenuBlock.php +++ b/src/Plugin/Block/SiteStructureMenuBlock.php @@ -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); } /** diff --git a/src/Plugin/ParentEntityHandler/NodeHandler.php b/src/Plugin/ParentEntityHandler/NodeHandler.php new file mode 100644 index 0000000..86ff857 --- /dev/null +++ b/src/Plugin/ParentEntityHandler/NodeHandler.php @@ -0,0 +1,72 @@ +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; + } + +} diff --git a/src/Plugin/ParentEntityHandler/TaxonomyTermHandler.php b/src/Plugin/ParentEntityHandler/TaxonomyTermHandler.php new file mode 100644 index 0000000..c2b344b --- /dev/null +++ b/src/Plugin/ParentEntityHandler/TaxonomyTermHandler.php @@ -0,0 +1,80 @@ +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(); + } + +} diff --git a/src/Plugin/ParentEntityHandler/UserHandler.php b/src/Plugin/ParentEntityHandler/UserHandler.php new file mode 100644 index 0000000..d953380 --- /dev/null +++ b/src/Plugin/ParentEntityHandler/UserHandler.php @@ -0,0 +1,47 @@ +getParameter('user'); + + if (!$user instanceof UserInterface) { + return NULL; + } + + return $user; + } + + /** + * {@inheritdoc} + */ + public function handlesEntity(EntityInterface $entity): bool { + return $entity instanceof UserInterface; + } + +} diff --git a/translations/pt-br.po b/translations/pt-br.po index 47a62a5..30a1407 100644 --- a/translations/pt-br.po +++ b/translations/pt-br.po @@ -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."