taxonomy_overview-1.0.1/src/Controller/TagsOverviewSingleController.php

src/Controller/TagsOverviewSingleController.php
<?php

namespace Drupal\taxonomy_overview\Controller;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Database\Database;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Render\Markup;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\node\NodeInterface;
use Drupal\paragraphs\Entity\Paragraph;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;

/**
 * Controller for tags overview: lists taxonomy terms and related info.
 *
 * Loads terms for a vocabulary (or a single term), finds related paragraphs
 * and nodes, translations and builds a table with operations.
 */
class TagsOverviewSingleController implements ContainerInjectionInterface {

  use StringTranslationTrait;

  /**
   * The current route match service.
   *
   * @var \Drupal\Core\Routing\RouteMatchInterface
   */
  protected $routeMatch;

  /**
   * The entity type manager service.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * The language manager service.
   *
   * @var \Drupal\Core\Language\LanguageManagerInterface
   */
  protected $languageManager;

  /**
   * The form builder service.
   *
   * @var \Drupal\Core\Form\FormBuilderInterface
   */
  protected $formBuilder;

  /**
   * The config factory service.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;

  /**
   * The vocabulary ID (bundle) being inspected.
   *
   * @var string|null
   */
  private $vocabularyId;

  /**
   * The taxonomy term context (if any).
   *
   * @var \Drupal\taxonomy\Entity\Term|null
   */
  private $taxonomyTerm;

  /**
   * Constructs a TagsOverviewSingleController.
   *
   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
   *   The current route match.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
   *   The language manager service.
   * @param \Drupal\Core\Form\FormBuilderInterface $form_builder
   *   The form builder service.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The config factory service.
   */
  public function __construct(RouteMatchInterface $route_match, EntityTypeManagerInterface $entity_type_manager, LanguageManagerInterface $language_manager, FormBuilderInterface $form_builder, ConfigFactoryInterface $config_factory) {
    $this->routeMatch = $route_match;
    $this->entityTypeManager = $entity_type_manager;
    $this->languageManager = $language_manager;
    $this->formBuilder = $form_builder;
    $this->configFactory = $config_factory;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    $instance = new static(
      $container->get('current_route_match'),
      $container->get('entity_type.manager'),
      $container->get('language_manager'),
      $container->get('form_builder'),
      $container->get('config.factory')
    );
    // string_translation service used by StringTranslationTrait.
    $instance->setStringTranslation($container->get('string_translation'));
    return $instance;
  }

  /**
   * Builds the overview page content for taxonomy terms.
   *
   * The request may contain filters:
   * - search: search string in term name
   * - sort_by: sort field (name, count, paragraph_count, translations,
   * node_count_content_type).
   * - sort_order: asc|desc
   * - random_bg: 1 to enable (unused in logic; removed unused var)
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The current request.
   *
   * @return array
   *   A render array for the page.
   */
  public function content(Request $request) {
    // Aggregation containers.
    $term_node_pages_content_types = [];
    $term_paragraph_by_content_type = [];
    $paragraph_node_used_bundle = [];
    $term_links_translatable = [];
    $term_duplicates_count = [];
    $paragraph_node_used = [];
    $term_translations = [];
    $term_node_counts = [];
    $paragraph_bundle = [];
    $paragraph_fields = [];
    $paragraph_count = [];
    $paragraph_ids = [];
    $node_fields = [];
    $term_names = [];

    // Route parameters.
    $this->vocabularyId = $this->routeMatch->getParameter('taxonomy_vocabulary');
    $this->taxonomyTerm = $this->routeMatch->getParameter('taxonomy_term');
    if ($this->taxonomyTerm instanceof Term) {
      $this->vocabularyId = $this->taxonomyTerm->bundle();
    }

    // Discover fields referencing this vocabulary.
    $entity_fields = $this->checkEntityReferenceVocabulary($this->vocabularyId);
    if (isset($entity_fields['node'])) {
      foreach ($entity_fields['node'] as $value) {
        $node_fields = array_merge(array_values($value), $node_fields);
      }
      $node_fields = array_unique($node_fields);

      foreach ($entity_fields['node'] as $value) {
        $paragraph_fields = array_merge(array_values($value), $paragraph_fields);
      }
      $paragraph_fields = array_unique($paragraph_fields);
    }

    $paragraph_types = $entity_fields['paragraph'] ?? [];

    $search_term = $request->query->get('search', '');
    $sort_by = $request->query->get('sort_by', 'name');
    $sort_order = $request->query->get('sort_order', 'desc');

    // Build taxonomy term query via storage.
    $term_storage = $this->entityTypeManager->getStorage('taxonomy_term');
    $query = $term_storage->getQuery()
      ->accessCheck(FALSE)
      ->condition('vid', $this->vocabularyId);

    if (!empty($search_term)) {
      $query->condition('name', '%' . $search_term . '%', 'LIKE');
    }

    if ($this->taxonomyTerm) {
      $query->condition('tid', $this->taxonomyTerm->id());
    }

    if ($sort_by === 'name') {
      $query->sort('name', $sort_order);
    }

    $term_ids = $query->execute();
    $terms = $term_storage->loadMultiple($term_ids);

    // Prepare paragraph storage and other storages used below.
    $paragraph_storage = $this->entityTypeManager->getStorage('paragraph');
    $node_storage = $this->entityTypeManager->getStorage('node');

    foreach ($terms as $term) {
      $term_name = $term->getName();
      $term_id = $term->id();
      $paragraph_bundle[$term_id] = [];
      $paragraph_node_used[$term_id] = [];
      $paragraph_node_used_bundle[$term_id] = [];
      $term_paragraph_by_content_type[$term_id] = [];

      $term_duplicates_count[$term_name] = $term_duplicates_count[$term_name] ?? 0;

      $term_links_translatable[$term_id] = $this->getTranslationsLinks($term);
      $translations = $this->getTranslations($term);
      $term_translations[$term_id] = implode(', ', $translations);

      if ($paragraph_types) {
        // For each paragraph type, count paragraphs that reference this term.
        foreach ($paragraph_types as $field_names) {
          foreach ($field_names as $field_name) {
            $paragraph_count[$term_id] = $paragraph_count[$term_id] ?? 0;
            $paragraph_ids[$term_id] = $paragraph_ids[$term_id] ?? [];

            $n_paragraphs = $paragraph_storage->getQuery()
              ->accessCheck(FALSE)
              ->condition('status', 1)
              ->condition($field_name, $term_id)
              ->execute();

            $paragraph_count[$term_id] += count($n_paragraphs);
            $paragraph_ids[$term_id] = array_merge($paragraph_ids[$term_id], $n_paragraphs);
          }
        }

        if (!empty($paragraph_ids[$term_id])) {
          foreach ($paragraph_ids[$term_id] as $pid) {
            $p = $paragraph_storage->load($pid);
            if ($p) {
              $data_used = $this->getParentEntityNode($p);
              $paragraph_bundle[$term_id][] = $p->bundle();
              if ($data_used !== NULL) {
                $paragraph_node_used[$term_id][] = $data_used['id'] ?? '';
                $paragraph_node_used_bundle[$term_id][] = $data_used['bundle'] ?? '';
              }
            }
          }
        }
      }

      // Count nodes that reference the term through
      // any node field we discovered.
      $node_count_arr = [];
      if (!empty($node_fields)) {
        $node_query = $node_storage->getQuery()->accessCheck(FALSE);
        $or_group = $node_query->orConditionGroup();
        foreach ($node_fields as $f) {
          $or_group->condition($f, $term_id, 'in');
        }
        $node_count_arr = $node_query->condition($or_group)->execute();
      }

      $term_node_counts[$term_id] = count(array_unique(array_filter(array_merge($paragraph_node_used[$term_id] ?? [], $node_count_arr))));
      $term_node_pages_content_types[$term_id] = implode(
        ', ',
        array_unique(
          array_merge($paragraph_node_used_bundle[$term_id] ?? [], $this->getContentTypesByTaxonomyTerm($term_id))
        )
      );

      if ($term_node_pages_content_types[$term_id] == '') {
        unset($term_node_pages_content_types[$term_id]);
      }
      $term_duplicates_count[$term_name]++;
      $term_names[$term_name][] = $term->id();
    }

    // Sorting on arrays of Term objects by various criteria.
    if ($sort_by === 'count') {
      uasort($terms, function ($a, $b) use ($term_node_counts, $sort_order) {
        $a_count = $term_node_counts[$a->id()] ?? 0;
        $b_count = $term_node_counts[$b->id()] ?? 0;
        return ($sort_order === 'asc') ? ($a_count <=> $b_count) : ($b_count <=> $a_count);
      });
    }
    elseif ($sort_by === 'node_count_content_type') {
      uasort($terms, function ($a, $b) use ($term_node_pages_content_types, $sort_order) {
        $a_count = $term_node_pages_content_types[$a->id()] ?? '';
        $b_count = $term_node_pages_content_types[$b->id()] ?? '';
        return ($sort_order === 'asc') ? ($a_count <=> $b_count) : ($b_count <=> $a_count);
      });
    }
    elseif ($sort_by === 'paragraph_count') {
      uasort($terms, function ($a, $b) use ($paragraph_count, $sort_order) {
        $a_count = $paragraph_count[$a->id()] ?? 0;
        $b_count = $paragraph_count[$b->id()] ?? 0;
        return ($sort_order === 'asc') ? ($a_count <=> $b_count) : ($b_count <=> $a_count);
      });
    }
    elseif ($sort_by === 'translations') {
      uasort($terms, function ($a, $b) use ($term_translations, $sort_order) {
        $a_translations = str_replace('Default: ', '', $term_translations[$a->id()] ?? '');
        $b_translations = str_replace('Default: ', '', $term_translations[$b->id()] ?? '');
        return ($sort_order === 'asc') ? strcasecmp($a_translations, $b_translations) : strcasecmp($b_translations, $a_translations);
      });
    }

    $build['fieldset'] = [
      '#type' => "fieldset",
      '#title' => 'Term Overview',
    ];

    // Define table rows.
    foreach ($terms as $term) {
      $term_name = $term->getName();
      $term_id = $term->id();

      $build['fieldset']['term_' . $term->id()] = [
        '#type' => 'container',
        '#attributes' => ['class' => ['tag-card']],
        'tid' => [
          '#markup' => $this->t('<div><span class="label">Term ID:</span> @id</div>', [
            '@id' => $term->id(),
          ]),
        ],
        'name' => [
          '#markup' => $this->t('<div><span class="label">Name:</span> @name</div>', [
            '@name' => Markup::create(implode(', ', $term_links_translatable[$term_id] ?? [])),
          ]),
        ],
        'nodes' => [
          '#markup' => $this->t('<div><span class="label">Nº Nodes:</span> @count</div>', [
            '@count' => $term_node_counts[$term_id] ?? 0,
          ]),
        ],
        'content_type' => [
          '#markup' => $this->t('<div><span class="label">Content Type:</span> @type</div>', [
            '@type' => $term_node_pages_content_types[$term_id] ?? '--',
          ]),
        ],
        'paragraphs' => [
          '#markup' => $this->t('<div><span class="label">Nº Paragraph:</span> @count</div>', [
            '@count' => $paragraph_count[$term_id] ?? 0,
          ]),
        ],
        'bundle' => [
          '#markup' => $this->t('<div><span class="label">Paragraph Bundle:</span> @bundle</div>', [
            '@bundle' => !empty($paragraph_bundle[$term_id]) ? implode(', ', $paragraph_bundle[$term_id]) : '--',
          ]),
        ],
        'translations' => [
          '#markup' => $this->t('<div><span class="label">Translations:</span> @langs</div>', [
            '@langs' => $term_translations[$term_id] ?? '',
          ]),
        ],
        'fields_used' => [
          '#markup' => $this->t('<div><span class="label">Fields Used:</span> @fields</div>', [
            '@fields' => implode(', ', $node_fields),
          ]),
        ],
      ];
    }

    // Attach simple CSS.
    $build['#attached']['html_head'][] = [
      [
        '#tag' => 'style',
        '#value' => '
          .tags-summary { margin-bottom: 20px; font-size: 15px; }
          .tag-card {
            border: 1px solid #ddd;
            border-radius: 8px;
            padding: 15px;
            margin-bottom: 15px;
            background: #fafafa;
            box-shadow: 0 2px 5px rgba(0,0,0,0.05);
          }
          .tag-card div { margin-bottom: 6px; }
          .tag-card .label {
            font-weight: 800;
            color: #444;
            display: inline-block;
            min-width: 150px;
          }
        ',
      ],
      'tags_report_styles',
    ];

    return $build;
  }

  /**
   * Get distinct content types that reference the given taxonomy term.
   *
   * @param int $term_id
   *   The taxonomy term ID.
   *
   * @return array
   *   An array of content type machine names.
   */
  private function getContentTypesByTaxonomyTerm($term_id) {
    $database = Database::getConnection();
    $query = $database->select('node_field_data', 'nfd')
      ->distinct()
      ->fields('nfd', ['type']);
    $query->innerJoin('taxonomy_index', 'ti', 'nfd.nid = ti.nid');
    $content_types = $query->condition('ti.tid', $term_id)->execute()->fetchCol();
    return $content_types ?: [];
  }

  /**
   * Find the parent entity node for an entity (Node or Paragraph).
   *
   * Traverses paragraph parent chain until it finds a node.
   *
   * @param mixed $entity
   *   The entity or paragraph instance.
   *
   * @return array|null
   *   An array containing 'bundle' and 'id' of the node or NULL if none.
   */
  private function getParentEntityNode($entity) {
    if ($entity instanceof NodeInterface) {
      return [
        'bundle' => $entity->bundle(),
        'id' => $entity->id(),
      ];
    }
    if ($entity instanceof Paragraph) {
      $parent = $entity->getParentEntity();
      if ($parent !== NULL) {
        return $this->getParentEntityNode($parent);
      }
    }
    return NULL;
  }

  /**
   * Check all fields to see which ones reference the given vocabulary.
   *
   * @param string $vocabularyId
   *   The vocabulary machine name.
   *
   * @return array
   *   An array keyed by entity type listing field names and paragraph mapping.
   */
  public function checkEntityReferenceVocabulary($vocabularyId) {
    $arr_data = [];

    $field_storage_configs = $this->entityTypeManager
      ->getStorage('field_storage_config')
      ->loadMultiple();

    foreach ($field_storage_configs as $field_storage_config) {
      $type = $field_storage_config->getType();
      if ($type === 'entity_reference' || $type === 'entity_reference_revisions') {
        $settings = $field_storage_config->getSettings();
        if (isset($settings['target_type']) && $settings['target_type'] === 'taxonomy_term') {
          $field_configs = $this->entityTypeManager
            ->getStorage('field_config')
            ->loadByProperties(['field_name' => $field_storage_config->getName()]);
          foreach ($field_configs as $field_config) {
            $field_settings = $field_config->getSetting('handler_settings');
            if (isset($field_settings['target_bundles'][$vocabularyId])) {
              $entity_type = $field_config->getTargetEntityTypeId();
              $arr_data[$entity_type][$field_config->getTargetBundle()][] = $field_config->getName();
            }
          }
        }
      }
    }

    return $arr_data;
  }

  /**
   * Check whether term translations are enabled for the vocabulary.
   *
   * @return bool
   *   TRUE if vocabulary term translation is enabled.
   */
  public function isVocabularyTermsTranslationEnabled() : bool {
    if (empty($this->vocabularyId)) {
      return FALSE;
    }
    $vocabulary_storage = $this->entityTypeManager->getStorage('taxonomy_vocabulary');
    $vocabulary = $vocabulary_storage->load($this->vocabularyId);
    if ($vocabulary instanceof Vocabulary) {
      $config = $this->configFactory->get('language.content_settings.taxonomy_term.' . $this->vocabularyId);
      if ($config) {
        $data = $config->getRawData();
        return !empty($data['third_party_settings']['content_translation']['enabled']);
      }
    }
    return FALSE;
  }

  /**
   * Returns term links for each translation (language: <a href="...">name</a>).
   *
   * @param \Drupal\taxonomy\Entity\Term $term
   *   The term entity.
   *
   * @return array
   *   An array of strings ready for Markup containing language code and link.
   */
  public function getTranslationsLinks(Term $term) {
    $languages = $this->languageManager->getLanguages();
    $term_links_translatable = [];

    foreach ($languages as $language) {
      $langcode = $language->getId();
      if ($term->hasTranslation($langcode)) {
        $translation = $term->getTranslation($langcode);
        $term_links_translatable[] = $langcode . ': <a href="' . $translation->toUrl()->toString() . '">' . $translation->getName() . '</a>';
      }
    }
    return $term_links_translatable;
  }

  /**
   * Get language codes for translations available for the term.
   *
   * @param \Drupal\taxonomy\Entity\Term $term
   *   The term entity.
   *
   * @return array
   *   Array of language codes.
   */
  public function getTranslations(Term $term) {
    $languages = $this->languageManager->getLanguages();
    $translations = [];

    $translations[$term->langcode->value] = $term->langcode->value;

    foreach ($languages as $language) {
      $langcode = $language->getId();
      if (!isset($translations[$langcode]) && $term->hasTranslation($langcode)) {
        $translations[] = $langcode;
      }
    }
    return $translations;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc