Add field_redirect_page to site_sections terms and fix root page hierarchy

- Add field_redirect_page to site_sections taxonomy terms, with a custom
  EntityReferenceSelection plugin that filters content_page nodes by section
- Redirect taxonomy term canonical URLs to the configured node (301)
- Fix root page detection in MenuBlock and views to also match nodes whose
  field_parent_page points to a taxonomy_term (not only empty parent)
- Move root taxonomy-term option to the top of the parent-page dropdown
- Add breadcrumb workaround for Gavias notech theme separator rendering
- Add imecc_menu_helper submodule
- Translate config labels and default terms from English to Portuguese

Co-Authored-By: Henrique Bauer <henrique@webcontent.com.br>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 07:52:49 -03:00
parent 36c3a2e9c0
commit 276fd028f2
26 changed files with 661 additions and 77 deletions

View File

@@ -0,0 +1,59 @@
langcode: pt-br
status: true
dependencies:
config:
- field.field.taxonomy_term.site_sections.field_redirect_page
- field.field.taxonomy_term.site_sections.layout_builder__layout
- taxonomy.vocabulary.site_sections
module:
- path
- text
id: taxonomy_term.site_sections.default
targetEntityType: taxonomy_term
bundle: site_sections
mode: default
content:
description:
type: text_textfield
weight: 0
region: content
settings:
size: 60
placeholder: ''
third_party_settings: { }
field_redirect_page:
type: options_select
weight: 10
region: content
settings: { }
third_party_settings: { }
langcode:
type: language_select
weight: 2
region: content
settings:
include_locked: true
third_party_settings: { }
name:
type: string_textfield
weight: -5
region: content
settings:
size: 60
placeholder: ''
third_party_settings: { }
path:
type: path
weight: 30
region: content
settings: { }
third_party_settings: { }
status:
type: boolean_checkbox
weight: 100
region: content
settings:
display_label: true
third_party_settings: { }
hidden:
layout_builder__layout: true

View File

@@ -0,0 +1,45 @@
uuid: f6aa2bd0-3f75-4489-b3f9-43429491b548
langcode: pt-br
status: true
dependencies:
config:
- field.field.taxonomy_term.site_sections.layout_builder__layout
- taxonomy.vocabulary.site_sections
module:
- layout_builder
- layout_discovery
third_party_settings:
layout_builder:
enabled: true
allow_custom: true
sections:
-
layout_id: layout_onecol
layout_settings:
label: ''
components:
0663ed2f-1401-4e75-8a69-971333b49bc2:
uuid: 0663ed2f-1401-4e75-8a69-971333b49bc2
region: content
configuration:
id: 'field_block:taxonomy_term:site_sections:description'
label_display: '0'
context_mapping:
entity: layout_builder.entity
formatter:
type: text_default
label: hidden
settings: { }
third_party_settings: { }
weight: 0
additional: { }
third_party_settings: { }
id: taxonomy_term.site_sections.default
targetEntityType: taxonomy_term
bundle: site_sections
mode: default
content: { }
hidden:
description: true
langcode: true
layout_builder__layout: true

View File

@@ -13,7 +13,7 @@ field_name: field_parent_page
entity_type: node
bundle: content_page
label: 'Parent Page'
description: 'Select the parent entity. The site section will be inherited automatically based on the parent type.'
description: 'Selecione a entidade pai. A seção do site será herdada automaticamente com base no tipo de pai.'
required: false
translatable: false
default_value: { }

View File

@@ -11,7 +11,7 @@ id: node.content_page.field_site_section
field_name: field_site_section
entity_type: node
bundle: content_page
label: 'Site Section'
label: 'Seção do Site'
description: 'For root pages, select the section. For child pages, this field is filled automatically.'
required: true
translatable: false

View File

@@ -11,8 +11,8 @@ id: node.section_page.field_site_section
field_name: field_site_section
entity_type: node
bundle: section_page
label: 'Site Section'
description: 'Select the site section this page belongs to.'
label: 'Seção do Site'
description: 'Selecione a seção do site à qual esta página pertence.'
required: true
translatable: false
default_value: { }

View File

@@ -0,0 +1,23 @@
langcode: pt-br
status: true
dependencies:
config:
- field.storage.taxonomy_term.field_redirect_page
- node.type.content_page
- taxonomy.vocabulary.site_sections
id: taxonomy_term.site_sections.field_redirect_page
field_name: field_redirect_page
entity_type: taxonomy_term
bundle: site_sections
label: 'Página de Redirecionamento'
description: ''
required: false
translatable: true
default_value: { }
default_value_callback: ''
settings:
handler: 'default:node_site_section_redirect'
handler_settings:
target_bundles:
content_page: content_page
field_type: entity_reference

View File

@@ -0,0 +1,21 @@
uuid: 726b6570-a8df-4518-8542-28446352fe46
langcode: pt-br
status: true
dependencies:
config:
- field.storage.taxonomy_term.layout_builder__layout
- taxonomy.vocabulary.site_sections
module:
- layout_builder
id: taxonomy_term.site_sections.layout_builder__layout
field_name: layout_builder__layout
entity_type: taxonomy_term
bundle: site_sections
label: Layout
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings: { }
field_type: layout_section

View File

@@ -0,0 +1,19 @@
langcode: pt-br
status: true
dependencies:
module:
- node
- taxonomy
id: taxonomy_term.field_redirect_page
field_name: field_redirect_page
entity_type: taxonomy_term
type: entity_reference
settings:
target_type: node
module: core
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false

View File

@@ -0,0 +1,19 @@
uuid: 99f94609-3b7c-490e-80a0-96036b03e9d3
langcode: pt-br
status: true
dependencies:
module:
- layout_builder
- taxonomy
id: taxonomy_term.layout_builder__layout
field_name: layout_builder__layout
entity_type: taxonomy_term
type: layout_section
settings: { }
module: layout_builder
locked: true
cardinality: 1
translatable: false
indexes: { }
persist_with_no_fields: false
custom_storage: false

View File

@@ -7,7 +7,7 @@ third_party_settings:
name: 'Content Page'
type: content_page
description: 'Pages with hierarchical parent-child structure for content organization.'
help: 'For root pages, fill in the Site Section manually. For child pages, select the parent page and the section will be inherited automatically.'
help: 'Para páginas raiz, preencha a Seção do Site manualmente. Para páginas filhas, selecione a página pai e a seção será herdada automaticamente.'
new_revision: true
preview_mode: 1
display_submitted: false

View File

@@ -6,7 +6,7 @@ third_party_settings:
enabled: true
name: 'Section Page'
type: section_page
description: 'Pages organized by site section, with URLs and breadcrumbs based on taxonomy.'
description: 'Páginas organizadas por seção do site, com URLs e breadcrumbs baseados na taxonomia.'
help: ''
new_revision: true
preview_mode: 1

View File

@@ -5,7 +5,7 @@ dependencies:
- structural_pages
- taxonomy
id: site_sections_term
label: 'Site Sections Term'
label: 'Termo de Seções do Site'
type: 'canonical_entities:taxonomy_term'
pattern: '[term:hierarchy-path]'
selection_logic: and

View File

@@ -1,7 +1,7 @@
langcode: en
status: true
dependencies: { }
name: 'Site Sections'
name: 'Seções do Site'
vid: site_sections
description: 'Main hierarchical structure for site organization.'
description: 'Estrutura hierárquica principal para organização do site.'
weight: 0

View File

@@ -0,0 +1,6 @@
name: 'IMECC Menu Helper'
type: module
description: 'Automatiza a hierarquia de menus baseada na Taxonomia de Setores.'
core_version_requirement: ^10
package: Custom
configure: imecc_menu_helper.settings

View File

@@ -0,0 +1,5 @@
imecc_menu_helper.settings_link:
title: 'Hierarquia de Menus IMECC'
parent: system.admin_config_content
description: 'Mapear Setores para Itens de Menu.'
route_name: imecc_menu_helper.settings

View File

@@ -0,0 +1,202 @@
<?php
use Drupal\node\NodeInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\Core\Routing\TrustedRedirectResponse;
use Drupal\Core\Url;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
/**
* Implements hook_node_presave().
* (Mantido intacto)
*/
function imecc_menu_helper_node_presave(NodeInterface $node) {
if ($node->getType() != 'pagina') return;
$field_name = 'field_taxinomia_setor';
if (!$node->hasField($field_name) || $node->get($field_name)->isEmpty()) return;
$setor_tid = $node->get($field_name)->target_id;
$config = \Drupal::config('imecc_menu_helper.settings');
$parent_mapping_string = $config->get('mapping_' . $setor_tid);
if (empty($parent_mapping_string)) return;
$current_parent = $node->menu['menu_parent'] ?? 'main:';
if ($current_parent != 'main:') {
if ($current_parent != $parent_mapping_string) return;
}
if (!$node->isNew() && strpos($parent_mapping_string, $node->uuid()) !== false) return;
$node->menu['enabled'] = 1;
$node->menu['title'] = $node->getTitle();
$node->menu['menu_parent'] = $parent_mapping_string;
}
/**
* Implements hook_form_alter().
*/
function imecc_menu_helper_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id) {
if (!in_array($form_id, ['node_pagina_form', 'node_pagina_edit_form'])) {
return;
}
// --- LÓGICA DO MENU (AJAX) ---
// REMOVIDO: O wrapper 'imecc-menu-wrapper' que causava erro.
// Agora atacamos direto o ID nativo 'edit-menu'.
if (isset($form['field_taxinomia_setor'])) {
$form['field_taxinomia_setor']['widget']['#ajax'] = [
'callback' => '_imecc_menu_helper_update_menu_options',
'wrapper' => 'edit-menu', // <--- CORREÇÃO: Volta para o ID nativo
'event' => 'change',
'progress' => [
'type' => 'throbber',
'message' => t('Carregando menu...'),
],
];
}
// --- LÓGICA DO REDIRECIONAMENTO (Barra Lateral) ---
$node = $form_state->getFormObject()->getEntity();
$should_open = FALSE;
if ($node->hasField('field_redirecionar_para') && !$node->get('field_redirecionar_para')->isEmpty()) {
$should_open = TRUE;
}
$form['redirection_settings'] = [
'#type' => 'details',
'#title' => t('Redirecionamento'),
'#group' => 'advanced',
'#open' => $should_open,
'#weight' => -10,
'#attributes' => [
'class' => ['node-form-redirection-settings'],
],
];
if (isset($form['field_redirecionar_para'])) {
$form['field_redirecionar_para']['#group'] = 'redirection_settings';
}
}
/**
* Callback AJAX (CORRIGIDO)
*/
function _imecc_menu_helper_update_menu_options(array &$form, FormStateInterface $form_state) {
$values = $form_state->getValue('field_taxinomia_setor');
$tid = 0;
if (is_array($values) && isset($values[0]['target_id'])) {
$tid = $values[0]['target_id'];
} elseif (is_array($values) && isset($values['target_id'])) {
$tid = $values['target_id'];
} elseif (is_numeric($values)) {
$tid = $values;
}
// CORREÇÃO: Garante que o ID do formulário se mantenha estável para não quebrar o AJAX
$form['menu']['#attributes']['id'] = 'edit-menu';
if (empty($tid)) return $form['menu'];
$config = \Drupal::config('imecc_menu_helper.settings');
$parent_mapping_string = $config->get('mapping_' . $tid);
if (empty($parent_mapping_string)) return $form['menu'];
list($menu_name, $root_plugin_id) = explode(':', $parent_mapping_string, 2);
/** @var \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree_service */
$menu_tree_service = \Drupal::service('menu.link_tree');
$parameters = new MenuTreeParameters();
$parameters->setRoot($root_plugin_id);
$parameters->excludeRoot();
$parameters->setMaxDepth(NULL);
$tree = $menu_tree_service->load($menu_name, $parameters);
$allowed_ids = [];
$allowed_ids[$parent_mapping_string] = true;
_imecc_menu_helper_extract_ids($tree, $menu_name, $allowed_ids);
if (isset($form['menu']['link']['menu_parent']['#options'])) {
$options = $form['menu']['link']['menu_parent']['#options'];
$filtered_options = [];
foreach ($options as $key => $label) {
if (isset($allowed_ids[$key])) {
$filtered_options[$key] = $label;
}
}
if (empty($filtered_options) && isset($options[$parent_mapping_string])) {
$filtered_options[$parent_mapping_string] = $options[$parent_mapping_string];
}
$form['menu']['link']['menu_parent']['#options'] = $filtered_options;
$current_val = $form_state->getValue(['menu', 'link', 'menu_parent']);
if (empty($current_val) || !isset($filtered_options[$current_val])) {
$form['menu']['link']['menu_parent']['#value'] = $parent_mapping_string;
}
}
$form['menu']['#open'] = TRUE;
if (isset($form['menu']['enabled'])) {
$form['menu']['enabled']['#value'] = 1;
}
// CORREÇÃO: Retorna o array direto, sem envelopar em arrays novos
return $form['menu'];
}
/**
* Função auxiliar recursiva (Mantida intacta)
*/
function _imecc_menu_helper_extract_ids(array $tree, $menu_name, array &$allowed_ids) {
foreach ($tree as $element) {
$plugin_id = $element->link->getPluginId();
$key = $menu_name . ':' . $plugin_id;
$allowed_ids[$key] = true;
if ($element->subtree) {
_imecc_menu_helper_extract_ids($element->subtree, $menu_name, $allowed_ids);
}
}
}
/**
* Implements hook_node_view().
* (Mantido intacto)
*/
function imecc_menu_helper_node_view(array &$build, NodeInterface $node, EntityViewDisplayInterface $display, $view_mode) {
if ($view_mode !== 'full') return;
$route_name = \Drupal::routeMatch()->getRouteName();
if ($route_name != 'entity.node.canonical') return;
$field_redirect = 'field_redirecionar_para';
if ($node->hasField($field_redirect) && !$node->get($field_redirect)->isEmpty()) {
$url_object = $node->get($field_redirect)->first()->getUrl();
$destination = $url_object->toString();
$current_url = Url::fromRoute('<current>')->toString();
if ($destination == $current_url) return;
$response = new TrustedRedirectResponse($destination, 302);
$response->addCacheableDependency($node);
$response->send();
exit;
}
}

View File

@@ -0,0 +1,3 @@
administer imecc menu helper:
title: 'Administrar configuração de menus do IMECC'
description: 'Permite configurar o relacionamento entre Setores e Páginas Pai.'

View File

@@ -0,0 +1,7 @@
imecc_menu_helper.settings:
path: '/admin/config/imecc/menu-helper'
defaults:
_form: '\Drupal\imecc_menu_helper\Form\MenuHelperSettingsForm'
_title: 'Configuração de Hierarquia IMECC'
requirements:
_permission: 'administer imecc menu helper'

View File

@@ -0,0 +1,79 @@
<?php
namespace Drupal\imecc_menu_helper\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Menu\MenuParentFormSelectorInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class MenuHelperSettingsForm extends ConfigFormBase {
protected $menuParentSelector;
public function __construct(MenuParentFormSelectorInterface $menu_parent_selector) {
$this->menuParentSelector = $menu_parent_selector;
}
public static function create(ContainerInterface $container) {
return new static(
$container->get('menu.parent_form_selector')
);
}
protected function getEditableConfigNames() {
return ['imecc_menu_helper.settings'];
}
public function getFormId() {
return 'imecc_menu_helper_settings_form';
}
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config('imecc_menu_helper.settings');
$form['description'] = [
'#markup' => '<p>Mapeie cada <strong>Setor</strong> (Taxonomia) a um <strong>Item de Menu Pai</strong>. Quando uma página for criada com este setor, ela será automaticamente movida para dentro deste pai.</p>',
];
// Carrega todos os termos do vocabulário 'setores'.
$terms = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadTree('setores');
// Opções de menu (apenas do menu principal).
$menu_options = $this->menuParentSelector->getParentSelectOptions('main:', NULL);
$form['mappings'] = [
'#type' => 'details',
'#title' => $this->t('Mapeamento Setor -> Menu Pai'),
'#open' => TRUE,
'#tree' => TRUE,
];
foreach ($terms as $term) {
$default_value = $config->get('mapping_' . $term->tid);
$form['mappings'][$term->tid] = [
'#type' => 'select',
'#title' => $term->name . ' (' . $term->tid . ')',
'#options' => $menu_options,
'#default_value' => $default_value,
'#empty_option' => '- Sem automação -',
'#description' => 'Selecione a página pai para o setor ' . $term->name,
];
}
return parent::buildForm($form, $form_state);
}
public function submitForm(array &$form, FormStateInterface $form_state) {
$config = $this->config('imecc_menu_helper.settings');
$mappings = $form_state->getValue('mappings');
foreach ($mappings as $tid => $menu_parent) {
$config->set('mapping_' . $tid, $menu_parent);
}
$config->save();
parent::submitForm($form, $form_state);
}
}

View File

@@ -16,7 +16,8 @@ use Drupal\structural_pages\ParentEntityHandler\ParentEntityHandlerManagerInterf
/**
* Provides a breadcrumb builder for section_page and content_page content types.
*/
class SectionBreadcrumbBuilder implements BreadcrumbBuilderInterface {
class SectionBreadcrumbBuilder implements BreadcrumbBuilderInterface
{
use StringTranslationTrait;
@@ -60,7 +61,8 @@ class SectionBreadcrumbBuilder implements BreadcrumbBuilderInterface {
/**
* {@inheritdoc}
*/
public function applies(RouteMatchInterface $route_match): bool {
public function applies(RouteMatchInterface $route_match): bool
{
$node = $route_match->getParameter('node');
if ($node instanceof NodeInterface) {
@@ -73,7 +75,8 @@ class SectionBreadcrumbBuilder implements BreadcrumbBuilderInterface {
/**
* {@inheritdoc}
*/
public function build(RouteMatchInterface $route_match): Breadcrumb {
public function build(RouteMatchInterface $route_match): Breadcrumb
{
$breadcrumb = new Breadcrumb();
$breadcrumb->addCacheContexts(['route']);
@@ -94,8 +97,7 @@ class SectionBreadcrumbBuilder implements BreadcrumbBuilderInterface {
if ($context) {
// Use the handler manager to build breadcrumbs for the context entity.
$this->handlerManager->buildBreadcrumbForEntity($breadcrumb, $context['entity']);
}
else {
} 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;
@@ -108,6 +110,12 @@ class SectionBreadcrumbBuilder implements BreadcrumbBuilderInterface {
$this->addParentBreadcrumbs($breadcrumb, $node);
}
// Gavias notech theme bug bypass: the theme requires an empty element
// before the title to correctly iterate and render '/' separators.
$breadcrumb->addLink(Link::createFromRoute('', '<none>'));
// Append the active page title, as the theme's title resolver fails for nodes.
$breadcrumb->addLink(Link::createFromRoute($node->getTitle(), '<none>'));
return $breadcrumb;
}
@@ -122,7 +130,8 @@ class SectionBreadcrumbBuilder implements BreadcrumbBuilderInterface {
* @return array|null
* An array with 'type' and 'entity' keys, or NULL if no context.
*/
protected function getParentContext(NodeInterface $node): ?array {
protected function getParentContext(NodeInterface $node): ?array
{
if ($node->bundle() !== 'content_page') {
return NULL;
}
@@ -130,9 +139,11 @@ class SectionBreadcrumbBuilder implements BreadcrumbBuilderInterface {
$visited = [];
$current = $node;
while ($current instanceof NodeInterface &&
$current->hasField('field_parent_page') &&
!$current->get('field_parent_page')->isEmpty()) {
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) {
@@ -180,8 +191,7 @@ class SectionBreadcrumbBuilder implements BreadcrumbBuilderInterface {
// If parent is a node, continue traversing.
if ($parent instanceof NodeInterface) {
$current = $parent;
}
else {
} else {
break;
}
}
@@ -197,7 +207,8 @@ class SectionBreadcrumbBuilder implements BreadcrumbBuilderInterface {
* @param int|string $term_id
* The taxonomy term ID.
*/
protected function addTaxonomyBreadcrumbs(Breadcrumb $breadcrumb, int|string $term_id): void {
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);
@@ -219,7 +230,8 @@ class SectionBreadcrumbBuilder implements BreadcrumbBuilderInterface {
* @param \Drupal\node\NodeInterface $node
* The current node.
*/
protected function addParentBreadcrumbs(Breadcrumb $breadcrumb, NodeInterface $node): void {
protected function addParentBreadcrumbs(Breadcrumb $breadcrumb, NodeInterface $node): void
{
$parents = $this->getNodeParents($node);
$parents = array_reverse($parents);
@@ -240,14 +252,17 @@ class SectionBreadcrumbBuilder implements BreadcrumbBuilderInterface {
* @return \Drupal\node\NodeInterface[]
* Array of parent nodes, from closest to farthest.
*/
protected function getNodeParents(NodeInterface $node): array {
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()) {
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) {

View File

@@ -73,6 +73,26 @@ class StructuralPagesRedirectSubscriber implements EventSubscriberInterface
}
}
}
} elseif ($route_name === 'entity.taxonomy_term.canonical') {
$term = $this->routeMatch->getParameter('taxonomy_term');
if ($term instanceof \Drupal\taxonomy\TermInterface && $term->bundle() === 'site_sections') {
if ($term->hasField('field_redirect_page') && !$term->get('field_redirect_page')->isEmpty()) {
try {
$target_node = $term->get('field_redirect_page')->entity;
if ($target_node && $target_node->access('view')) {
$url = $target_node->toUrl()->toString();
// Cacheable redirect response.
$response = new TrustedRedirectResponse($url, 301);
$response->addCacheableDependency($term);
$response->addCacheableDependency($target_node);
$event->setResponse($response);
}
} catch (\Exception $e) {
// If invalid, proceed normally.
}
}
}
}
}

View File

@@ -16,7 +16,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Configuration form for Structural Pages module.
*/
class StructuralPagesSettingsForm extends ConfigFormBase {
class StructuralPagesSettingsForm extends ConfigFormBase
{
/**
* The entity type manager.
@@ -69,7 +70,8 @@ class StructuralPagesSettingsForm extends ConfigFormBase {
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container): static {
public static function create(ContainerInterface $container): static
{
return new static(
$container->get('config.factory'),
$container->get('config.typed'),
@@ -82,21 +84,24 @@ class StructuralPagesSettingsForm extends ConfigFormBase {
/**
* {@inheritdoc}
*/
public function getFormId(): string {
public function getFormId(): string
{
return 'structural_pages_settings_form';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames(): array {
protected function getEditableConfigNames(): array
{
return ['structural_pages.settings'];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state): array {
public function buildForm(array $form, FormStateInterface $form_state): array
{
$config = $this->config('structural_pages.settings');
$allowed_targets = $config->get('allowed_parent_targets') ?? [];
@@ -116,8 +121,8 @@ class StructuralPagesSettingsForm extends ConfigFormBase {
$form['site_section_vocabulary'] = [
'#type' => 'select',
'#title' => $this->t('Site section vocabulary'),
'#description' => $this->t('Select the taxonomy vocabulary used for site sections. Content pages and section pages will reference terms from this vocabulary.'),
'#title' => $this->t('Vocabulário de Seções do Site'),
'#description' => $this->t('Selecione o vocabulário de taxonomia usado para as seções do site. As páginas de conteúdo e páginas de seção farão referência aos termos deste vocabulário.'),
'#options' => $vocabulary_options,
'#default_value' => $config->get('site_section_vocabulary') ?? 'site_sections',
'#required' => TRUE,
@@ -194,7 +199,8 @@ class StructuralPagesSettingsForm extends ConfigFormBase {
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state): void {
public function submitForm(array &$form, FormStateInterface $form_state): void
{
$vocabulary = $form_state->getValue('site_section_vocabulary');
$values = $form_state->getValue('allowed_parent_targets') ?? [];
$supportedEntityTypes = $this->handlerManager->getSupportedEntityTypes();
@@ -258,7 +264,8 @@ class StructuralPagesSettingsForm extends ConfigFormBase {
* @param array $targets
* The allowed targets.
*/
protected function updateFieldConfiguration(array $targets): void {
protected function updateFieldConfiguration(array $targets): void
{
$field_config = $this->entityTypeManager
->getStorage('field_config')
->load('node.content_page.field_parent_page');
@@ -300,8 +307,7 @@ class StructuralPagesSettingsForm extends ConfigFormBase {
// Handle wildcard bundles.
if ($bundle === '*') {
$entity_type_settings[$entity_type]['handler_settings']['target_bundles'] = NULL;
}
elseif ($entity_type_settings[$entity_type]['handler_settings']['target_bundles'] !== NULL) {
} elseif ($entity_type_settings[$entity_type]['handler_settings']['target_bundles'] !== NULL) {
$entity_type_settings[$entity_type]['handler_settings']['target_bundles'][$bundle] = $bundle;
}
}
@@ -323,7 +329,8 @@ class StructuralPagesSettingsForm extends ConfigFormBase {
* @param string $vocabulary
* The vocabulary machine name to set as target bundle.
*/
protected function updateSiteSectionFieldConfiguration(string $vocabulary): void {
protected function updateSiteSectionFieldConfiguration(string $vocabulary): void
{
$field_names = [
'node.section_page.field_site_section',
'node.content_page.field_site_section',

View File

@@ -388,8 +388,11 @@ class StructuralPagesMenuBlock extends BlockBase implements ContainerFactoryPlug
->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');
// Root pages could have an empty parent page OR explicitly point to the taxonomy term.
$or_group = $query->orConditionGroup()
->notExists('field_parent_page')
->condition('field_parent_page.target_type', 'taxonomy_term');
$query->condition($or_group);
$query->condition('field_site_section.target_id', $parent_id);
} else {
// Child pages belong to a specific parent entity (node).

View File

@@ -0,0 +1,42 @@
<?php
namespace Drupal\structural_pages\Plugin\EntityReferenceSelection;
use Drupal\node\Plugin\EntityReferenceSelection\NodeSelection;
/**
* Filter Content Pages by the currently edited Site Section term.
*
* @EntityReferenceSelection(
* id = "default:node_site_section_redirect",
* label = @Translation("Site Section Redirect Pages"),
* entity_types = {"node"},
* group = "default",
* weight = 10
* )
*/
class SiteSectionRedirectSelection extends NodeSelection
{
/**
* {@inheritdoc}
*/
protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS')
{
$query = parent::buildEntityQuery($match, $match_operator);
// Get the taxonomy term being edited.
$term = $this->configuration['entity'] ?? NULL;
if ($term && $term->getEntityTypeId() === 'taxonomy_term' && $term->bundle() === 'site_sections') {
if ($term->id()) {
$query->condition('field_site_section', $term->id());
} else {
// If it's a newly created term, there are no pages for it yet to redirect to.
$query->condition('nid', 0);
}
}
return $query;
}
}

View File

@@ -39,38 +39,38 @@ function _structural_pages_create_default_terms(): void {
// Structure: name => children.
$terms_structure = [
'News' => [],
'Events' => [],
'People' => [],
'Institutional' => [
'About',
'Communication',
'Information and Services',
'Team',
'Management',
'Inclusion and Belonging',
'Notícias' => [],
'Eventos' => [],
'Pessoas' => [],
'Institucional' => [
'Sobre',
'Comunicação',
'Informações e Serviços',
'Equipe',
'Gestão',
'Inclusão e Pertencimento',
],
'Undergraduate' => [
'Statistics',
'Mathematics',
'Applied Mathematics',
'Mathematics Teaching',
'Graduação' => [
'Estatística',
'Matemática',
'Matemática Aplicada',
'Licenciatura em Matemática',
],
'Graduate' => [
'Statistics Program',
'Mathematics Program',
'Applied Mathematics Program',
'Pós-Graduação' => [
'Programa de Estatística',
'Programa de Matemática',
'Programa de Matemática Aplicada',
],
'Research' => [],
'Extension' => [],
'Administration' => [],
'Departments' => [
'Statistics Department',
'Mathematics Department',
'Applied Mathematics Department',
'Pesquisa' => [],
'Extensão' => [],
'Administração' => [],
'Departamentos' => [
'Departamento de Estatística',
'Departamento de Matemática',
'Departamento de Matemática Aplicada',
],
'Library' => [],
'IT Services' => [],
'Biblioteca' => [],
'Informática' => [],
];
$created_terms = [];

View File

@@ -410,6 +410,17 @@ function _structural_pages_alter_parent_page_form(&$form, \Drupal\Core\Form\Form
$default_parent = $current_entity->get('field_parent_page')->target_type . ':' . $current_entity->get('field_parent_page')->target_id;
}
// Add the Root (Taxonomy term) as the default option at the top of the tree.
$term = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->load($site_section_id);
$term_label = $term ? $term->label() : t('Taxonomia');
$root_key = 'taxonomy_term:' . $site_section_id;
$options = [$root_key => '< ' . t('Raiz (@term)', ['@term' => $term_label]) . ' >'] + $options;
if (empty($default_parent)) {
$default_parent = $root_key;
}
// Hide the original field.
$form['field_parent_page']['#access'] = FALSE;
@@ -419,8 +430,8 @@ function _structural_pages_alter_parent_page_form(&$form, \Drupal\Core\Form\Form
'#title' => t('Parent Page'),
'#options' => $options,
'#default_value' => $default_parent,
'#empty_option' => t('- Raiz da Seção -'),
'#description' => t('Select the parent page within this site section.'),
'#empty_option' => t('- Selecione a Página Pai -'),
'#description' => t('Select the parent page within this site section. Select Root if this is a top-level page.'),
// Attach a select2/chosen class if available in standard themes
'#attributes' => [
'class' => ['select2-widget', 'chosen-enable'],
@@ -468,12 +479,6 @@ function structural_pages_parent_page_ajax_callback(array &$form, \Drupal\Core\F
*/
function _structural_pages_build_parent_tree_options($site_section_id, $current_node_id = NULL) {
$options = [];
// Add the Section's Taxonomy Term as a Root option
$term = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->load($site_section_id);
if ($term) {
$options['taxonomy_term:' . $site_section_id] = '< Raiz (' . $term->getName() . ') >';
}
$query = \Drupal::entityQuery('node')
->condition('type', 'content_page')
@@ -581,7 +586,11 @@ function structural_pages_views_post_execute(\Drupal\views\ViewExecutable $view)
$query->condition('field_parent_page.target_type', 'node');
} else {
// Root page. Siblings are other root pages in the same site section.
$query->notExists('field_parent_page');
$orGroup = $query->orConditionGroup()
->notExists('field_parent_page')
->condition('field_parent_page.target_type', 'taxonomy_term');
$query->condition($orGroup);
if (!$current_node->get('field_site_section')->isEmpty()) {
$query->condition('field_site_section.target_id', $current_node->get('field_site_section')->target_id);
}