mirror of
https://gitlab.unicamp.br/infimecc_drupal11_modules/structural_pages.git
synced 2026-03-10 02:17:42 -03:00
Initial commit: Site Structure module for Drupal
Drupal module that provides hierarchical site structure management with support for sections, categories, and content items. Includes path aliases with tokens, breadcrumb integration, and admin interface. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
320
src/Breadcrumb/SectionBreadcrumbBuilder.php
Normal file
320
src/Breadcrumb/SectionBreadcrumbBuilder.php
Normal file
@@ -0,0 +1,320 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Provides a breadcrumb builder for section_page and content_page content types.
|
||||
*/
|
||||
class SectionBreadcrumbBuilder implements BreadcrumbBuilderInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected EntityTypeManagerInterface $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Content types that this builder applies to.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected array $applicableBundles = ['section_page', 'content_page'];
|
||||
|
||||
/**
|
||||
* Constructs a SectionBreadcrumbBuilder object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applies(RouteMatchInterface $route_match): bool {
|
||||
$node = $route_match->getParameter('node');
|
||||
|
||||
if ($node instanceof NodeInterface) {
|
||||
return in_array($node->bundle(), $this->applicableBundles, TRUE);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build(RouteMatchInterface $route_match): Breadcrumb {
|
||||
$breadcrumb = new Breadcrumb();
|
||||
$breadcrumb->addCacheContexts(['route']);
|
||||
|
||||
$node = $route_match->getParameter('node');
|
||||
|
||||
if (!$node instanceof NodeInterface) {
|
||||
return $breadcrumb;
|
||||
}
|
||||
|
||||
$breadcrumb->addCacheableDependency($node);
|
||||
|
||||
// Add "Home" as first item.
|
||||
$breadcrumb->addLink(Link::createFromRoute($this->t('Home'), '<front>'));
|
||||
|
||||
// Determine the context type for content_page.
|
||||
$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;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No parent context, check for site section directly.
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// For content_page, add node parent hierarchy.
|
||||
if ($node->bundle() === 'content_page') {
|
||||
$this->addParentBreadcrumbs($breadcrumb, $node);
|
||||
}
|
||||
|
||||
return $breadcrumb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the root parent context for a content_page.
|
||||
*
|
||||
* Traverses up the parent chain to find the root context (user, group, or taxonomy).
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* The content_page node.
|
||||
*
|
||||
* @return array|null
|
||||
* An array with 'type' and 'entity' keys, or NULL if no context.
|
||||
*/
|
||||
protected function getParentContext(NodeInterface $node): ?array {
|
||||
if ($node->bundle() !== 'content_page') {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$visited = [];
|
||||
$current = $node;
|
||||
|
||||
while ($current instanceof NodeInterface &&
|
||||
$current->hasField('field_parent_page') &&
|
||||
!$current->get('field_parent_page')->isEmpty()) {
|
||||
|
||||
$parent_field = $current->get('field_parent_page')->first();
|
||||
if (!$parent_field) {
|
||||
break;
|
||||
}
|
||||
|
||||
$parent_entity_type = $parent_field->target_type ?? NULL;
|
||||
$parent_id = $parent_field->target_id ?? NULL;
|
||||
|
||||
if (!$parent_entity_type || !$parent_id) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Avoid infinite loops.
|
||||
$visit_key = $parent_entity_type . ':' . $parent_id;
|
||||
if (isset($visited[$visit_key])) {
|
||||
break;
|
||||
}
|
||||
$visited[$visit_key] = TRUE;
|
||||
|
||||
$parent = $this->entityTypeManager
|
||||
->getStorage($parent_entity_type)
|
||||
->load($parent_id);
|
||||
|
||||
if (!$parent) {
|
||||
break;
|
||||
}
|
||||
|
||||
// If parent is user or group, that's our context.
|
||||
if (in_array($parent_entity_type, ['user', 'group'])) {
|
||||
return [
|
||||
'type' => $parent_entity_type,
|
||||
'entity' => $parent,
|
||||
];
|
||||
}
|
||||
|
||||
// If parent is taxonomy_term, that's our context (handled via site_section).
|
||||
if ($parent_entity_type === 'taxonomy_term') {
|
||||
return [
|
||||
'type' => 'taxonomy_term',
|
||||
'entity' => $parent,
|
||||
];
|
||||
}
|
||||
|
||||
// If parent is a node, continue traversing.
|
||||
if ($parent instanceof NodeInterface) {
|
||||
$current = $parent;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
* @param \Drupal\Core\Breadcrumb\Breadcrumb $breadcrumb
|
||||
* The breadcrumb object.
|
||||
* @param int|string $term_id
|
||||
* The taxonomy term ID.
|
||||
*/
|
||||
protected function addTaxonomyBreadcrumbs(Breadcrumb $breadcrumb, int|string $term_id): void {
|
||||
$term_storage = $this->entityTypeManager->getStorage('taxonomy_term');
|
||||
$ancestors = $term_storage->loadAllParents($term_id);
|
||||
$ancestors = array_reverse($ancestors);
|
||||
|
||||
foreach ($ancestors as $ancestor) {
|
||||
$breadcrumb->addCacheableDependency($ancestor);
|
||||
$breadcrumb->addLink($ancestor->toLink());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds breadcrumbs based on content_page parent hierarchy.
|
||||
*
|
||||
* Only adds node parents to the breadcrumb, not user/group/taxonomy
|
||||
* which are handled separately.
|
||||
*
|
||||
* @param \Drupal\Core\Breadcrumb\Breadcrumb $breadcrumb
|
||||
* The breadcrumb object.
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* The current node.
|
||||
*/
|
||||
protected function addParentBreadcrumbs(Breadcrumb $breadcrumb, NodeInterface $node): void {
|
||||
$parents = $this->getNodeParents($node);
|
||||
$parents = array_reverse($parents);
|
||||
|
||||
foreach ($parents as $parent) {
|
||||
$breadcrumb->addCacheableDependency($parent);
|
||||
$breadcrumb->addLink($parent->toLink());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all node parents of a content_page.
|
||||
*
|
||||
* Stops when encountering a non-node parent (user, group, taxonomy_term).
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* The node.
|
||||
*
|
||||
* @return \Drupal\node\NodeInterface[]
|
||||
* Array of parent nodes, from closest to farthest.
|
||||
*/
|
||||
protected function getNodeParents(NodeInterface $node): array {
|
||||
$parents = [];
|
||||
$visited = [];
|
||||
$current = $node;
|
||||
|
||||
while ($current instanceof NodeInterface &&
|
||||
$current->hasField('field_parent_page') &&
|
||||
!$current->get('field_parent_page')->isEmpty()) {
|
||||
|
||||
$parent_field = $current->get('field_parent_page')->first();
|
||||
if (!$parent_field) {
|
||||
break;
|
||||
}
|
||||
|
||||
$parent_entity_type = $parent_field->target_type ?? NULL;
|
||||
$parent_id = $parent_field->target_id ?? NULL;
|
||||
|
||||
if (!$parent_entity_type || !$parent_id) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Only continue if parent is a node.
|
||||
if ($parent_entity_type !== 'node') {
|
||||
break;
|
||||
}
|
||||
|
||||
// Avoid infinite loops.
|
||||
if (isset($visited[$parent_id])) {
|
||||
break;
|
||||
}
|
||||
$visited[$parent_id] = TRUE;
|
||||
|
||||
$parent = $this->entityTypeManager
|
||||
->getStorage('node')
|
||||
->load($parent_id);
|
||||
|
||||
if (!$parent instanceof NodeInterface) {
|
||||
break;
|
||||
}
|
||||
|
||||
$parents[] = $parent;
|
||||
$current = $parent;
|
||||
}
|
||||
|
||||
return $parents;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user