page archives projets filters

This commit is contained in:
2026-10-26 16:31:07 +01:00
parent 329459600b
commit 101e7124c2
7 changed files with 357 additions and 11 deletions
@@ -80,3 +80,18 @@ function leshed_helpers_ensure_term_alias(EntityInterface $entity) {
'langcode' => $langcode,
])->save();
}
/**
* Déclare le plugin d'area "liste de termes en liens" pour les vues.
*/
function leshed_helpers_views_data() {
$data['views']['leshed_helpers_term_links'] = [
'title' => t('Liste de termes en liens (Le Shed)'),
'help' => t("Liste les termes d'un vocabulaire en liens vers un chemin de base suivi du tid."),
'area' => [
'id' => 'leshed_helpers_term_links',
],
];
return $data;
}
@@ -0,0 +1,212 @@
<?php
/**
* @file
* Plugin d'area Views listant les termes d'un vocabulaire en liens.
*/
declare(strict_types=1);
namespace Drupal\leshed_helpers\Plugin\views\area;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Url;
use Drupal\views\Attribute\ViewsArea;
use Drupal\views\Plugin\views\area\AreaPluginBase;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Affiche les termes d'un vocabulaire en liens vers un chemin filtré par tid.
*
* Chaque terme pointe vers <base_path>/<tid> ; l'alias géré par
* leshed_helpers (ou pathauto) est appliqué automatiquement à la génération
* du lien.
*/
#[ViewsArea('leshed_helpers_term_links')]
class TermLinks extends AreaPluginBase implements ContainerFactoryPluginInterface {
/**
* Le gestionnaire d'entités.
*/
protected EntityTypeManagerInterface $entityTypeManager;
/**
* La connexion à la base de données.
*/
protected Connection $connection;
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, Connection $connection) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityTypeManager = $entity_type_manager;
$this->connection = $connection;
}
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_type.manager'),
$container->get('database')
);
}
protected function defineOptions() {
$options = parent::defineOptions();
$options['vocabulary'] = ['default' => ''];
$options['base_path'] = ['default' => ''];
$options['list_title'] = ['default' => ''];
$options['show_all'] = ['default' => TRUE];
$options['only_used'] = ['default' => FALSE];
$options['used_bundle'] = ['default' => ''];
return $options;
}
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
$vocabularies = [];
foreach ($this->entityTypeManager->getStorage('taxonomy_vocabulary')->loadMultiple() as $vocabulary) {
$vocabularies[$vocabulary->id()] = $vocabulary->label();
}
$form['vocabulary'] = [
'#type' => 'select',
'#title' => $this->t('Vocabulaire'),
'#options' => $vocabularies,
'#default_value' => $this->options['vocabulary'],
'#required' => TRUE,
];
$form['base_path'] = [
'#type' => 'textfield',
'#title' => $this->t('Chemin de base'),
'#description' => $this->t('Chemin de la vue filtrée, sans slash initial. Exemple : projets.'),
'#default_value' => $this->options['base_path'],
'#required' => TRUE,
];
$form['list_title'] = [
'#type' => 'textfield',
'#title' => $this->t('Titre de la liste'),
'#default_value' => $this->options['list_title'],
];
$form['show_all'] = [
'#type' => 'checkbox',
'#title' => $this->t('Ajouter un lien « Tous » vers la page non filtrée'),
'#default_value' => $this->options['show_all'],
];
$form['only_used'] = [
'#type' => 'checkbox',
'#title' => $this->t("N'afficher que les termes utilisés par au moins un contenu publié"),
'#default_value' => $this->options['only_used'],
];
$node_types = [];
foreach ($this->entityTypeManager->getStorage('node_type')->loadMultiple() as $node_type) {
$node_types[$node_type->id()] = $node_type->label();
}
$form['used_bundle'] = [
'#type' => 'select',
'#title' => $this->t('Type de contenu'),
'#options' => $node_types,
'#empty_option' => $this->t('- Tous types de contenu -'),
'#empty_value' => '',
'#default_value' => $this->options['used_bundle'],
'#states' => [
'visible' => [
':input[name="options[only_used]"]' => ['checked' => TRUE],
],
],
];
}
/**
* {@inheritdoc}
*/
public function render($empty = FALSE) {
if (empty($this->options['vocabulary']) || empty($this->options['base_path'])) {
return [];
}
$storage = $this->entityTypeManager->getStorage('taxonomy_term');
$tree = $storage->loadTree($this->options['vocabulary']);
if (empty($tree)) {
return [];
}
// tids des termes du vocabulaire, dans l'ordre de l'arborescence.
$tids = array_map(static fn($term) => (int) $term->tid, $tree);
if (!empty($this->options['only_used'])) {
// taxonomy_index ne référence que les contenus publiés ; jointure sur
// node_field_data pour restreindre au type de contenu voulu. Une seule
// requête pour tout le vocabulaire.
$query = $this->connection->select('taxonomy_index', 'ti');
$query->distinct();
$query->fields('ti', ['tid']);
$query->condition('ti.tid', $tids, 'IN');
$query->join('node_field_data', 'nfd', 'nfd.nid = ti.nid AND nfd.status = 1');
if (!empty($this->options['used_bundle'])) {
$query->condition('nfd.type', $this->options['used_bundle']);
}
$used = array_map('intval', $query->execute()->fetchCol());
$tids = array_values(array_intersect($tids, $used));
}
$entities = $storage->loadMultiple($tids);
// tids présents dans les arguments de la vue, pour marquer le lien actif.
$current_tids = [];
foreach ($this->view->args as $arg) {
foreach (explode(',', (string) $arg) as $tid) {
$current_tids[] = trim($tid);
}
}
$base_path = '/' . trim($this->options['base_path'], '/');
$items = [];
if (!empty($this->options['show_all'])) {
$items[] = [
'#type' => 'link',
'#title' => $this->t('Tous'),
'#url' => Url::fromUserInput($base_path),
'#attributes' => $current_tids ? [] : ['class' => ['is-active']],
];
}
foreach ($tree as $position) {
$term = $entities[(int) $position->tid] ?? NULL;
if ($term === NULL || !$term->isPublished()) {
continue;
}
$items[] = [
'#type' => 'link',
'#title' => $term->label(),
'#url' => Url::fromUserInput($base_path . '/' . $term->id()),
'#attributes' => in_array((string) $term->id(), $current_tids, TRUE)
? ['class' => ['is-active']]
: [],
];
}
$cache_tags = ['taxonomy_term_list:' . $this->options['vocabulary']];
if (!empty($this->options['only_used'])) {
// Le filtrage par usage dépend des contenus publiés.
$cache_tags[] = 'node_list';
}
return [
'#theme' => 'item_list',
'#title' => $this->options['list_title'],
'#list_type' => 'ul',
'#items' => $items,
'#attributes' => ['class' => ['leshed-term-links']],
'#cache' => [
'tags' => $cache_tags,
],
];
}
}