From d418da3968bd5959bece259103822fbd6d16bfac Mon Sep 17 00:00:00 2001
From: Lauri Eskola <lauri.eskola@acquia.com>
Date: Thu, 24 Aug 2023 14:21:50 +0300
Subject: [PATCH] Issue #3372092 by srishtiiee, lauriii, larowlan,
 tim.plunkett, kunal.sachdev, Wim Leers: Allow field_type_categories.yml
 entries to define asset libraries

---
 .../Core/Field/FallbackFieldTypeCategory.php  |  7 ++-
 .../Drupal/Core/Field/FieldTypeCategory.php   |  7 +++
 .../Core/Field/FieldTypeCategoryInterface.php |  8 +++
 .../Core/Field/FieldTypeCategoryManager.php   |  4 ++
 core/modules/comment/comment.module           | 11 +++-
 .../datetime_range/datetime_range.module      | 11 +++-
 ...eld_plugins_test.field_type_categories.yml |  2 +
 .../Kernel/FieldTypeCategoryDiscoveryTest.php |  2 +
 .../field_ui/src/Form/FieldStorageAddForm.php |  7 ++-
 .../FieldTypeCategoriesIntegrationTest.php    | 60 +++++++++++++++++++
 .../file/file.field_type_categories.yml       |  2 +
 core/modules/file/file.module                 |  7 ---
 core/modules/link/link.module                 | 11 +++-
 core/modules/media/media.module               | 10 +++-
 .../options/options.field_type_categories.yml |  2 +
 core/modules/options/options.module           |  7 ---
 core/modules/telephone/telephone.module       | 11 +++-
 .../text/text.field_type_categories.yml       |  2 +
 core/modules/text/text.module                 |  7 ---
 19 files changed, 138 insertions(+), 40 deletions(-)
 create mode 100644 core/modules/field_ui/tests/src/Functional/FieldTypeCategoriesIntegrationTest.php

diff --git a/core/lib/Drupal/Core/Field/FallbackFieldTypeCategory.php b/core/lib/Drupal/Core/Field/FallbackFieldTypeCategory.php
index 1a83aa073740..d8319d28d501 100644
--- a/core/lib/Drupal/Core/Field/FallbackFieldTypeCategory.php
+++ b/core/lib/Drupal/Core/Field/FallbackFieldTypeCategory.php
@@ -10,13 +10,14 @@ class FallbackFieldTypeCategory extends FieldTypeCategory {
   /**
    * {@inheritdoc}
    */
-  public function __construct(array $configuration) {
+  public function __construct(array $configuration, string $plugin_id, array $plugin_definition) {
+    $plugin_id = $configuration['unique_identifier'];
     $plugin_definition = [
       'label' => $configuration['label'] ?? '',
       'description' => $configuration['description'] ?? '',
       'weight' => $configuration['weight'] ?? 0,
-    ];
-    parent::__construct($configuration, $configuration['unique_identifier'], $plugin_definition);
+    ] + $plugin_definition;
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
   }
 
 }
diff --git a/core/lib/Drupal/Core/Field/FieldTypeCategory.php b/core/lib/Drupal/Core/Field/FieldTypeCategory.php
index ac04b5124947..168616102e1b 100644
--- a/core/lib/Drupal/Core/Field/FieldTypeCategory.php
+++ b/core/lib/Drupal/Core/Field/FieldTypeCategory.php
@@ -33,4 +33,11 @@ public function getWeight(): int {
     return $this->pluginDefinition['weight'];
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getLibraries(): array {
+    return $this->pluginDefinition['libraries'] ?? [];
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Field/FieldTypeCategoryInterface.php b/core/lib/Drupal/Core/Field/FieldTypeCategoryInterface.php
index e9a1a0d21a85..3727df1bb778 100644
--- a/core/lib/Drupal/Core/Field/FieldTypeCategoryInterface.php
+++ b/core/lib/Drupal/Core/Field/FieldTypeCategoryInterface.php
@@ -33,4 +33,12 @@ public function getDescription(): TranslatableMarkup;
    */
   public function getWeight(): int;
 
+  /**
+   * Returns asset libraries for the field group.
+   *
+   * @return array
+   *   The asset libraries to attach.
+   */
+  public function getLibraries(): array;
+
 }
diff --git a/core/lib/Drupal/Core/Field/FieldTypeCategoryManager.php b/core/lib/Drupal/Core/Field/FieldTypeCategoryManager.php
index 29efa654063d..7155545030b4 100644
--- a/core/lib/Drupal/Core/Field/FieldTypeCategoryManager.php
+++ b/core/lib/Drupal/Core/Field/FieldTypeCategoryManager.php
@@ -19,6 +19,8 @@
  *     label: STRING
  *     description: STRING
  *     weight: INTEGER
+ *     libraries:
+ *       - STRING
  * @endcode
  * For example:
  * @code
@@ -26,6 +28,8 @@
  *   label: Text
  *   description: Text fields.
  *   weight: 2
+ *   libraries:
+ *     - module_name/library_name
  * @endcode
  *
  * @see \Drupal\Core\Field\FieldTypeCategoryInterface
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index 8caa332efca4..934f0bdbe2ff 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -16,6 +16,7 @@
 use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
 use Drupal\Core\Entity\Entity\EntityViewMode;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Field\FieldTypeCategoryManagerInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
@@ -785,8 +786,12 @@ function comment_entity_view_display_presave(EntityViewDisplayInterface $display
 }
 
 /**
- * Implements hook_preprocess_form_element__new_storage_type().
+ * Implements hook_field_type_category_info_alter().
  */
-function comment_preprocess_form_element__new_storage_type(&$variables) {
-  $variables['#attached']['library'][] = 'comment/drupal.comment-icon';
+function comment_field_type_category_info_alter(&$definitions) {
+  // TRICKY: the `comment` field type belongs in the `general` category, so the
+  // libraries need to be attached using an alter hook.
+  if (array_key_exists(FieldTypeCategoryManagerInterface::FALLBACK_CATEGORY, $definitions)) {
+    $definitions[FieldTypeCategoryManagerInterface::FALLBACK_CATEGORY]['libraries'][] = 'comment/drupal.comment-icon';
+  }
 }
diff --git a/core/modules/datetime_range/datetime_range.module b/core/modules/datetime_range/datetime_range.module
index 65a9e692dff9..37283d67a208 100644
--- a/core/modules/datetime_range/datetime_range.module
+++ b/core/modules/datetime_range/datetime_range.module
@@ -5,6 +5,7 @@
  * Field hooks to implement a datetime field that stores a start and end date.
  */
 
+use Drupal\Core\Field\FieldTypeCategoryManagerInterface;
 use Drupal\Core\Url;
 use Drupal\Core\Routing\RouteMatchInterface;
 
@@ -29,8 +30,12 @@ function datetime_range_help($route_name, RouteMatchInterface $route_match) {
 }
 
 /**
- * Implements hook_preprocess_form_element__new_storage_type().
+ * Implements hook_field_type_category_info_alter().
  */
-function datetime_range_preprocess_form_element__new_storage_type(&$variables) {
-  $variables['#attached']['library'][] = 'datetime_range/drupal.datetime_range-icon';
+function datetime_range_field_type_category_info_alter(&$definitions) {
+  // TRICKY: the `datetime_range` field type belongs in the `general` category,
+  // so the libraries need to be attached using an alter hook.
+  if (array_key_exists(FieldTypeCategoryManagerInterface::FALLBACK_CATEGORY, $definitions)) {
+    $definitions[FieldTypeCategoryManagerInterface::FALLBACK_CATEGORY]['libraries'][] = 'datetime_range/drupal.datetime_range-icon';
+  }
 }
diff --git a/core/modules/field/tests/modules/field_plugins_test/field_plugins_test.field_type_categories.yml b/core/modules/field/tests/modules/field_plugins_test/field_plugins_test.field_type_categories.yml
index 35b725c68630..fe3b94f826fe 100644
--- a/core/modules/field/tests/modules/field_plugins_test/field_plugins_test.field_type_categories.yml
+++ b/core/modules/field/tests/modules/field_plugins_test/field_plugins_test.field_type_categories.yml
@@ -2,3 +2,5 @@ test_category:
   label: 'Test category'
   description: 'This is a test field type category.'
   weight: -10
+  libraries:
+    - field_plugins_test/test_library
diff --git a/core/modules/field/tests/src/Kernel/FieldTypeCategoryDiscoveryTest.php b/core/modules/field/tests/src/Kernel/FieldTypeCategoryDiscoveryTest.php
index 0dbf670fdcbe..22fa2ef738c2 100644
--- a/core/modules/field/tests/src/Kernel/FieldTypeCategoryDiscoveryTest.php
+++ b/core/modules/field/tests/src/Kernel/FieldTypeCategoryDiscoveryTest.php
@@ -29,12 +29,14 @@ public function testFieldTypeCategories() {
       'Test category',
       'This is a test field type category.',
       -10,
+      ['field_plugins_test/test_library'],
     ];
 
     $this->assertSame($expected, [
       (string) $category->getLabel(),
       (string) $category->getDescription(),
       $category->getWeight(),
+      $category->getLibraries(),
     ]);
   }
 
diff --git a/core/modules/field_ui/src/Form/FieldStorageAddForm.php b/core/modules/field_ui/src/Form/FieldStorageAddForm.php
index ed591d3ae03e..097af8b1111e 100644
--- a/core/modules/field_ui/src/Form/FieldStorageAddForm.php
+++ b/core/modules/field_ui/src/Form/FieldStorageAddForm.php
@@ -157,12 +157,13 @@ public function buildForm(array $form, FormStateInterface $form_state, $entity_t
 
     $field_type_options = $unique_definitions = [];
     $grouped_definitions = $this->fieldTypePluginManager->getGroupedDefinitions($this->fieldTypePluginManager->getUiDefinitions(), 'label', 'id');
+    $category_definitions = $this->fieldTypeCategoryManager->getDefinitions();
     // Invoke a hook to get category properties.
     foreach ($grouped_definitions as $category => $field_types) {
       foreach ($field_types as $name => $field_type) {
         $unique_definitions[$category][$name] = ['unique_identifier' => $name] + $field_type;
         if ($this->fieldTypeCategoryManager->hasDefinition($category)) {
-          $category_plugin = $this->fieldTypeCategoryManager->createInstance($category, $unique_definitions[$category][$name]);
+          $category_plugin = $this->fieldTypeCategoryManager->createInstance($category, $unique_definitions[$category][$name], $category_definitions[$category]);
           $field_type_options[$category_plugin->getPluginId()] = ['unique_identifier' => $name] + $field_type;
         }
         else {
@@ -243,6 +244,10 @@ public function buildForm(array $form, FormStateInterface $form_state, $entity_t
           '#variant' => 'field-option',
         ],
       ];
+
+      if ($libraries = $category_info->getLibraries()) {
+        $field_type_options_radios[$id]['#attached']['library'] = $libraries;
+      }
     }
     uasort($field_type_options_radios, [SortArray::class, 'sortByWeightProperty']);
     $form['add']['new_storage_type'] = $field_type_options_radios;
diff --git a/core/modules/field_ui/tests/src/Functional/FieldTypeCategoriesIntegrationTest.php b/core/modules/field_ui/tests/src/Functional/FieldTypeCategoriesIntegrationTest.php
new file mode 100644
index 000000000000..1490c2e8410b
--- /dev/null
+++ b/core/modules/field_ui/tests/src/Functional/FieldTypeCategoriesIntegrationTest.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Drupal\Tests\field_ui\Functional;
+
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests field UI integration with field type categories for loading libraries.
+ *
+ * @group field_ui
+ */
+class FieldTypeCategoriesIntegrationTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'node',
+    'file',
+    'field_ui',
+    'options',
+    'comment',
+    'link',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    // Create a test user.
+    $admin_user = $this->drupalCreateUser(['administer node fields']);
+    $this->drupalLogin($admin_user);
+  }
+
+  /**
+   * Tests if the libraries are loaded on FieldStorageAddForm.
+   */
+  public function testLibrariesLoaded() {
+    $this->drupalGet('admin/structure/types/manage/' . $this->drupalCreateContentType()->id() . '/fields/add-field');
+    $page_content = $this->getSession()->getPage()->getContent();
+    $css_libraries = [
+      'drupal.file-icon',
+      'drupal.text-icon',
+      'drupal.options-icon',
+      'drupal.comment-icon',
+      'drupal.link-icon',
+    ];
+    foreach ($css_libraries as $css_library) {
+      // Check if the library asset is present in the rendered HTML.
+      $this->assertStringContainsString($css_library, $page_content);
+    }
+  }
+
+}
diff --git a/core/modules/file/file.field_type_categories.yml b/core/modules/file/file.field_type_categories.yml
index efb40e7eeb03..8f96134e8f4e 100644
--- a/core/modules/file/file.field_type_categories.yml
+++ b/core/modules/file/file.field_type_categories.yml
@@ -2,3 +2,5 @@ file_upload:
   label: 'File upload'
   description: 'Field to upload any type of files.'
   weight: -15
+  libraries:
+    - file/drupal.file-icon
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index 220ec88bc47e..34b00de5c680 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -1552,10 +1552,3 @@ function file_field_find_file_reference_column(FieldDefinitionInterface $field)
   }
   return FALSE;
 }
-
-/**
- * Implements hook_preprocess_form_element__new_storage_type().
- */
-function file_preprocess_form_element__new_storage_type(&$variables) {
-  $variables['#attached']['library'][] = 'file/drupal.file-icon';
-}
diff --git a/core/modules/link/link.module b/core/modules/link/link.module
index 590c01cc91c4..f05fe1c23817 100644
--- a/core/modules/link/link.module
+++ b/core/modules/link/link.module
@@ -5,6 +5,7 @@
  * Defines simple link field types.
  */
 
+use Drupal\Core\Field\FieldTypeCategoryManagerInterface;
 use Drupal\Core\Link;
 use Drupal\Core\Url;
 use Drupal\Core\Routing\RouteMatchInterface;
@@ -67,8 +68,12 @@ function template_preprocess_link_formatter_link_separate(&$variables) {
 }
 
 /**
- * Implements hook_preprocess_form_element__new_storage_type().
+ * Implements hook_field_type_category_info_alter().
  */
-function link_preprocess_form_element__new_storage_type(&$variables) {
-  $variables['#attached']['library'][] = 'link/drupal.link-icon';
+function link_field_type_category_info_alter(&$definitions) {
+  // TRICKY: the `link` field type belongs in the `general` category, so the
+  // libraries need to be attached using an alter hook.
+  if (array_key_exists(FieldTypeCategoryManagerInterface::FALLBACK_CATEGORY, $definitions)) {
+    $definitions[FieldTypeCategoryManagerInterface::FALLBACK_CATEGORY]['libraries'][] = 'link/drupal.link-icon';
+  }
 }
diff --git a/core/modules/media/media.module b/core/modules/media/media.module
index 962925315898..f297c3e28581 100644
--- a/core/modules/media/media.module
+++ b/core/modules/media/media.module
@@ -534,8 +534,12 @@ function media_views_query_substitutions(ViewExecutable $view) {
 }
 
 /**
- * Implements hook_preprocess_form_element__new_storage_type().
+ * Implements hook_field_type_category_info_alter().
  */
-function media_preprocess_form_element__new_storage_type(&$variables) {
-  $variables['#attached']['library'][] = 'media/drupal.media-icon';
+function media_field_type_category_info_alter(&$definitions) {
+  // TRICKY: the `media` field type belongs in the `general` category, so the
+  // libraries need to be attached using an alter hook.
+  if (array_key_exists(FieldTypeCategoryManagerInterface::FALLBACK_CATEGORY, $definitions)) {
+    $definitions[FieldTypeCategoryManagerInterface::FALLBACK_CATEGORY]['libraries'][] = 'media/drupal.media-icon';
+  }
 }
diff --git a/core/modules/options/options.field_type_categories.yml b/core/modules/options/options.field_type_categories.yml
index 9c23ff067057..339cc386c823 100644
--- a/core/modules/options/options.field_type_categories.yml
+++ b/core/modules/options/options.field_type_categories.yml
@@ -2,3 +2,5 @@ selection_list:
   label: 'Selection list'
   description: 'Field to select from predefined options.'
   weight: -15
+  libraries:
+    - options/drupal.options-icon
diff --git a/core/modules/options/options.module b/core/modules/options/options.module
index 2feed0daf806..608f4bcc6586 100644
--- a/core/modules/options/options.module
+++ b/core/modules/options/options.module
@@ -155,10 +155,3 @@ function options_form_field_storage_config_edit_form_alter(&$form, FormStateInte
   $form['#attached']['library'][] = 'field_ui/drupal.field_ui';
   $table['#attributes']['class'][] = 'allowed-values-table';
 }
-
-/**
- * Implements hook_preprocess_form_element__new_storage_type().
- */
-function options_preprocess_form_element__new_storage_type(&$variables) {
-  $variables['#attached']['library'][] = 'options/drupal.options-icon';
-}
diff --git a/core/modules/telephone/telephone.module b/core/modules/telephone/telephone.module
index c89d8bd83fd9..34899b47283b 100644
--- a/core/modules/telephone/telephone.module
+++ b/core/modules/telephone/telephone.module
@@ -5,6 +5,7 @@
  * Defines a simple telephone number field type.
  */
 
+use Drupal\Core\Field\FieldTypeCategoryManagerInterface;
 use Drupal\Core\Url;
 use Drupal\Core\Routing\RouteMatchInterface;
 
@@ -36,8 +37,12 @@ function telephone_field_formatter_info_alter(&$info) {
 }
 
 /**
- * Implements hook_preprocess_form_element__new_storage_type().
+ * Implements hook_field_type_category_info_alter().
  */
-function telephone_preprocess_form_element__new_storage_type(&$variables) {
-  $variables['#attached']['library'][] = 'telephone/drupal.telephone-icon';
+function telephone_field_type_category_info_alter(&$definitions) {
+  // TRICKY: the `telephone` field type belongs in the `general` category, so
+  // the libraries need to be attached using an alter hook.
+  if (array_key_exists(FieldTypeCategoryManagerInterface::FALLBACK_CATEGORY, $definitions)) {
+    $definitions[FieldTypeCategoryManagerInterface::FALLBACK_CATEGORY]['libraries'][] = 'telephone/drupal.telephone-icon';
+  }
 }
diff --git a/core/modules/text/text.field_type_categories.yml b/core/modules/text/text.field_type_categories.yml
index 787cfc979572..c819e4b83bc1 100644
--- a/core/modules/text/text.field_type_categories.yml
+++ b/core/modules/text/text.field_type_categories.yml
@@ -2,3 +2,5 @@ formatted_text:
   label: 'Formatted text'
   description: 'Text field with markup support and optional editor.'
   weight: -45
+  libraries:
+    - text/drupal.text-icon
diff --git a/core/modules/text/text.module b/core/modules/text/text.module
index 54f513281d4f..c0a03c62f978 100644
--- a/core/modules/text/text.module
+++ b/core/modules/text/text.module
@@ -164,10 +164,3 @@ function text_summary($text, $format = NULL, $size = NULL) {
 
   return $summary;
 }
-
-/**
- * Implements hook_preprocess_form_element__new_storage_type().
- */
-function text_preprocess_form_element__new_storage_type(&$variables) {
-  $variables['#attached']['library'][] = 'text/drupal.text-icon';
-}
-- 
GitLab