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,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);
}
}