mirror of
https://gitlab.unicamp.br/infimecc_drupal11_modules/site_tools.git
synced 2026-05-03 19:50:42 -03:00
Adiciona formatter de lista hierárquica para o vocabulário MSC 2020
MscTermListFormatter agrupa os termos selecionados por categoria pai, exibindo o pai como item de primeiro nível e os filhos como sub-lista. Pais não selecionados diretamente são carregados automaticamente para servir de cabeçalho do grupo. Inclui opção "Link para a entidade referenciada" para linkar os rótulos às páginas dos termos. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\site_tools_msc_2020\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\FormatterBase;
|
||||
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
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.
|
||||
*
|
||||
* @FieldFormatter(
|
||||
* id = "msc_term_list",
|
||||
* label = @Translation("MSC 2020 — Lista hierárquica"),
|
||||
* field_types = {
|
||||
* "entity_reference"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class MscTermListFormatter extends FormatterBase {
|
||||
|
||||
public function __construct(
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
FieldDefinitionInterface $field_definition,
|
||||
array $settings,
|
||||
$label,
|
||||
$view_mode,
|
||||
array $third_party_settings,
|
||||
protected EntityTypeManagerInterface $entityTypeManager,
|
||||
) {
|
||||
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings);
|
||||
}
|
||||
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
|
||||
return new static(
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$configuration['field_definition'],
|
||||
$configuration['settings'],
|
||||
$configuration['label'],
|
||||
$configuration['view_mode'],
|
||||
$configuration['third_party_settings'],
|
||||
$container->get('entity_type.manager'),
|
||||
);
|
||||
}
|
||||
|
||||
public static function isApplicable(FieldDefinitionInterface $field_definition): bool {
|
||||
$storage = $field_definition->getFieldStorageDefinition();
|
||||
if ($storage->getSetting('target_type') !== 'taxonomy_term') {
|
||||
return FALSE;
|
||||
}
|
||||
$handler_settings = $field_definition->getSetting('handler_settings');
|
||||
return !empty($handler_settings['target_bundles']['msc_2020']);
|
||||
}
|
||||
|
||||
public static function defaultSettings(): array {
|
||||
return ['link_to_entity' => FALSE] + parent::defaultSettings();
|
||||
}
|
||||
|
||||
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'),
|
||||
'#default_value' => $this->getSetting('link_to_entity'),
|
||||
];
|
||||
return $elements;
|
||||
}
|
||||
|
||||
public function settingsSummary(): array {
|
||||
$summary = parent::settingsSummary();
|
||||
$summary[] = $this->getSetting('link_to_entity')
|
||||
? $this->t('Link para a entidade referenciada')
|
||||
: $this->t('Sem link para a entidade referenciada');
|
||||
return $summary;
|
||||
}
|
||||
|
||||
public function viewElements(FieldItemListInterface $items, $langcode): array {
|
||||
$tids = [];
|
||||
foreach ($items as $item) {
|
||||
if ($item instanceof EntityReferenceItem && !empty($item->target_id)) {
|
||||
$tids[] = (int) $item->target_id;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($tids)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$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 = [];
|
||||
|
||||
foreach ($terms as $term) {
|
||||
$code = $term->get('field_msc_code')->value;
|
||||
if (strlen($code) === 2) {
|
||||
$parents_by_code[$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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Carrega pais que não foram selecionados mas são necessários para agrupar.
|
||||
if (!empty($missing_codes)) {
|
||||
$parent_tids = $storage->getQuery()
|
||||
->condition('vid', 'msc_2020')
|
||||
->condition('field_msc_code', array_values($missing_codes), 'IN')
|
||||
->accessCheck(FALSE)
|
||||
->execute();
|
||||
foreach ($storage->loadMultiple($parent_tids) as $term) {
|
||||
$code = $term->get('field_msc_code')->value;
|
||||
$parents_by_code[$code] = $term;
|
||||
}
|
||||
}
|
||||
|
||||
// Ordena grupos por código.
|
||||
$all_codes = array_unique(
|
||||
array_merge(array_keys($parents_by_code), array_keys($children_by_code))
|
||||
);
|
||||
sort($all_codes);
|
||||
|
||||
// Monta o render array como lista aninhada.
|
||||
$build = [
|
||||
'#prefix' => '<ul class="msc-terms-list">',
|
||||
'#suffix' => '</ul>',
|
||||
];
|
||||
|
||||
foreach ($all_codes as $i => $code) {
|
||||
$parent = $parents_by_code[$code] ?? NULL;
|
||||
$children = $children_by_code[$code] ?? [];
|
||||
|
||||
usort($children, fn($a, $b) => strcmp(
|
||||
$a->get('field_msc_code')->value,
|
||||
$b->get('field_msc_code')->value,
|
||||
));
|
||||
|
||||
$build[$i] = [
|
||||
'#prefix' => '<li class="msc-terms-list__parent">',
|
||||
'#suffix' => '</li>',
|
||||
'label' => $this->termLabel($parent, $code, $link),
|
||||
];
|
||||
|
||||
if (!empty($children)) {
|
||||
$build[$i]['children'] = [
|
||||
'#prefix' => '<ul class="msc-terms-list__children">',
|
||||
'#suffix' => '</ul>',
|
||||
];
|
||||
foreach ($children as $j => $child) {
|
||||
$child_code = $child->get('field_msc_code')->value;
|
||||
$build[$i]['children'][$j] = [
|
||||
'#prefix' => '<li class="msc-terms-list__child">',
|
||||
'#suffix' => '</li>',
|
||||
] + $this->termLabel($child, $child_code, $link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [$build];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna um render array para o rótulo do termo, com ou sem link.
|
||||
*
|
||||
* Quando o termo pai não está na seleção (apenas agrupa filhos), é exibido
|
||||
* sem link mesmo que a opção esteja ativa, pois o pai não foi selecionado.
|
||||
*
|
||||
* @param \Drupal\taxonomy\TermInterface|null $term
|
||||
* Termo a exibir, ou NULL se o pai não foi carregado.
|
||||
* @param string $code
|
||||
* Código MSC usado como fallback quando $term é NULL.
|
||||
* @param bool $link
|
||||
* Se TRUE e $term não é NULL, envolve o rótulo num link.
|
||||
*/
|
||||
protected function termLabel($term, string $code, bool $link): array {
|
||||
if ($term === NULL) {
|
||||
return ['#markup' => Html::escape($code)];
|
||||
}
|
||||
|
||||
$text = Html::escape($code . ' — ' . $term->label());
|
||||
|
||||
if ($link && $term->access('view')) {
|
||||
return [
|
||||
'#type' => 'link',
|
||||
'#title' => $code . ' — ' . $term->label(),
|
||||
'#url' => $term->toUrl(),
|
||||
];
|
||||
}
|
||||
|
||||
return ['#markup' => $text];
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user