type_tray-1.0.x-dev/src/Controller/TypeTrayController.php

src/Controller/TypeTrayController.php
<?php

namespace Drupal\type_tray\Controller;

use Drupal\Component\Utility\SortArray;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Link;
use Drupal\Core\Url;
use Drupal\node\Controller\NodeController;
use Drupal\node\Entity\NodeType;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

/**
 * Tweaks NodeController according to our needs.
 */
class TypeTrayController extends NodeController {

  /**
   * The module-relative path of the thumbnail to be used by default.
   */
  public const TYPE_TRAY_DEFAULT_THUMBNAIL_PATH = '/assets/thumbnails/wysiwyg1.png';

  /**
   * The module-relative path of the icon to be used by default.
   */
  public const TYPE_TRAY_DEFAULT_ICON_PATH = '/assets/icons/file-text.svg';

  /**
   * The "Uncategorized" fall-back category label.
   */
  public const UNCATEGORIZED_LABEL = 'Uncategorized';

  /**
   * The key to be used for the fall-back category.
   */
  public const UNCATEGORIZED_KEY = '_none';

  /**
   * The key to be used for the "Favorites" category, if applicable.
   */
  public const FAVORITES_KEY = 'type_tray__favorites';

  /**
   * The label to be used for the "Favorites" category, if applicable.
   */
  public const FAVORITES_LABEL = 'Favorites';

  /**
   * The cache tags invalidator service.
   *
   * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface
   */
  protected $cacheTagsInvalidator;

  /**
   * The module extension list.
   *
   * @var \Drupal\Core\Extension\ModuleExtensionList
   */
  protected $moduleList;

  /**
   * The file URL generator service.
   *
   * @var \Drupal\Core\File\FileUrlGeneratorInterface
   */
  protected $fileUrlGenerator;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    $instance = parent::create($container);
    $instance->cacheTagsInvalidator = $container->get('cache_tags.invalidator');
    $instance->moduleList = $container->get('extension.list.module');
    $instance->fileUrlGenerator = $container->get('file_url_generator');
    return $instance;
  }

  /**
   * Override the addPage so we are able to display it our way.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request object.
   *
   * @return array
   *   A render array for a list of node types that can be added.
   */
  public function addPage(?Request $request = NULL) {
    $config = $this->config('type_tray.settings');
    if ($request) {
      $layout = $request->query->get('layout') ?? 'grid';
    }
    else {
      $layout = 'grid';
    }
    $build = [
      '#theme' => 'type_tray_page',
      '#items' => [],
      '#layout' => $layout,
      '#category_labels' => static::getTypeTrayCategories(),
      '#cache' => [
        'tags' => $this->entityTypeManager()->getDefinition('node_type')->getListCacheTags(),
      ],
      '#attached' => [
        'library' => 'type_tray/type_tray',
      ],
    ];

    // Only use node types the user has access to.
    $types = [];
    foreach ($this->entityTypeManager()->getStorage('node_type')->loadMultiple() as $type) {
      $access = $this->entityTypeManager()->getAccessControlHandler('node')->createAccess($type->id(), NULL, [], TRUE);
      if ($access->isAllowed()) {
        $types[$type->id()] = $type;
      }
      $this->renderer->addCacheableDependency($build, $access);
    }

    // Group the types by their categories.
    $tmp_types = [];
    // If there is at least one favorite for this user, create a favorites
    // category as the first group.
    $user_favorites = static::getUserFavorites();
    if (!empty($user_favorites)) {
      $favorites = array_map(function ($item) use ($types) {
        return $types[$item];
      }, array_combine($user_favorites, $user_favorites));
      $tmp_types[static::FAVORITES_KEY] = $favorites;
    }
    foreach ($types as $type_id => $type) {
      $category = static::getCategory($type->id());
      $tmp_types[key($category)][] = $type;
    }
    // We will honor the order categories were entered in the config form, and
    // make sure "Uncategorized" comes after all of them.
    $grouped_types = [];
    foreach (static::getTypeTrayCategories() as $category_key => $category_label) {
      if (!empty($tmp_types[$category_key])) {
        $grouped_types[$category_key] = $tmp_types[$category_key];
      }
    }
    if (!empty($grouped_types[static::UNCATEGORIZED_KEY])) {
      $uncategorized_group = $grouped_types[static::UNCATEGORIZED_KEY];
      unset($grouped_types[static::UNCATEGORIZED_KEY]);
      $grouped_types[static::UNCATEGORIZED_KEY] = $uncategorized_group;
    }

    foreach ($grouped_types as $category => $group) {
      // Sort them within each group.
      $group = $this->sortTypesByWeight($group);

      // Add the additional info the build.
      foreach ($group as $type) {
        assert($type instanceof NodeType);
        $settings = $type->getThirdPartySettings('type_tray');
        $short_description = [
          '#markup' => $type->getDescription(),
        ];
        $extended_description = [];
        if (!empty($settings['type_description'])) {
          $text_format = $config->get('text_format') ?? 'plain_text';
          $extended_description = [
            '#type' => 'processed_text',
            '#format' => $text_format,
            '#text' => $settings['type_description'],
          ];
        }

        // Prepare a link to add/remove to/from favorites.
        if (in_array($type->id(), $user_favorites)) {
          $favorite_link_text = $this->t('Remove @type from favorites', [
            '@type' => $type->label(),
          ]);
          $favorite_link_url = Url::fromRoute('type_tray.favorites', [
            'type' => $type->id(),
            'op' => 'remove',
          ])->toString();
          $favorite_link_action = 'remove';
        }
        else {
          $favorite_link_text = $this->t('Add @type to favorites', [
            '@type' => $type->label(),
          ]);
          $favorite_link_url = Url::fromRoute('type_tray.favorites', [
            'type' => $type->id(),
            'op' => 'add',
          ])->toString();
          $favorite_link_action = 'add';
        }

        $thumbnail_url = !empty($settings['type_thumbnail']) ? $settings['type_thumbnail'] : $this->moduleList->getPath('type_tray') . static::TYPE_TRAY_DEFAULT_THUMBNAIL_PATH;
        $icon_url = !empty($settings['type_icon']) ? $settings['type_icon'] : $this->moduleList->getPath('type_tray') . static::TYPE_TRAY_DEFAULT_ICON_PATH;

        $build['#items'][$category][$type->id()] = [
          '#theme' => 'type_tray_teaser',
          '#content_type_link' => Link::createFromRoute($type->label(), 'node.add', ['node_type' => $type->id()]),
          '#thumbnail_url' => $this->fileUrlGenerator->generateString($thumbnail_url),
          '#thumbnail_alt' => $this->t('Thumbnail of a @label content type.', [
            '@label' => $type->label(),
          ]),
          '#icon_url' => $this->fileUrlGenerator->generateString($icon_url),
          '#icon_alt' => $this->t('Icon of a @label content type.', [
            '@label' => $type->label(),
          ]),
          '#short_description' => $short_description,
          '#extended_description' => $extended_description,
          '#layout' => $layout,
          '#content_type_entity' => $type,
          '#favorite_link_text' => $favorite_link_text,
          '#favorite_link_url' => $favorite_link_url,
          '#favorite_link_action' => $favorite_link_action,
        ];
        if (!empty($settings['existing_nodes_link_text'])) {
          // This avoids having site builders rely on Config Translation to
          // make this translatable. Not ideal, but worth in this case.
          // @codingStandardsIgnoreLine
          $all_nodes_label = $this->t(Xss::filterAdmin($settings['existing_nodes_link_text']));

          // Remove scripts/style from the label.
          $build['#items'][$category][$type->id()]['#nodes_by_type_link'] = Link::createFromRoute(
            $all_nodes_label,
            'system.admin_content',
            [],
            [
              'query' => [
                'type' => $type->id(),
              ],
            ]
          );
        }

        CacheableMetadata::createFromObject($type)
          ->addCacheContexts(['user'])
          ->applyTo($build['#items'][$category][$type->id()]);
      }
    }

    return $build;
  }

  /**
   * Helper to check the weight on each type and sort by them.
   *
   * @param \Drupal\node\Entity\NodeType[] $types
   *   An indexed array of node types to sort. Note that if an associative
   *   array is passed in, the keys will be lost.
   *
   * @return \Drupal\node\Entity\NodeType[]
   *   The same array passed in, but sorted by the 'type_weight' third-party
   *   setting. Will use weight=0 if no value is defined for this setting.
   */
  private function sortTypesByWeight(array $types) {
    $items = [];
    foreach ($types as $type) {
      assert($type instanceof NodeType);
      $items[] = [
        'type' => $type,
        'weight' => $type->getThirdPartySetting('type_tray', 'type_weight', 0),
      ];
    }
    uasort($items, [SortArray::class, 'sortByWeightElement']);
    return array_map(function ($item) {
      return $item['type'];
    }, $items);
  }

  /**
   * Returns categories used to classify and group content types.
   *
   * @return array|string
   *   An associative array where keys are category names, and values their
   *   user-facing labels.
   */
  public static function getTypeTrayCategories() {
    $config = \Drupal::config('type_tray.settings');
    $fallback_label = $config->get('fallback_label') ?? static::UNCATEGORIZED_LABEL;
    $categories = $config->get('categories') ?? [];
    // If there is at least one type marked as favorite for this user, add it
    // as an available category.
    $user_favorites = static::getUserFavorites();
    if (!empty($user_favorites)) {
      $categories = [static::FAVORITES_KEY => static::FAVORITES_LABEL] + $categories;
    }
    // Add the fallback, and pass all labels through t() to make them available
    // to be translated through interface translation.
    $categories = $categories + [static::UNCATEGORIZED_KEY => $fallback_label];
    foreach ($categories as $key => &$category_label) {
      // @codingStandardsIgnoreLine
      $category_label = t($category_label);
    }
    return $categories;
  }

  /**
   * Helper to retrieve the types marked as favorites by the current user.
   *
   * @return string[]
   *   A list of type machine names that the current user marked as favorites.
   */
  public static function getUserFavorites() {
    $collection = \Drupal::keyValue('type_tray_favorites');
    $user_favorites = $collection->get(\Drupal::currentUser()->id()) ?? [];
    return array_keys(array_filter($user_favorites));
  }

  /**
   * Get the category key/label for a given content type.
   *
   * @param string $type_id
   *   The content type machine name.
   *
   * @return array
   *   A single-item associative array, where the key is the category key, and
   *   the value is the user-facing category label.
   */
  public static function getCategory($type_id) {
    $categories = static::getTypeTrayCategories();
    $uncategorized = [static::UNCATEGORIZED_KEY => $categories[static::UNCATEGORIZED_KEY]];

    /** @var \Drupal\node\Entity\NodeType $type */
    $type = \Drupal::entityTypeManager()
      ->getStorage('node_type')
      ->load($type_id);
    if (empty($type)) {
      return $uncategorized;
    }

    // Check if there's one chosen by an admin.
    $category = $type->getThirdPartySetting('type_tray', 'type_category');
    if (empty($category) || empty($categories[$category])) {
      return $uncategorized;
    }

    return [$category => $categories[$category]];
  }

  /**
   * Callback to add/remove a type from a user's favorites list.
   *
   * @param string $type
   *   The machine name of the content type being (un)favorited.
   * @param string $op
   *   Either 'add' or 'remove', indicating to add or remove the type from
   *   the favorites list.
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request object.
   */
  public function processFavorites($type, $op, Request $request) {
    if (!in_array($op, ['add', 'remove'], TRUE)) {
      throw new NotFoundHttpException();
    }

    // Only allow dealing with types that exist and this user can access.
    $type = $this->entityTypeManager()->getStorage('node_type')->load($type);
    if (empty($type)) {
      throw new NotFoundHttpException();
    }
    $access = $this->entityTypeManager()->getAccessControlHandler('node')->createAccess($type->id(), NULL, [], TRUE);
    if (!$access->isAllowed()) {
      throw new NotFoundHttpException();
    }

    $collection = $this->keyValue('type_tray_favorites');
    $uid = $this->currentUser()->id();
    $user_favorites = $collection->get($uid) ?? [];
    // The value in the collection is an associative array where keys are type
    // machine names, and values are a boolean indicating whether it has been
    // favorited or not.
    $user_favorites[$type->id()] = $op === 'add' ? TRUE : FALSE;
    $collection->set($uid, $user_favorites);
    $this->cacheTagsInvalidator->invalidateTags([
      'config:node_type_list',
    ]);

    // Send the user back to the Type Tray page.
    return new RedirectResponse(Url::fromRoute('node.add_page')->setAbsolute()->toString());
  }

}

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

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