Loading core/assets/schemas/v1/icon_pack.schema.json 0 → 100644 +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 core/core.services.yml +40 −0 Original line number Diff line number Diff line Loading @@ -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: Loading Loading @@ -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 } core/lib/Drupal/Core/Render/Element/Icon.php 0 → 100644 +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; } } core/lib/Drupal/Core/Template/IconsTwigExtension.php 0 → 100644 +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, ]; } } core/lib/Drupal/Core/Theme/Icon/Attribute/IconExtractor.php 0 → 100644 +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
core/assets/schemas/v1/icon_pack.schema.json 0 → 100644 +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
core/core.services.yml +40 −0 Original line number Diff line number Diff line Loading @@ -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: Loading Loading @@ -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 }
core/lib/Drupal/Core/Render/Element/Icon.php 0 → 100644 +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; } }
core/lib/Drupal/Core/Template/IconsTwigExtension.php 0 → 100644 +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, ]; } }
core/lib/Drupal/Core/Theme/Icon/Attribute/IconExtractor.php 0 → 100644 +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 = [], ) {} }