Commit 95350475 authored by catch's avatar catch
Browse files

Issue #3471494 by mogtofu33, catch, pdureau, nod_, larowlan, andypost, mxh,...

Issue #3471494 by mogtofu33, catch, pdureau, nod_, larowlan, andypost, mxh, grimreaper, ckrina, quietone: Add an icon management API

(cherry picked from commit ed6e9292)
parent fb2c558d
Loading
Loading
Loading
Loading
Loading
+99 −0
Original line number Diff line number Diff line
{
  "type": "object",
  "required": [
    "extractor",
    "template"
  ],
  "properties": {
    "label": {
      "title": "Label",
      "description": "Translatable label",
      "type": "string"
    },
    "description": {
      "title": "Description",
      "description": "Translatable description",
      "type": "string"
    },
    "enabled": {
      "type": "boolean"
    },
    "version": {
      "title": "Version",
      "type": "string"
    },
    "links": {
      "title": "External links",
      "description": "Both compact and full syntaxes are available",
      "type": "array",
      "items": {
        "anyOf": [
          {
            "type": "string",
            "format": "iri-reference"
          },
          {
            "type": "object",
            "properties": {
              "title": {
                "type": "string"
              },
              "url": {
                "type": "string",
                "format": "iri-reference"
              }
            }
          }
        ]
      }
    },
    "license": {
      "title": "License",
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        },
        "url": {
          "type": "string",
          "format": "iri-reference"
        },
        "gpl-compatible": {
          "type": "boolean"
        }
      }
    },
    "extractor": {
      "title": "Extractor ID",
      "description": "The plugin ID of the extractor",
      "type": "string",
      "pattern": "^[A-Za-z]+\\w*$"
    },
    "config": {
      "title": "Extractor configuration",
      "description": "The structure is specific to the extractor plugin",
      "type": "object"
    },
    "settings": {
      "title": "Settings",
      "description": "Used to build the settings form. Each setting is a JSON schema",
      "type": "object",
      "patternProperties": {
        "^\\w+$": {
          "$ref": "http://json-schema.org/draft-04/schema#"
        }
      },
      "additionalProperties": false
    },
    "template": {
      "title": "Twig template",
      "type": "string"
    },
    "library": {
      "title": "Asset library",
      "description": "The ID of an asset library",
      "type": "string",
      "pattern": "^\\w+/[A-Za-z]+\\w*$"
    }
  }
}
 No newline at end of file
+40 −0
Original line number Diff line number Diff line
@@ -1720,6 +1720,10 @@ services:
    arguments: ['@plugin.manager.sdc', '@Drupal\Core\Theme\Component\ComponentValidator']
    tags:
      - { name: twig.extension, priority: 101 }
  Drupal\Core\Template\IconsTwigExtension:
    arguments: ['@plugin.manager.icon_pack']
    tags:
      - { name: twig.extension, priority: 101 }
  twig.extension.debug:
    class: Twig\Extension\DebugExtension
    tags:
@@ -1884,3 +1888,39 @@ services:
    tags:
      - { name: twig.loader, priority: 5 }
  Drupal\Core\EventSubscriber\CsrfExceptionSubscriber: ~
  plugin.manager.icon_pack:
    class: Drupal\Core\Theme\Icon\Plugin\IconPackManager
    calls:
      - [setValidator, []]
    arguments:
      [
        '@module_handler',
        '@theme_handler',
        '@cache.discovery',
        '@plugin.manager.icon_extractor',
        '@Drupal\Core\Theme\Icon\IconCollector',
        '%app.root%',
      ]
  Drupal\Core\Theme\Icon\Plugin\IconPackManagerInterface: '@plugin.manager.icon_pack'
  plugin.manager.icon_extractor:
    class: Drupal\Core\Theme\Icon\IconExtractorPluginManager
    parent: default_plugin_manager
    arguments: ['@plugin_form.factory']
  Drupal\Core\Theme\Icon\IconExtractorInterface: '@plugin.manager.icon_extractor'
  Drupal\Core\Theme\Icon\IconFinder:
    autowire: true
    arguments:
      [
        '@file_url_generator',
        '@logger.channel.default',
        '%app.root%',
      ]
  Drupal\Core\Theme\Icon\IconCollector:
    arguments:
      [
        '@plugin.manager.icon_extractor',
        '@cache.default',
        '@lock',
      ]
    tags:
      - { name: needs_destruction }
+102 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Core\Render\Element;

use Drupal\Core\Render\Attribute\RenderElement;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Theme\Icon\IconDefinition;

/**
 * Provides a render element to display an icon.
 *
 * Properties:
 * - #pack_id: (string) Icon Pack provider plugin id.
 * - #icon_id: (string) Name of the icon.
 * - #settings: (array) Settings sent to the inline Twig template.
 *
 * Usage Example:
 * @code
 * $build['icon'] = [
 *   '#type' => 'icon',
 *   '#pack_id' => 'material_symbols',
 *   '#icon_id' => 'home',
 *   '#settings' => [
 *     'width' => 64,
 *   ],
 * ];
 * @endcode
 *
 * @internal
 */
#[RenderElement('icon')]
class Icon extends RenderElementBase {

  /**
   * {@inheritdoc}
   */
  public function getInfo(): array {
    return [
      '#pre_render' => [
        [self::class, 'preRenderIcon'],
      ],
      '#pack_id' => '',
      '#icon_id' => '',
      '#settings' => [],
    ];
  }

  /**
   * Icon element pre render callback.
   *
   * @param array $element
   *   An associative array containing the properties of the icon element.
   *
   * @return array
   *   The modified element.
   */
  public static function preRenderIcon(array $element): array {
    $icon_full_id = IconDefinition::createIconId($element['#pack_id'], $element['#icon_id']);

    $pluginManagerIconPack = \Drupal::service('plugin.manager.icon_pack');
    if (!$icon = $pluginManagerIconPack->getIcon($icon_full_id)) {
      return $element;
    }

    // Build context minimal values as icon_id, optional source and attributes.
    $context = [
      'icon_id' => $icon->getIconId(),
    ];
    // Better to not have source value if not set for the template.
    if ($source = $icon->getSource()) {
      $context['source'] = $source;
    }
    // Silently ensure settings is an array.
    if (!is_array($element['#settings'])) {
      $element['#settings'] = [];
    }

    $extractor_data = $icon->getAllData();
    // Inject attributes variable if not created by the extractor.
    if (!isset($extractor_data['attributes'])) {
      $extractor_data['attributes'] = new Attribute();
    }

    $element['inline-template'] = [
      '#type' => 'inline_template',
      '#template' => $icon->getTemplate(),
      // Context include data from extractor and settings, priority on settings
      // from this element. Context as last value to be sure nothing override
      // icon_id or source if set.
      '#context' => array_merge($extractor_data, $element['#settings'], $context),
    ];

    if ($library = $icon->getLibrary()) {
      $element['inline-template']['#attached'] = ['library' => [$library]];
    }

    return $element;
  }

}
+52 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Core\Template;

use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;

/**
 * Twig extension for icon.
 *
 * @internal
 */
final class IconsTwigExtension extends AbstractExtension {

  /**
   * {@inheritdoc}
   */
  public function getFunctions(): array {
    return [
      new TwigFunction('icon', [$this, 'getIconRenderable']),
    ];
  }

  /**
   * Get an icon renderable array.
   *
   * @param string|null $pack_id
   *   The icon set ID.
   * @param string|null $icon_id
   *   The icon ID.
   * @param array|null $settings
   *   An array of settings for the icon.
   *
   * @return array
   *   The icon renderable.
   */
  public function getIconRenderable(?string $pack_id, ?string $icon_id, ?array $settings = []): array {
    if (!$pack_id || !$icon_id) {
      return [];
    }

    return [
      '#type' => 'icon',
      '#pack_id' => $pack_id,
      '#icon_id' => $icon_id,
      '#settings' => $settings,
    ];
  }

}
+42 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Core\Theme\Icon\Attribute;

use Drupal\Component\Plugin\Attribute\AttributeBase;
use Drupal\Core\StringTranslation\TranslatableMarkup;

/**
 * The icon_extractor attribute.
 *
 * @internal
 *   This API is experimental.
 */
#[\Attribute(\Attribute::TARGET_CLASS)]
class IconExtractor extends AttributeBase {

  /**
   * Constructs a new IconExtractor instance.
   *
   * @param string $id
   *   The plugin ID.
   * @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $label
   *   (optional) The human-readable name of the plugin.
   * @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $description
   *   (optional) A brief description of the plugin.
   * @param class-string|null $deriver
   *   (optional) The deriver class.
   * @param string[] $forms
   *   (optional) An array of form class names keyed by a string used as name
   *   for operation when using \Drupal\Core\Plugin\PluginWithFormsTrait.
   */
  public function __construct(
    public readonly string $id,
    public readonly ?TranslatableMarkup $label,
    public readonly ?TranslatableMarkup $description = NULL,
    public readonly ?string $deriver = NULL,
    public readonly array $forms = [],
  ) {}

}
Loading