admin_toolbar_content-1.0.0/src/Plugin/AdminToolbarContent/AdminToolbarContentContentPlugin.php

src/Plugin/AdminToolbarContent/AdminToolbarContentContentPlugin.php
<?php

namespace Drupal\admin_toolbar_content\Plugin\AdminToolbarContent;

use Drupal\admin_toolbar_content\AdminToolbarContentPluginBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Menu\MenuLinkDefault;
use Drupal\views\Views;

/**
 * An AdminToolbarContentPlugin for altering the system content menu.
 *
 * @see \Drupal\admin_toolbar_content\Plugin\Derivative\AdminToolbarContentMenuLinks.
 *
 * @AdminToolbarContentPlugin(
 *   id = "content",
 *   name = @Translation("Content"),
 *   description = @Translation("Alters the system content menu to provide links for each content type."),
 *   entity_type = "node_type"
 * )
 */
class AdminToolbarContentContentPlugin extends AdminToolbarContentPluginBase {

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

    $elements += $this->buildConfigFormRecentItems($form, $form_state);

    $elements['hide_non_content_items'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Hide non content items'),
      '#description' => $this->t('Hides items under "content" not directly related to content types.'),
      '#default_value' => $this->config->get("plugins." . $this->getPluginId() . '.hide_non_content_items') ?? FALSE,
    ];

    $elements['hide_content_type_items'] = [
      '#type' => 'checkboxes',
      '#title' => $this->t('Hide content type items'),
      '#description' => $this->t('If one of these conditions is met, the relevant content type item will be hidden.'),
      '#options' => [
        'content_view' => $this->t("If the content type is not available in the 'content' view 'type' filter."),
        'admin_permissions' => $this->t("If the user doesn't have administrative permissions."),
      ],
      '#default_value' => $this->config->get("plugins." . $this->getPluginId() . '.hide_content_type_items') ?? [],
    ];

    return $elements;
  }

  /**
   * Add config fields to a plugin config form when it is using recent 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 recent items.
   */
  protected function buildConfigFormRecentItems(array &$form, FormStateInterface $form_state): array {

    $elements = [];

    $elements['recent_items'] = [
      '#type' => 'details',
      '#title' => $this->t('Recent content'),
    ];

    $elements['recent_items']['number_of_items'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Number of items'),
      '#description' => $this->t('The amount of items to show. Set to 0 or leave empty to disable recent items.'),
      '#default_value' => $this->config->get("plugins." . $this->getPluginId() . '.recent_items.number_of_items') ?? 5,
    ];

    $elements['recent_items']['hide_empty_list'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Hide empty list'),
      '#description' => $this->t('Hide recent content list if non are available.'),
      '#default_value' => $this->config->get("plugins." . $this->getPluginId() . '.recent_items.hide_empty_list') ?? FALSE,
    ];

    $recent_items_link_options = [
      'default' => $this->t('Edit form'),
      'view' => $this->t('View'),
    ];

    // Add Layout Builder option only if layout builder is enabled.
    if ($this->moduleHandler->moduleExists('layout_builder')) {
      $recent_items_link_options['layout_builder'] = $this->t('Layout Builder');
    }

    $elements['recent_items']['link'] = [
      '#type' => 'radios',
      '#title' => $this->t('Link'),
      '#description' => $this->t('Choose where recent items should be linked to.'),
      '#options' => $recent_items_link_options,
      '#default_value' => $this->config->get("plugins." . $this->getPluginId() . '.link') ?? 'default',
    ];

    return $elements;
  }

  /**
   * {@inheritdoc}
   */
  public function alterDiscoveredMenuLinks(array &$links): void {

    $hide_non_content_items = $this->config->get("plugins." . $this->getPluginId() . '.hide_non_content_items') ?? 0;
    if ($hide_non_content_items) {
      $parents = ['system.admin_content'];
      while (!empty($parents)) {
        $removed = [];
        foreach ($links as $name => $link) {
          if (isset($link['parent']) && in_array($link['parent'], $parents)) {
            if (!str_starts_with($name, 'admin_toolbar_content')) {
              unset($links[$name]);
              $removed[] = $name;
            }
          }
        }
        $parents = $removed;
      }
    }

    // Unset the original "add content" menu item and it's children.
    // These are replaced with the links from createMenuLinkItems.
    unset($links['admin_toolbar_tools.extra_links:node.add']);
    $contentTypes = $this->getItems();
    foreach ($contentTypes as $contentType) {
      unset($links['admin_toolbar_tools.extra_links:node.add.' . $contentType->id()]);
    }

  }

  /**
   * {@inheritdoc}
   */
  public function needsMenuLinkRebuild(EntityInterface $entity): bool {
    return (bool) (
      $entity->getEntityTypeId() === 'node' &&
      $this->config->get("plugins." . $this->getPluginId() . '.recent_items.number_of_items') ?? 0
    );
  }

  /**
   * {@inheritdoc}
   */
  public function createMenuLinkItems(): void {
    $this->createCollectionLinks('system.admin_content');
    $this->createItemLinks('system.admin_content', 'type');
    $this->createItemAddLinks('node.add');
    $this->createItemRecentContentLinks('node');
  }

  /**
   * {@inheritdoc}
   */
  protected function createCollectionLink(array $collection, $route_name, $route_parameters = []): void {
    parent::createCollectionLink($collection, $route_name, $route_parameters);
    if (isset($this->links[$collection['id']])) {
      // Because we don't have a custom root item, we add collections to the
      // existing system content menu item.
      if ($collection['parent'] == $this->getPluginId()) {
        $this->links[$collection['id']]['parent'] = 'system.admin_content';
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  protected function createItemLink(mixed $item, string $route_name, string $route_item_parameter): void {
    parent::createItemLink($item, $route_name, $route_item_parameter);
    $collection = $this->getItemCollection($item);
    if (isset($this->links[$collection['id'] . '.' . $item->id()])) {
      // Because we don't have a custom root item, we add items without a
      // collection to the existing system content menu item.
      if ($collection['id'] == $this->getPluginId()) {
        $this->links[$collection['id'] . '.' . $item->id()]['parent'] = 'system.admin_content';
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  protected function filterMenuItem(array &$item, &$parent = NULL): bool {

    // Only fetch this once, so stick it in a static variable.
    static $view_definition = NULL;

    /** @var \Drupal\Core\Url $url */
    $url = $item['url'];
    if ($url->isRouted() && $url->getRouteName() == 'system.admin_content') {
      // Determine if we need to hide content type menu items.
      $params = $url->getRouteParameters();
      $content_type = $params['type'] ?? '';

      if (empty($content_type)) {
        return FALSE;
      }

      $hide_content_type_items = $this->config->get("plugins." . $this->getPluginId() . '.hide_content_type_items') ?? [];

      if (!empty($hide_content_type_items['content_view'])) {
        // If the content type is not set in the content view type filter
        // selection, then don't show the menu item.
        if (!$view_definition) {
          // Check if there is a content type specific view, else fall back to
          // the normal content view of core.
          $view = Views::getView('content_' . $content_type) ?? Views::getView('content');
          if ($view) {
            // Look for the page_1 display (set by core by default). Or fallback
            // to the default settings.
            /** @var \Drupal\views\Plugin\views\ViewsHandlerInterface $handler */
            $view_definition = $view->getHandler('page_1', 'filter', 'type')
              ?? $view->getHandler('default', 'filter', 'type')
              ?? [];
          }
        }

        if (!empty($view_definition['value']) && !in_array($content_type, $view_definition['value'])) {
          return TRUE;
        }
      }

      if (!empty($hide_content_type_items['admin_permissions']) && $this->currentUser->id() > 1) {

        // We need at least permission to see the overview page.
        if (!$this->currentUser->hasPermission('access content overview')) {
          return TRUE;
        }

        // If the user doesn't have at least one of these administrative
        // permissions on the content type, then don't show the menu item.
        $admin_permissions = FALSE;
        $permissions = [
          // General node permissions.
          'bypass node access',
          'administer nodes',
          'view own unpublished content',
          'view all revisions',
          'revert all revisions',
          'delete all revisions',
          // Content type specific permissions.
          "create $content_type content",
          "edit own $content_type content",
          "edit any $content_type content",
          "delete own $content_type content",
          "delete any $content_type content",
          "view $content_type revisions",
          "revert $content_type revisions",
          "delete $content_type revisions",
        ];
        foreach ($permissions as $permission) {
          $admin_permissions |= $this->currentUser->hasPermission($permission);
          if ($admin_permissions) {
            // We know enough, stop checking.
            break;
          }
        }

        if (!$admin_permissions) {
          return TRUE;
        }

      }

    }

    // Filter out "recent items" or "more" children if there are no recent menu
    // children left.
    if (!empty($item['below'])) {

      $recent_items_index = null;
      $more_items_index = null;
      $count_recent_items = 0;
      foreach ($item['below'] as $index => $child) {
        $attributes = $child['url']->getOption('attributes') ?? [];
        if (in_array('admin-toolbar-content--recent-items', $attributes['class'])) {
          $recent_items_index = $index;
        } elseif (in_array('admin-toolbar-content--more-recent-items', $attributes['class'])) {
          $more_items_index = $index;
        } elseif (in_array('admin-toolbar-content--recent-item', $attributes['class'])) {
          // Remove the child if it is still an empty placeholder
          // (has no parameter entity id filled in).
          if (empty($child['url']->getRouteParameters()['node'])) {
            unset($item['below'][$index]);
          }
          else {
            $count_recent_items++;
          }
        }
      }

      if (!$count_recent_items && ($this->config->get("plugins." . $this->getPluginId() . '.recent_items.hide_empty_list') ?? 0)) {
        unset($item['below'][$recent_items_index]);
        unset($item['below'][$more_items_index]);
      }

      // If less then the desired count items remain, just remove the more link.
      $count = $this->config->get("plugins." . $this->getPluginId() . '.recent_items.number_of_items') ?? 0;
      if ($count_recent_items < $count) {
        unset($item['below'][$more_items_index]);
      }

    }

    return parent::filterMenuItem($item, $parent);
  }

  /**
   * Creates the recent content links for items.
   *
   * @param string $type
   *   The type.
   */
  protected function createItemRecentContentLinks(string $type): void {

    $count = $this->config->get("plugins." . $this->getPluginId() . '.recent_items.number_of_items') ?? 0;

    if (empty($count)) {
      return;
    }

    $items = $this->getItems();

    $recent_items_link = $this->config->get("plugins." . $this->getPluginId() . '.recent_items.link') ?? 'default';

    $route_name = [
      'default' => 'entity.node.edit_form',
      'view' => 'entity.node.canonical',
      'layout_builder' => 'layout_builder.overrides.' . $type . '.view',
    ][$recent_items_link];

    $route_parameters = [];

    foreach ($items as $item) {
      $this->createItemRecentContentEditLinks($item, $type, $count, $route_name, $route_parameters);
    }

  }

  /**
   * Create the edit links for the recent items in the content.
   *
   * @param mixed $item
   *   The item.
   * @param string $entity_type
   *   The entity type.
   * @param int $count
   *   How many items to generate.
   * @param string $route_name
   *   The route name.
   * @param array $route_parameters
   *   The route parameters.
   */
  protected function createItemRecentContentEditLinks(mixed $item, string $entity_type, int $count, string $route_name, array $route_parameters = []): void {

    $collection = $this->getItemCollection($item);

    // If the item link is not there, we can't add recent items.
    if (!isset($this->links[$collection['id'] . '.' . $item->id()])) {
      return;
    }

    $type = $this->entityTypeManager->getDefinition($entity_type);

    /** @var \Drupal\node\NodeStorageInterface $entity_storage */
    $entity_storage = $this->entityTypeManager->getStorage($entity_type);

    // Check if we have items of this type.
    $ids = $entity_storage->getQuery()
      // We don't do the access check here, we let menu handle that.
      // Otherwise, the check is performed on the user doing the rebuild.
      ->accessCheck(FALSE)
      ->condition($type->getKey('bundle'), $item->id())
      ->range(0, $count + 1)
      ->sort('changed', 'DESC')
      ->sort($type->getKey('id'), 'DESC')
      ->execute();

    if (empty($ids)) {
      return;
    }

    // Add a "Recent items" empty menu item, to create a visual divider
    // in the menu.
    $this->links[$collection['id'] . '.' . $item->id() . '.recent'] = [
        'title' => $this->t('Recent items'),
        'route_name' => '<none>',
        'parent' => $this->baseMenuLinkPluginDefinition['id'] . ':' . $collection['id'] . '.' . $item->id(),
        'weight' => 0,
        'options' => [
          'attributes' => [
            'class' => [
              'admin-toolbar-content--recent-items',
              'admin-toolbar-content--recent-items--' . $item->id(),
            ],
          ],
        ],
        // We need to override admin_toolbar_tools MenuLinkEntity::getTitle().
        'class' => "Drupal\Core\Menu\MenuLinkDefault",
      ] + $this->links[$collection['id'] . '.' . $item->id()];

    // Create menu item placeholders. Each user will have a different list
    // of recent menu items. So we create menu items with out custom Menu link
    // entity that will replace the placeholder with a linke to the entities
    // the user is entitled to administer.
    for ($counter = 0; $counter < $count; $counter++) {
      // Use a placeholder entity id (0).
      $route_parameters = [
          $entity_type => 0,
        ] + $route_parameters;

      if ($this->isRouteAvailable($route_name)) {
        $this->createItemRecentContentEditLink($collection, $item, $route_name, $route_parameters, $counter);
      }
    }

    // Add a "More" menu item, linking to the content overview.
    if (count($ids) > $count) {
      $this->links[$collection['id'] . '.' . $item->id() . '.more'] = [
          'title' => $this->t('More'),
          'weight' => $counter + 2,
          'parent' => $this->baseMenuLinkPluginDefinition['id'] . ':' . $collection['id'] . '.' . $item->id(),
          'options' => [
            'attributes' => [
              'class' => [
                'admin-toolbar-content--more-recent-items',
                'admin-toolbar-content--more-recent-items--' . $item->id(),
              ],
            ],
          ],
          // We need to override admin_toolbar_tools MenuLinkEntity::getTitle().
          'class' => "Drupal\Core\Menu\MenuLinkDefault",
        ] + $this->links[$collection['id'] . '.' . $item->id()];
    }
  }

  /**
   * Create the edit link for the recent item in the content.
   *
   * @param array $collection
   *   The collections.
   * @param mixed $item
   *   The item to create the link for.
   * @param string $route_name
   *   The route name.
   * @param array $route_parameters
   *   The route parameters.
   * @param int $weight
   *   The weight.
   */
  protected function createItemRecentContentEditLink(array $collection, mixed $item, string $route_name, array $route_parameters, int $count): void {

    $this->links[$collection['id'] . '.' . $item->id() . '.recent.entity.' . $count] = [
        'class' => 'Drupal\admin_toolbar_content\Plugin\Menu\RecentMenuLinkEntity',
        'route_name' => $route_name,
        'route_parameters' => $route_parameters,
        'menu_name' => 'admin',
        'weight' => $count + 1,
        'parent' => $this->baseMenuLinkPluginDefinition['id'] . ':' . $collection['id'] . '.' . $item->id(),
        'options' => [
          'attributes' => [
            'class' => [
              'admin-toolbar-content--recent-item',
              'admin-toolbar-content--recent-item--' . $item->id(),
            ],
          ],
        ],
        'metadata' => [
          // See RecentMenuLinkEntity class how these are used.
          'entity_type' => $item->getEntityType()->getBundleOf(),
          'entity_bundle' => $item->id(),
          'entity_count' => $count,
        ],
      ] + $this->baseMenuLinkPluginDefinition;
  }
}

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

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