From d33378c2f0859d8e5827e0b93e8aee7e695533d5 Mon Sep 17 00:00:00 2001 From: "Quintino A. G. Souza" Date: Sun, 15 Mar 2026 09:19:55 -0300 Subject: [PATCH] =?UTF-8?q?Adiciona=20termos=20de=20n=C3=ADvel=203=20ao=20?= =?UTF-8?q?vocabul=C3=A1rio=20MSC=202020?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expande o CSV de ~597 para ~6100 termos incorporando o terceiro nível hierárquico (códigos de 5 caracteres, ex.: 03B05). Inclui as traduções pt-br dos termos de nível 3 já no CSV. Atualiza MscTermListFormatter e MscTermSelectWidget para suportar a hierarquia de três níveis, adiciona biblioteca CSS dedicada ao formatter e adiciona o schema de configuração do formatter. Co-Authored-By: Claude Sonnet 4.6 --- .../schema/site_tools_msc_2020.schema.yml | 7 + .../site_tools_msc_2020/css/msc-term-list.css | 12 + .../site_tools_msc_2020.libraries.yml | 6 + .../FieldFormatter/MscTermListFormatter.php | 160 +- .../Field/FieldWidget/MscTermSelectWidget.php | 331 +- .../migrations/data/msc_2020_terms.csv | 6699 +++++++++++++++-- .../migrations/msc_2020_terms_pt_br.yml | 5 +- 7 files changed, 6493 insertions(+), 727 deletions(-) create mode 100644 modules/site_tools_msc_2020/config/schema/site_tools_msc_2020.schema.yml create mode 100644 modules/site_tools_msc_2020/css/msc-term-list.css diff --git a/modules/site_tools_msc_2020/config/schema/site_tools_msc_2020.schema.yml b/modules/site_tools_msc_2020/config/schema/site_tools_msc_2020.schema.yml new file mode 100644 index 0000000..daa1c16 --- /dev/null +++ b/modules/site_tools_msc_2020/config/schema/site_tools_msc_2020.schema.yml @@ -0,0 +1,7 @@ +field.widget.settings.msc_term_select: + type: mapping + label: 'Configurações do widget MSC 2020 — Seleção em cascata' + mapping: + max_depth: + type: integer + label: 'Nível máximo' diff --git a/modules/site_tools_msc_2020/css/msc-term-list.css b/modules/site_tools_msc_2020/css/msc-term-list.css new file mode 100644 index 0000000..fcee091 --- /dev/null +++ b/modules/site_tools_msc_2020/css/msc-term-list.css @@ -0,0 +1,12 @@ +.msc-terms-list { + list-style: none; + padding: 0; + margin: 0; +} + +.msc-terms-list__l1-children, +.msc-terms-list__l2-children { + list-style: none; + padding-left: 1.5em; + margin: 0; +} diff --git a/modules/site_tools_msc_2020/site_tools_msc_2020.libraries.yml b/modules/site_tools_msc_2020/site_tools_msc_2020.libraries.yml index 5bc43ad..c39b896 100644 --- a/modules/site_tools_msc_2020/site_tools_msc_2020.libraries.yml +++ b/modules/site_tools_msc_2020/site_tools_msc_2020.libraries.yml @@ -1,2 +1,8 @@ msc_term_select_widget: version: VERSION + +msc_term_list: + version: VERSION + css: + component: + css/msc-term-list.css: {} diff --git a/modules/site_tools_msc_2020/src/Plugin/Field/FieldFormatter/MscTermListFormatter.php b/modules/site_tools_msc_2020/src/Plugin/Field/FieldFormatter/MscTermListFormatter.php index 3323d01..208e073 100644 --- a/modules/site_tools_msc_2020/src/Plugin/Field/FieldFormatter/MscTermListFormatter.php +++ b/modules/site_tools_msc_2020/src/Plugin/Field/FieldFormatter/MscTermListFormatter.php @@ -14,9 +14,9 @@ use Symfony\Component\DependencyInjection\ContainerInterface; /** * Formatter que exibe termos MSC 2020 como lista hierárquica aninhada. * - * Os termos selecionados são agrupados pela categoria pai. O pai aparece - * como item de primeiro nível e os filhos como sub-lista dentro dele. - * Opcionalmente, os rótulos são linkados para a página do termo. + * Os termos selecionados são agrupados pela sua hierarquia de ancestrais. + * Suporta até 3 níveis (área → subcampo → tópico). Ancestrais ausentes na + * seleção são carregados automaticamente para garantir o agrupamento correto. * * @FieldFormatter( * id = "msc_term_list", @@ -70,8 +70,8 @@ class MscTermListFormatter extends FormatterBase { public function settingsForm(array $form, FormStateInterface $form_state): array { $elements = parent::settingsForm($form, $form_state); $elements['link_to_entity'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Link para a entidade referenciada'), + '#type' => 'checkbox', + '#title' => $this->t('Link para a entidade referenciada'), '#default_value' => $this->getSetting('link_to_entity'), ]; return $elements; @@ -97,97 +97,159 @@ class MscTermListFormatter extends FormatterBase { return []; } - $link = (bool) $this->getSetting('link_to_entity'); + $link = (bool) $this->getSetting('link_to_entity'); $storage = $this->entityTypeManager->getStorage('taxonomy_term'); $terms = $storage->loadMultiple($tids); - // Separa pais e filhos; coleta códigos de pais ausentes. - $parents_by_code = []; - $children_by_code = []; - $missing_codes = []; + // Classifica os termos selecionados pelos seus níveis na hierarquia. + // $l1_terms[l1_code] = term (ou NULL se não selecionado) + // $l2_terms[l1_code][l2_code] = term + // $l3_terms[l2_code][l3_code] = term + $l1_terms = []; + $l2_terms = []; + $l3_terms = []; + + // Códigos de ancestrais que precisam ser carregados para agrupamento. + $missing_l2_codes = []; + $missing_l1_codes = []; foreach ($terms as $term) { $code = $term->get('field_msc_code')->value; - if (strlen($code) === 2) { - $parents_by_code[$code] = $term; + $len = strlen($code); + + if ($len === 2) { + $l1_terms[$code] = $term; } - else { - $parent_code = substr($code, 0, 2); - $children_by_code[$parent_code][] = $term; - if (!isset($parents_by_code[$parent_code])) { - $missing_codes[$parent_code] = $parent_code; + elseif ($len === 3) { + $l1_code = substr($code, 0, 2); + $l2_terms[$l1_code][$code] = $term; + if (!isset($l1_terms[$l1_code])) { + $missing_l1_codes[$l1_code] = $l1_code; + } + } + elseif ($len === 5) { + $l2_code = substr($code, 0, 3); + $l1_code = substr($code, 0, 2); + $l3_terms[$l2_code][$code] = $term; + if (!array_key_exists($l2_code, $l2_terms[$l1_code] ?? [])) { + $missing_l2_codes[$l2_code] = $l2_code; + } + if (!isset($l1_terms[$l1_code])) { + $missing_l1_codes[$l1_code] = $l1_code; } } } - // Carrega pais que não foram selecionados mas são necessários para agrupar. - if (!empty($missing_codes)) { - $parent_tids = $storage->getQuery() + // Carrega L2 ausentes (necessários para agrupar L3). + if (!empty($missing_l2_codes)) { + $found_tids = $storage->getQuery() ->condition('vid', 'msc_2020') - ->condition('field_msc_code', array_values($missing_codes), 'IN') + ->condition('field_msc_code', array_values($missing_l2_codes), 'IN') ->accessCheck(FALSE) ->execute(); - foreach ($storage->loadMultiple($parent_tids) as $term) { - $code = $term->get('field_msc_code')->value; - $parents_by_code[$code] = $term; + foreach ($storage->loadMultiple($found_tids) as $term) { + $code = $term->get('field_msc_code')->value; + $l1_code = substr($code, 0, 2); + $l2_terms[$l1_code][$code] = $term; + if (!isset($l1_terms[$l1_code])) { + $missing_l1_codes[$l1_code] = $l1_code; + } } } - // Ordena grupos por código. - $all_codes = array_unique( - array_merge(array_keys($parents_by_code), array_keys($children_by_code)) - ); - sort($all_codes); + // Carrega L1 ausentes (necessários para agrupar L2 ou L3). + if (!empty($missing_l1_codes)) { + $found_tids = $storage->getQuery() + ->condition('vid', 'msc_2020') + ->condition('field_msc_code', array_values($missing_l1_codes), 'IN') + ->accessCheck(FALSE) + ->execute(); + foreach ($storage->loadMultiple($found_tids) as $term) { + $code = $term->get('field_msc_code')->value; + $l1_terms[$code] = $term; + } + } - // Monta o render array como lista aninhada. + // Coleta todos os códigos L1 que aparecem (diretamente ou como ancestral). + $all_l1_codes = array_unique(array_merge( + array_keys($l1_terms), + array_keys($l2_terms), + array_map(fn($c) => substr($c, 0, 2), array_keys($l3_terms)), + )); + sort($all_l1_codes); + + // Monta o render array como lista aninhada (até 3 níveis). $build = [ '#prefix' => '', ]; - foreach ($all_codes as $i => $code) { - $parent = $parents_by_code[$code] ?? NULL; - $children = $children_by_code[$code] ?? []; + foreach ($all_l1_codes as $i => $l1_code) { + $l1_term = $l1_terms[$l1_code] ?? NULL; + $l2_group = $l2_terms[$l1_code] ?? []; - usort($children, fn($a, $b) => strcmp( - $a->get('field_msc_code')->value, - $b->get('field_msc_code')->value, - )); + // L2 codes relevantes para este L1: os selecionados + os que agrupam L3. + $l3_l2_codes = array_filter( + array_keys($l3_terms), + fn($l2_code) => substr($l2_code, 0, 2) === $l1_code, + ); + $all_l2_codes = array_unique(array_merge(array_keys($l2_group), $l3_l2_codes)); + sort($all_l2_codes); $build[$i] = [ - '#prefix' => '
  • ', + '#prefix' => '
  • ', '#suffix' => '
  • ', - 'label' => $this->termLabel($parent, $code, $link), + 'label' => $this->termLabel($l1_term, $l1_code, $link), ]; - if (!empty($children)) { + if (!empty($all_l2_codes)) { $build[$i]['children'] = [ - '#prefix' => '