navigation_extra-1.0.x-dev/src/NavigationExtraPluginBase.php

src/NavigationExtraPluginBase.php
<?php

declare(strict_types=1);

namespace Drupal\navigation_extra;

use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\PluginBase;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Routing\Exception\RouteNotFoundException;

/**
 * Provides a basic deriver with caching capabilities.
 */
abstract class NavigationExtraPluginBase extends PluginBase implements NavigationExtraPluginInterface, ContainerFactoryPluginInterface {

  use StringTranslationTrait;

  /**
   * The entire list of links.
   *
   * @var array
   */
  protected array $links;

  /**
   * The collections for this plugin.
   *
   * @var array
   */
  protected array $collections;

  /**
   * The items this plugin is handling.
   *
   * @var array
   */
  protected array $items;

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

  /**
   * The current user.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  protected AccountProxyInterface $currentUser;

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

  /**
   * The route provider.
   *
   * @var \Drupal\Core\Routing\RouteProviderInterface
   */
  protected RouteProviderInterface $routeProvider;

  /**
   * The module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected ModuleHandlerInterface $moduleHandler;

  /**
   * The admin toolbar config settings.
   *
   * @var \Drupal\Core\Config\ImmutableConfig
   */
  protected ImmutableConfig $config;

  /**
   * The entity repository.
   *
   * @var \Drupal\Core\Entity\EntityRepositoryInterface
   */
  protected EntityRepositoryInterface $entityRepository;

  /**
   * Constructs a \Drupal\Component\Plugin\PluginBase object.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\Core\Language\LanguageManagerInterface $languageManager
   *   The language manager.
   * @param \Drupal\Core\Session\AccountProxyInterface $current_user
   *   The current user.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager service.
   * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
   *   The route provider.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The config factory.
   * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
   *   The entity repository.
   */
  public function __construct(
    array $configuration,
    string $plugin_id,
    mixed $plugin_definition,
    LanguageManagerInterface $languageManager,
    AccountProxyInterface $current_user,
    EntityTypeManagerInterface $entity_type_manager,
    RouteProviderInterface $route_provider,
    ModuleHandlerInterface $module_handler,
    ConfigFactoryInterface $config_factory,
    EntityRepositoryInterface $entity_repository,
  ) {

    parent::__construct($configuration, $plugin_id, $plugin_definition);

    $this->languageManager = $languageManager;
    $this->currentUser = $current_user;
    $this->entityTypeManager = $entity_type_manager;
    $this->routeProvider = $route_provider;
    $this->moduleHandler = $module_handler;
    $this->config = $config_factory->get('navigation_extra.settings');
    $this->entityRepository = $entity_repository;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('language_manager'),
      $container->get('current_user'),
      $container->get('entity_type.manager'),
      $container->get('router.route_provider'),
      $container->get('module_handler'),
      $container->get('config.factory'),
      $container->get('entity.repository'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigForm(array &$form, FormStateInterface $form_state): array {
    $elements = [];

    $elements['enabled'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Enable'),
      '#description' => $this->getPluginDefinition()['description'] ?? $this->t('Enable the %plugin plugin.', [
        '%plugin' => $this->getPluginDefinition()['name'],
      ]),
      '#default_value' => $this->config->get("plugins." . $this->getPluginId() . ".enabled") ?? FALSE,
    ];

    $weight = $this->getPluginDefinition()['weight'] ?? 0;
    $elements['weight'] = [
      '#type' => 'number',
      '#title' => $this->t('Weight'),
      '#description' => $this->t('Menu weight of the %plugin plugin. Set to 0 for default behavior.', ['%plugin' => $this->getPluginDefinition()['name']]),
      '#default_value' => $this->config->get("plugins." . $this->getPluginId() . ".weight") ?? $weight,
    ];

    return $elements;
  }

  /**
   * Add config fields to a plugin config form when it is using create items.
   *
   * @param array $form
   *   The complete config form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   *
   * @return array
   *   An array of elements for configuring the create items.
   */
  protected function buildConfigFormCreateItems(array &$form, FormStateInterface $form_state): array {

    $elements = [];

    $elements['create_items'] = [
      '#type' => 'details',
      '#title' => $this->t('Create items'),
    ];

    $elements['create_items']['hide_from_navigation_create'] = [
      '#type' => 'checkbox',
      '#title' => $this->t("Hide from create new content menu"),
      '#description' => $this->t("Hide from create new content menu."),
      '#default_value' => $this->config->get("plugins.{$this->getPluginId()}.create_items.hide_from_navigation_create") ?? 0,
    ];

    $elements['create_items']['show_create_new_links'] = [
      '#type' => 'checkbox',
      '#title' => $this->t("Show create new content items"),
      '#description' => $this->t("Show create new content items in content menu."),
      '#default_value' => $this->config->get("plugins.{$this->getPluginId()}.create_items.show_create_new_links") ?? 0,
    ];

    $elements['create_items']['navigation_create_collections'] = [
      '#type' => 'checkbox',
      '#title' => $this->t("Use collections in create new content"),
      '#description' => $this->t("Use collections hierarchy in the top level create content menu item."),
      '#default_value' => $this->config->get("plugins.{$this->getPluginId()}.create_items.navigation_create_collections") ?? 0,
    ];

    return $elements;
  }

  /**
   * {@inheritdoc}
   */
  public function submitConfigForm(array &$form, FormStateInterface $form_state): void {
    // Base class does nothing.
  }

  /**
   * {@inheritdoc}
   */
  public function preAlterDiscoveredMenuLinks(array &$links): void {
    // Base class does nothing.
  }

  /**
   * {@inheritdoc}
   */
  public function alterDiscoveredMenuLinks(array &$links): void {
    // Base class does nothing.
  }

  /**
   * {@inheritdoc}
   */
  public function postAlterDiscoveredMenuLinks(array &$links): void {
    // Base class does nothing.
  }

  /**
   * {@inheritdoc}
   */
  public function needsMenuLinkRebuild(EntityInterface $entity): bool {
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function preprocessMenu(array &$variables): void {
    $this->filterMenuItems($variables['items']);
  }

  /**
   * {@inheritdoc}
   */
  public function pageAttachments(array &$page): void {
    // Base class does nothing.
  }

  /**
   * Ensure a route exists and add the link.
   *
   * @param string $link_name
   *   The name of the link being added.
   * @param array $link
   *   The link array, as defined in hook_menu_links_discovered_alter().
   * @param array $links
   *   The existing array of links.
   */
  protected function addLink(string $link_name, array $link, array &$links): void {
    try {
      // Ensure the route exists (there is no separate "exists" method).
      if (isset($link['route_name'])) {
        $this->routeProvider->getRouteByName($link['route_name']);
      }

      // By default add everything to the content menu.
      $links[$link_name] = $link + [
        'menu_name' => 'content',
        'provider' => 'navigation_extra',
      ];
    }
    catch (RouteNotFoundException) {
      // The module isn't installed, or the route (such as provided by a view)
      // has been deleted.
    }
  }

  /**
   * Remove the top level create content links.
   *
   * @param string $link_name
   *   The name of the link to be removed.
   * @param array $links
   *   The array of links being altered.
   */
  public function removeLink(string $link_name, array &$links): void {
    unset($links[$link_name]);

    // Also remove any links that have set admin/content as their parent link.
    // They are unsupported by the Navigation module.
    foreach ($links as $link_name => $link) {
      if (isset($link['parent']) && $link['parent'] === $link_name) {
        unset($links[$link_name]);
      }
    }
  }

  /**
   * Hides links from admin menu, if user doesn't have access rights.
   */
  protected function filterMenuItems(array &$items, &$parent = NULL): void {
    foreach ($items as $menu_id => &$item) {
      if (!empty($item['below'])) {
        // Recursively call this function for the child items.
        $this->filterMenuItems($item['below'], $item);
      }
      if ($this->filterMenuItem($item, $parent)) {
        unset($items[$menu_id]);
      }
    }
  }

  /**
   * Determines if a menu item needs to be filtered out or not.
   *
   * The base class will filter out collection items with no children.
   *
   * @param array $item
   *   The item to check for filtering.
   * @param array|null $parent
   *   The parent item of the given $item.
   *
   * @return bool
   *   True if the items needs to be filtered out, false if we can leave it in.
   */
  protected function filterMenuItem(array &$item, ?array &$parent = NULL): bool {
    if (empty($item['below']) && ($this->config->get('common.hide_empty_collections') ?? FALSE)) {
      $attributes = $item['url']->getOption('attributes') ?? [];
      if (in_array('navigation-extra--collection', $attributes['class'])) {
        return TRUE;
      }
    }

    return FALSE;
  }

  /**
   * Helper to determine if a route exists.
   *
   * @param string $route_name
   *   The name of the route to check.
   *
   * @return bool
   *   True if the route exists or not.
   */
  protected function isRouteAvailable(string $route_name): bool {
    return (count($this->routeProvider->getRoutesByNames([$route_name])) === 1);
  }

  /**
   * {@inheritdoc}
   */
  public function isEnabled(): bool {

    // Check if plugin is enabled in config.
    $enabled = (bool) $this->config->get("plugins." . $this->getPluginId() . '.enabled') ?? FALSE;

    if ($enabled) {
      // If the plugin deals with entities, make sure the type it handles
      // exists.
      $definition = $this->getPluginDefinition();
      $entity_type = $definition['entity_type'] ?? FALSE;
      if ($entity_type) {
        try {
          // Try to fetch the storage for the entity type.
          $this->entityTypeManager->getStorage($entity_type);
        }
        catch (InvalidPluginDefinitionException | PluginNotFoundException) {
          return FALSE;
        }
      }
    }

    return $enabled;
  }

  /**
   * Get the collection an item belongs to.
   *
   * @param mixed $item
   *   The item.
   * @param array|null $collections
   *   The collections.
   *
   * @return array
   *   The collections for the item.
   */
  protected function getItemCollection(mixed $item, ?array $collections = NULL): array {

    $found = [];

    if (!isset($collections)) {
      $collections = $this->getCollections();
    }

    foreach ($collections as $collection) {
      if (in_array($item->id(), $collection['items'] ?? [])) {
        $found = $collection;
      }
      else {
        $found = $this->getItemCollection($item, $collection['collections'] ?? []);
      }

      if (!empty($found)) {
        break;
      }
    }

    return $found;
  }

  /**
   * Get the collection structure for this plugin and stores it into the class.
   *
   * @return array
   *   A flattened array of collections to process for this plugin.
   */
  protected function getCollections(): array {

    if (empty($this->collections)) {
      // $collections = $this->moduleHandler->invokeAll(
      // 'navigation_extra_collections'
      // );
      $this->collections = $this->moduleHandler->invokeAll('navigation_extra_collections');

      foreach ($this->collections as &$collection) {
        // Sort collections so grouping will use alphabetic order.
        asort($collection);

        $this->prepCollection($collection);
      }
    }

    return $this->collections[$this->getPluginId()] ?? [];
  }

  /**
   * Preps the collections array, so it can be used for creating items.
   *
   * @param array $collections
   *   The collections.
   */
  protected function prepCollection(array &$collections): void {
    // Get the starting weight, or 0 if no grouping for collections is set.
    $group_collections_weight = [
      'bottom' => 100,
      'top' => -100,
    ][$this->config->get('common.group_collections')] ?? 0;

    foreach ($collections as $id => &$collection) {

      // The id represents the hierarchy of the collections, so add the parent.
      $collection['id'] = $id;

      // Use group collections weight if group collections is enabled.
      if (!empty($group_collections_weight) && empty($collection['weight'])) {
        $collection['weight'] = $group_collections_weight;
        $group_collections_weight++;
      }

      if (isset($collection['collections'])) {
        asort($collection['collections']);
        $this->prepCollection($collection['collections']);
      }
    }
  }

  /**
   * Get an array of items to create item links for.
   *
   * @return array
   *   The items as array.
   */
  protected function getItems($entity_type): array {

    if (empty($this->items[$entity_type])) {
      try {
        $this->items[$entity_type] = $this->entityTypeManager
          ->getStorage($entity_type)
          ->loadMultiple();
      }
      catch (InvalidPluginDefinitionException | PluginNotFoundException) {
        return [];
      }
    }

    return $this->items[$entity_type];
  }

  /**
   * Creates the collection links menu items tree.
   *
   * Example:
   *
   * $links['navigation.content.collection_a']
   * $links['navigation.content.collection_b']
   *
   * @param string $parent
   *   The parent link ID, used to nest child collections.
   * @param callable $link_callback
   *   Callback providing additional default route configuration
   *   for the collection links.
   * @param array $links
   *   The links generated so far.
   * @param array|null $collections
   *   The collections to be processed.
   *
   * @see \Drupal\navigation_extra\NavigationExtraPluginBase::getCollections()
   */
  protected function addCollectionLinks(
    string $parent,
    callable $link_callback,
    array &$links,
    ?array $collections = NULL,
  ): void {

    if (!isset($collections)) {
      $collections = $this->getCollections();
    }

    foreach ($collections as $collection) {

      $link_name = "$parent.{$collection['id']}";

      $link = $link_callback($collection) + [
        'title' => $collection['label'],
        'route_parameters' => [
          'collection' => $collection['id'],
        ],
        'parent' => $parent,
        'weight' => $collection['weight'] ?? 0,
        'options' => [
          'attributes' => [
            'class' => [
              'navigation-extra--collection',
              'navigation-extra--collection--' . $this->getPluginId(),
              'navigation-extra--collection--' . str_replace('.', '--', $collection['id']),
            ],
          ],
        ],
      ];

      $this->addLink($link_name, $link, $links);

      if (!empty($collection['collections'])) {
        $this->addCollectionLinks($link_name, $link_callback, $links, $collection['collections']);
      }

    }
  }

  /**
   * Creates the item links for a collection.
   *
   * @param string $parent
   *   The parent link ID, used to nest child collections.
   * @param string $entity_type
   *   The kind of entity items we need to process.
   * @param callable $link_callback
   *   Callback providing additional default route configuration
   *   for the item links.
   * @param array $links
   *   The links generated so far.
   */
  protected function addItemLinks(
    string $parent,
    string $entity_type,
    callable $link_callback,
    array &$links,
  ): void {

    $items = $this->getItems($entity_type);

    foreach ($items as $item) {
      $collection = $this->getItemCollection($item);

      $item_parent = rtrim("$parent." . ($collection['id'] ?? ''), '.');

      $link_name = "$item_parent.{$item->id()}";

      $link = $link_callback($item) + [
        'class' => '\Drupal\navigation_extra\Plugin\Menu\TranslatedMenuLink',
        'title' => $item->label(),
        'parent' => $item_parent,
        'weight' => $collection['weight'] ?? 0,
        'options' => [
          'attributes' => [
            'class' => [
              'navigation-extra--collection--item',
              'navigation-extra--collection--item--' . $item->id(),
            ],
          ],
        ],
        'metadata' => [
          'entity_id' => $item->id(),
          'entity_type' => $entity_type,
        ],
      ];

      $this->addLink($link_name, $link, $links);
    }

  }

  /**
   * Creates the add new item links for a collection item.
   *
   * @param string $parent
   *   The parent link menu to add the item links.
   * @param string $entity_type
   *   The kind of entity items we need to process.
   * @param callable $link_callback
   *   Callback providing additional default route configuration
   *   for the item add links.
   * @param array $links
   *   The links generated so far.
   */
  protected function addCreateNewItemLinks(
    string $parent,
    string $entity_type,
    callable $link_callback,
    array &$links,
  ): void {

    $items = $this->getItems($entity_type);

    foreach ($items as $item) {
      $collection = $this->getItemCollection($item);

      $item_parent = rtrim("$parent." . ($collection['id'] ?? ''), '.');

      $link_name = "$item_parent.{$item->id()}.add";

      $link = $link_callback($item) + [
        'title' => $this->t("Add new"),
        'parent' => "$item_parent.{$item->id()}",
        'options' => [
          'attributes' => [
            'class' => [
              'navigation-extra--collection--item--add',
              'navigation-extra--collection--item--add--' . $item->id(),
            ],
          ],
        ],
      ];

      $this->addLink($link_name, $link, $links);
    }

  }

  /**
   * Adds create entity links to the menu structure.
   *
   * This method adds links for creating new entities to the specified
   * collection or parent link in the navigation menu. If the configuration
   * specifies that the links should be hidden, it removes them instead.
   *
   * @param string $parent_link_name
   *   The name of the parent link where the new links will be added.
   * @param string $collection_route_name
   *   The route name of the collection where the links are added.
   * @param string $add_route_name
   *   The route name for adding new entities.
   * @param string $entity_type
   *   The type of the entity being linked.
   * @param array &$links
   *   The array of links being altered.
   */
  protected function addCreateEntityLinks(string $parent_link_name, string $collection_route_name, string $add_route_name, string $entity_type, array &$links): void {
    $hide_from_navigation_create = $this->config->get("plugins.{$this->getPluginId()}.create_items.hide_from_navigation_create") ?? 0;
    if ($hide_from_navigation_create) {
      // Remove the items from navigation.create.
      $items = $this->getItems($entity_type);
      foreach ($items as $item) {
        $this->removeLink("navigation.content.{$entity_type}.{$item->id()}", $links);
      }
    }
    else {
      // Use collections in the create new content menu item.
      $navigation_create_collections = $this->config->get("plugins.{$this->getPluginId()}.create_items.navigation_create_collections") ?? 0;
      if ($navigation_create_collections) {
        // Remove the items from navigation.create.
        $items = $this->getItems($entity_type);
        foreach ($items as $item) {
          $this->removeLink("navigation.content.{$entity_type}.{$item->id()}", $links);
        }

        // Add collections to navigation.create.
        $this->addCollectionLinks(
          'navigation.create',
          fn($collection) => ([
            'route_name' => $collection_route_name,
            'route_parameters' => [
              'collection' => $collection['id'],
            ],
          ]),
          $links
        );

        // Re-create items to navigation.create.
        $this->addItemLinks(
          'navigation.create',
          $entity_type,
          fn($item) => ([
            'route_name' => $add_route_name,
            'route_parameters' => [
              $entity_type => $item->id(),
            ],
          ]),
          $links
        );
      }
    }

    $show_create_new_links = $this->config->get("plugins.{$this->getPluginId()}.create_items.show_create_new_links") ?? 0;
    if ($show_create_new_links) {
      $this->addCreateNewItemLinks(
        $parent_link_name,
        $entity_type,
        fn($item) => ([
          'route_name' => $add_route_name,
          'route_parameters' => [
            $entity_type => $item->id(),
          ],
        ]),
        $links
      );
    }
  }

}

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

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