logo

Дополнительные типы блоков (EBT) — новый опыт конструктора страниц❗

Дополнительные типы блоков (EBT) — стилизованные, настраиваемые типы блоков: слайдшоу, вкладки, карточки, аккордеоны и многие другие. Встроенные настройки для фона, DOM Box, плагины Javascript.

Демо EBT модули Скачать EBT модули

❗Дополнительные типы параграфов (EPT) — новый опыт работы с параграфами

Дополнительные типы параграфов (EPT) — набор модулей, основанный на аналогичных параграфах.

Демо EPT модули Скачать EPT модули

Scroll

Ограничение доступа к словарю терминов таксономии с помощью подписчика событий (Event Subscriber)

11/06/2025, by Ivan

Menu

Иногда на сайте требуются фиксированные, постоянные категории, которые не должны случайно обновляться. В этом случае можно использовать пользовательский код с подписчиком событий (Event Subscriber).

Добавим новый класс подписчика событий в кастомный модуль.

drupalbook_custom.services.yml

services:  
  drupalbook_custom.tag_redirect_subscriber:
    class: Drupal\drupalbook_custom\EventSubscriber\TagRedirectSubscriber
    arguments:
      - '@entity_type.manager'
      - '@current_user'
    tags:
      - { name: event_subscriber }

Теперь добавим наш подписчик событий в drupalbook_custom/src/EventSubscriber/TagRedirectSubscriber:

<?php

namespace Drupal\drupalbook_custom\EventSubscriber;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Routing\TrustedRedirectResponse;
use Drupal\Core\Session\AccountProxyInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;

/**
 * Перенаправляет неадминистраторов со страниц администрирования словаря "tag".
 *
 * Подписчик на уровне запроса срабатывает рано, что позволяет прервать
 * выполнение и вернуть редирект до выполнения контроллера.
 */
class TagRedirectSubscriber implements EventSubscriberInterface {

  /**
   * Сервис entity-type manager.
   *
   * Приведен как пример зависимости; не обязателен для текущей логики,
   * но полезен для будущих доработок, если потребуется загрузка сущностей.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected EntityTypeManagerInterface $entityTypeManager;

  /**
   * Сервис текущего пользователя.
   *
   * Используется для быстрого определения роли пользователя,
   * чтобы не перенаправлять администраторов.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  protected AccountProxyInterface $currentUser;

  /**
   * Конструктор подписчика.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   Менеджер сущностей.
   * @param \Drupal\Core\Session\AccountProxyInterface $current_user
   *   Текущий пользователь, выполняющий запрос.
   */
  public function __construct(
    EntityTypeManagerInterface $entity_type_manager,
    AccountProxyInterface $current_user,
  ) {
    $this->entityTypeManager = $entity_type_manager;
    $this->currentUser = $current_user;
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents(): array {
    // Приоритет 32 — параметры маршрута уже доступны,
    // но контроллер еще не вызван.
    return [
      KernelEvents::REQUEST => ['onKernelRequest', 32],
    ];
  }

  /**
   * Выполняет редирект, если неадминистратор обращается к страницам управления словарем "tag".
   *
   * @param \Symfony\Component\HttpKernel\Event\RequestEvent $event
   *   Событие ядра с информацией о запросе.
   */
  public function onKernelRequest(RequestEvent $event): void {
    // Работаем только с основным запросом.
    if (!$event->isMainRequest()) {
      return;
    }

    // Администраторов не перенаправляем.
    if ($this->currentUser->hasRole('administrator')) {
      return;
    }

    $request = $event->getRequest();
    $route_name = $request->attributes->get('_route');

    // Куда перенаправлять все запрещенные попытки.
    $redirect_to = 'https://drupalbook.org/admin/structure'
      . '/taxonomy/manage/tag/overview';

    switch ($route_name) {
      case 'entity.taxonomy_vocabulary.overview_form':
      case 'entity.taxonomy_vocabulary.overview_terms':
      case 'entity.taxonomy_term.add_form':
        // Убеждаемся, что это словарь "tag".
        $vocabulary = $request->attributes->get('taxonomy_vocabulary');
        if (!empty($vocabulary) && $vocabulary->id() === 'tag') {
          $event->setResponse(new TrustedRedirectResponse($redirect_to));
        }
        return;

      case 'entity.taxonomy_term.edit_form':
      case 'entity.taxonomy_term.delete_form':
        /** @var \Drupal\taxonomy\Entity\Term|null $term */
        $term = $request->attributes->get('taxonomy_term');
        // bundle() возвращает машинное имя словаря.
        if ($term && $term->bundle() === 'tag') {
          $event->setResponse(new TrustedRedirectResponse($redirect_to));
        }
        return;

      default:
        return;
    }
  }

}

Класс TagRedirectSubscriber — это пользовательский подписчик событий в Drupal, предназначенный для ограничения доступа к административным страницам конкретного словаря таксономии (в данном случае, "tag") для неадминистраторов. Ниже представлен разбор его структуры и ключевых моментов:

1. Назначение и область применения

  • Цель: Предотвратить случайные или несанкционированные изменения в словаре "tag", перенаправляя неадминистраторов с его административных маршрутов.
  • Преимущество: Добавляет дополнительный уровень UI/UX-контроля доступа для критически важных словарей, поддерживая стабильность фиксированных категорий.

2. Структура класса и зависимости

  • Класс реализует EventSubscriberInterface, что делает его совместимым с событийной системой Symfony, используемой в Drupal.
  • Зависимости внедряются через конструктор:
    • EntityTypeManagerInterface: Добавлен на будущее, если потребуется работа с сущностями. Не обязателен для текущей логики, но облегчает расширяемость.
    • AccountProxyInterface: Используется для быстрого получения информации о ролях пользователя.
    Внедрение зависимостей обеспечивает тестируемость, переиспользуемость и следование лучшим практикам сервис-контейнера Drupal.

3. Подписка на события

  • Класс подписан на событие KernelEvents::REQUEST с приоритетом 32.
    Этот приоритет гарантирует:
    • Параметры маршрута уже доступны (маршрутизация завершена).
    • Контроллер еще не выполнен, что позволяет перехватить и прервать выполнение запроса до передачи его контроллеру.

4. Логика перенаправления

  • Весь доступ и логика перенаправления реализованы в методе onKernelRequest():
    • Обработка только основного запроса: Исключает дублирующую обработку на подзапросах.
    • Разрешение для администраторов: Если у пользователя есть роль administrator, доступ не ограничивается.
    • Проверка маршрутов: Обрабатываются только определенные маршруты, связанные со словарем таксономии и его терминами.
    • Перенаправление неадминистраторов:
      • Для overview, add, list (entity.taxonomy_vocabulary.overview_form, entity.taxonomy_vocabulary.overview_terms, entity.taxonomy_term.add_form) проверяется, что речь о словаре tag.
      • Для edit и delete (entity.taxonomy_term.edit_form, entity.taxonomy_term.delete_form) проверяется bundle() термина (машинное имя словаря).
    • Использование Trusted Redirect: При совпадении условий пользователь перенаправляется на страницу обзора словаря "tag".
  • Расширяемость: Логика легко дорабатывается для дополнительных словарей или ролей при необходимости.

5. Безопасность и лучшие практики

  • Ранний перехват: Благодаря запуску на этапе запроса подписчик может ограничить доступ до обработки или отображения каких-либо данных.
  • Проверка ролей: Эффективно определяет роли пользователя, чтобы не ограничивать доступ администраторам и разработчикам сайта.
  • Чистое разделение логики: Отдельно реализованы маршрутизация, проверки пользователя и логика перенаправления для удобства поддержки.

6. Возможные улучшения

  • Так как EntityTypeManagerInterface уже внедрен, в будущем можно реализовать проверки на уровне сущностей (например, доступ к отдельным терминам или связанному контенту).
  • Класс можно обобщить для поддержки нескольких словарей или сделать перенаправления настраиваемыми через конфигурацию.

7. Основные выводы

  • Данный подписчик событий реализует эффективный способ контроля доступа в Drupal, используя событийную архитектуру Symfony для ранней и быстрой обработки запроса.
  • Подход идеально подходит для защиты таксономий, которыми должны управлять только доверенные пользователи, снижая риск случайных изменений.