mirror of
https://gitlab.unicamp.br/infimecc_drupal11_modules/structural_pages.git
synced 2026-05-05 21:15:29 -03:00
Altera lógica de Site Section e adiciona redirect em content_page
- Adiciona campo field_redirect_link (link) no bundle content_page; EventSubscriber emite redirect 301 quando o campo está preenchido - field_site_section passa a ser obrigatório - Formulário de content_page: AJAX no site section, select hierárquico de página pai filtrado por seção, validação customizada - StructuralPagesMenuBlock: MAX_DEPTH 10→50, nova lógica de raiz via field_site_section, variável ancestor_url no render array - Template do menu: novas classes BEM/Gva, suporte a is_redirect, usa ancestor_url em vez de chamada Twig direta - CSS do menu reescrito com estilos flyout/sidebar; Select2 adicionado para o campo de página pai no formulário admin - display_submitted desabilitado no tipo content_page Co-Authored-By: Bauer <henrique@webcontent.com.br> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
79
src/EventSubscriber/StructuralPagesRedirectSubscriber.php
Normal file
79
src/EventSubscriber/StructuralPagesRedirectSubscriber.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\structural_pages\EventSubscriber;
|
||||
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Routing\TrustedRedirectResponse;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
||||
/**
|
||||
* Redirects content pages to their configured link if set.
|
||||
*/
|
||||
class StructuralPagesRedirectSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* The route match.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteMatchInterface
|
||||
*/
|
||||
protected $routeMatch;
|
||||
|
||||
/**
|
||||
* Constructs a new StructuralPagesRedirectSubscriber object.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The route match.
|
||||
*/
|
||||
public function __construct(RouteMatchInterface $route_match)
|
||||
{
|
||||
$this->routeMatch = $route_match;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
$events[KernelEvents::REQUEST][] = ['onKernelRequest', 30];
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects if the node has a redirect link.
|
||||
*
|
||||
* @param \Symfony\Component\HttpKernel\Event\RequestEvent $event
|
||||
* The event to process.
|
||||
*/
|
||||
public function onKernelRequest(RequestEvent $event)
|
||||
{
|
||||
if (!$event->isMainRequest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$route_name = $this->routeMatch->getRouteName();
|
||||
if ($route_name === 'entity.node.canonical') {
|
||||
$node = $this->routeMatch->getParameter('node');
|
||||
if ($node instanceof NodeInterface && $node->bundle() === 'content_page') {
|
||||
if ($node->hasField('field_redirect_link') && !$node->get('field_redirect_link')->isEmpty()) {
|
||||
try {
|
||||
/** @var \Drupal\link\LinkItemInterface $link_item */
|
||||
$link_item = $node->get('field_redirect_link')->first();
|
||||
$url = $link_item->getUrl()->toString();
|
||||
|
||||
// Cacheable redirect response.
|
||||
$response = new TrustedRedirectResponse($url, 301);
|
||||
$response->addCacheableDependency($node);
|
||||
$event->setResponse($response);
|
||||
} catch (\Exception $e) {
|
||||
// If the link is invalid, proceed normally.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,12 +27,13 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class StructuralPagesMenuBlock extends BlockBase implements ContainerFactoryPluginInterface {
|
||||
class StructuralPagesMenuBlock extends BlockBase implements ContainerFactoryPluginInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Maximum depth to prevent infinite loops.
|
||||
* Maximum depth to prevent infinite loops. We set it very high (50) to allow unlimited sublevels.
|
||||
*/
|
||||
protected const MAX_DEPTH = 10;
|
||||
protected const MAX_DEPTH = 50;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
@@ -82,7 +83,8 @@ class StructuralPagesMenuBlock extends BlockBase implements ContainerFactoryPlug
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static
|
||||
{
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
@@ -96,9 +98,10 @@ class StructuralPagesMenuBlock extends BlockBase implements ContainerFactoryPlug
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration(): array {
|
||||
public function defaultConfiguration(): array
|
||||
{
|
||||
return [
|
||||
'max_depth' => 3,
|
||||
'max_depth' => 50,
|
||||
'show_ancestor_title' => TRUE,
|
||||
'expand_active_trail' => TRUE,
|
||||
] + parent::defaultConfiguration();
|
||||
@@ -107,7 +110,8 @@ class StructuralPagesMenuBlock extends BlockBase implements ContainerFactoryPlug
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockForm($form, FormStateInterface $form_state): array {
|
||||
public function blockForm($form, FormStateInterface $form_state): array
|
||||
{
|
||||
$form = parent::blockForm($form, $form_state);
|
||||
|
||||
$form['max_depth'] = [
|
||||
@@ -139,7 +143,8 @@ class StructuralPagesMenuBlock extends BlockBase implements ContainerFactoryPlug
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockSubmit($form, FormStateInterface $form_state): void {
|
||||
public function blockSubmit($form, FormStateInterface $form_state): void
|
||||
{
|
||||
$this->configuration['max_depth'] = $form_state->getValue('max_depth');
|
||||
$this->configuration['show_ancestor_title'] = $form_state->getValue('show_ancestor_title');
|
||||
$this->configuration['expand_active_trail'] = $form_state->getValue('expand_active_trail');
|
||||
@@ -148,7 +153,8 @@ class StructuralPagesMenuBlock extends BlockBase implements ContainerFactoryPlug
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build(): array {
|
||||
public function build(): array
|
||||
{
|
||||
$this->cacheTags = [];
|
||||
|
||||
// Get current context.
|
||||
@@ -177,6 +183,7 @@ class StructuralPagesMenuBlock extends BlockBase implements ContainerFactoryPlug
|
||||
$build = [
|
||||
'#theme' => 'structural_pages_menu',
|
||||
'#ancestor' => $ancestor,
|
||||
'#ancestor_url' => $ancestor->hasLinkTemplate('canonical') ? $ancestor->toUrl()->toString() : '',
|
||||
'#tree' => $tree,
|
||||
'#active_trail' => $active_trail,
|
||||
'#show_ancestor_title' => $this->configuration['show_ancestor_title'],
|
||||
@@ -195,15 +202,15 @@ class StructuralPagesMenuBlock extends BlockBase implements ContainerFactoryPlug
|
||||
* @return \Drupal\node\NodeInterface|null
|
||||
* The current node or NULL.
|
||||
*/
|
||||
protected function getCurrentNode(): ?NodeInterface {
|
||||
protected function getCurrentNode(): ?NodeInterface
|
||||
{
|
||||
// Try to get from block context first.
|
||||
try {
|
||||
$node = $this->getContextValue('node');
|
||||
if ($node instanceof NodeInterface) {
|
||||
return $node;
|
||||
}
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
} catch (\Exception $e) {
|
||||
// Context not available.
|
||||
}
|
||||
|
||||
@@ -221,7 +228,8 @@ class StructuralPagesMenuBlock extends BlockBase implements ContainerFactoryPlug
|
||||
* @return \Drupal\Core\Entity\EntityInterface|null
|
||||
* The ancestor entity (term, user, or group) or NULL.
|
||||
*/
|
||||
protected function findAncestor(?NodeInterface $node): ?EntityInterface {
|
||||
protected function findAncestor(?NodeInterface $node): ?EntityInterface
|
||||
{
|
||||
if (!$node) {
|
||||
// Check if we're on a taxonomy term, user, or group page.
|
||||
return $this->getAncestorFromRoute();
|
||||
@@ -278,7 +286,8 @@ class StructuralPagesMenuBlock extends BlockBase implements ContainerFactoryPlug
|
||||
* @return \Drupal\Core\Entity\EntityInterface|null
|
||||
* The ancestor entity or NULL.
|
||||
*/
|
||||
protected function getAncestorFromRoute(): ?EntityInterface {
|
||||
protected function getAncestorFromRoute(): ?EntityInterface
|
||||
{
|
||||
return $this->handlerManager->getEntityFromRoute($this->routeMatch);
|
||||
}
|
||||
|
||||
@@ -291,7 +300,8 @@ class StructuralPagesMenuBlock extends BlockBase implements ContainerFactoryPlug
|
||||
* @return array
|
||||
* The menu tree structure.
|
||||
*/
|
||||
protected function buildTree(EntityInterface $ancestor): array {
|
||||
protected function buildTree(EntityInterface $ancestor): array
|
||||
{
|
||||
$root_pages = $this->getChildPages($ancestor->getEntityTypeId(), (string) $ancestor->id());
|
||||
|
||||
if (empty($root_pages)) {
|
||||
@@ -321,14 +331,29 @@ class StructuralPagesMenuBlock extends BlockBase implements ContainerFactoryPlug
|
||||
* @return array
|
||||
* The branch structure.
|
||||
*/
|
||||
protected function buildBranch(NodeInterface $node, int $current_depth, int $max_depth): array {
|
||||
protected function buildBranch(NodeInterface $node, int $current_depth, int $max_depth): array
|
||||
{
|
||||
$this->cacheTags[] = 'node:' . $node->id();
|
||||
|
||||
$url = $node->toUrl()->toString();
|
||||
$is_redirect = FALSE;
|
||||
if ($node->hasField('field_redirect_link') && !$node->get('field_redirect_link')->isEmpty()) {
|
||||
try {
|
||||
/** @var \Drupal\link\LinkItemInterface $link_item */
|
||||
$link_item = $node->get('field_redirect_link')->first();
|
||||
$url = $link_item->getUrl()->toString();
|
||||
$is_redirect = TRUE;
|
||||
} catch (\Exception $e) {
|
||||
// Fallback to node url if the redirect link is invalid.
|
||||
}
|
||||
}
|
||||
|
||||
$branch = [
|
||||
'entity' => $node,
|
||||
'id' => $node->id(),
|
||||
'title' => $node->label(),
|
||||
'url' => $node->toUrl()->toString(),
|
||||
'url' => $url,
|
||||
'is_redirect' => $is_redirect,
|
||||
'depth' => $current_depth,
|
||||
'children' => [],
|
||||
];
|
||||
@@ -354,15 +379,24 @@ class StructuralPagesMenuBlock extends BlockBase implements ContainerFactoryPlug
|
||||
* @return \Drupal\node\NodeInterface[]
|
||||
* Array of child nodes.
|
||||
*/
|
||||
protected function getChildPages(string $parent_type, string $parent_id): array {
|
||||
protected function getChildPages(string $parent_type, string $parent_id): array
|
||||
{
|
||||
$query = $this->entityTypeManager->getStorage('node')->getQuery()
|
||||
->accessCheck(TRUE)
|
||||
->condition('type', 'content_page')
|
||||
->condition('status', 1)
|
||||
->condition('field_parent_page.target_type', $parent_type)
|
||||
->condition('field_parent_page.target_id', $parent_id)
|
||||
->sort('title', 'ASC');
|
||||
|
||||
if ($parent_type === 'taxonomy_term') {
|
||||
// Root pages of a section have no parent page, but belong to the section.
|
||||
$query->notExists('field_parent_page');
|
||||
$query->condition('field_site_section.target_id', $parent_id);
|
||||
} else {
|
||||
// Child pages belong to a specific parent entity (node).
|
||||
$query->condition('field_parent_page.target_type', $parent_type);
|
||||
$query->condition('field_parent_page.target_id', $parent_id);
|
||||
}
|
||||
|
||||
$nids = $query->execute();
|
||||
|
||||
if (empty($nids)) {
|
||||
@@ -386,7 +420,8 @@ class StructuralPagesMenuBlock extends BlockBase implements ContainerFactoryPlug
|
||||
* @return array
|
||||
* Array of node IDs in the active trail.
|
||||
*/
|
||||
protected function getActiveTrail(NodeInterface $node): array {
|
||||
protected function getActiveTrail(NodeInterface $node): array
|
||||
{
|
||||
$trail = [$node->id()];
|
||||
$visited = [$node->id() => TRUE];
|
||||
$current = $node;
|
||||
@@ -427,14 +462,16 @@ class StructuralPagesMenuBlock extends BlockBase implements ContainerFactoryPlug
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags(): array {
|
||||
public function getCacheTags(): array
|
||||
{
|
||||
return Cache::mergeTags(parent::getCacheTags(), ['node_list:content_page']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts(): array {
|
||||
public function getCacheContexts(): array
|
||||
{
|
||||
return Cache::mergeContexts(parent::getCacheContexts(), ['route', 'url.path']);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user