class IconPackManager

Defines an icon pack plugin manager to deal with icons.

An extension can define an icon pack in an EXTENSION_NAME.icons.yml file contained in the extension's base directory. Each icon pack must have an `extractor` and `template` property. An optional `config` property can be required based on the value of the `extractor` property.


example_pack:
  extractor: (string) Plugin ID of the IconExtractor. Provided extractors are
    `path`, `svg`, and `svg_sprite`. Contributed modules can provide more
    extractors for different use cases. These extractors will use the value
    of `config: sources` below to discover the icons and build the icon list
    for this icon pack.
  template: (string) Twig template to render the icon and the icon values
    available in the template:
    - `icon_id`: Icon ID based on filename or {icon_id} pattern.
    - `source`: Icon path or URL relative to the extension or to the Drupal
        root.
    - all specific values from the extractor plugin, extractor `svg` will
        provide a `content` variable with the SVG icon code without <svg>
        wrapper.
    - all `settings` values if set below based on keys.
  config:
    sources: (array) Mandatory for extractors: `path`, `svg`, `svg_sprite`.
     This list of paths or URLs is used to generate the list of icons for
     this pack. Extractors `path` and `svg_sprite` allow remote files, while
    `svg` does not allow remote files for security concerns, as the extractor
     generates a variable with the content of the SVG file. Examples of
     values:
      - path/to/relative/*.svg
      - path/to/relative/{icon_id}-suffix.svg # Extract icon id.
      - /path/relative/drupal/root/*.svg # For icons in /libraries.
      - http://www.my_domain.com/my_icon.png
      - ...
    # ... Other keys for specific or contributed extractor plugins.

  # Recommended values:
  label: (string) The name of the icon pack for display

  # Optional values:
  description: (string) The description of the icon pack for display.
  license:
    name: (string) A System Package Data Exchange (SPDX) license identifier
      such as "GPL-2.0-or-later" (see https://spdx.org/licenses/), or if
      not applicable, the human-readable name of the license.
    url: (string) The URL of the license information for the version
      of the library used.
    gpl-compatible: A Boolean for whether this library is GPL compatible.
  links: (array)
    - (string) The URL of a Documentation page.
    - ...
  version: (string) The version of the icon pack.
  enabled: (boolean) Set FALSE to disable the icon pack discovering process.
    Definition will not be populated with icons. Defaults to TRUE.
  preview: (string) Optional Twig template for previewing icons in the admin
    backend when the standard template does not support proper icon display.
    This is particularly useful for font-based icon packs that use a format
    like <i class="..">. By default, the admin preview relies on the
    <img src=""> template. This feature aids contrib module implementations
    that integrate icons with the Field API or CKEditor when a preview is
    necessary.
  library: (string) Drupal library machine name to include.

  # Optional values for the template; they must follow JSON Schema and can
  # only be non-scalar primitives.
  # A specific class \Drupal\Core\Theme\Icon\IconExtractorSettingsForm
  # will transform these settings into Drupal Form API to be available as a
  # form for contributed modules implementing icons for FormElement, Field
  # API, Menu, CKEditor, or other Drupal APIs.
  # Constraints in the form are indicative and will not apply to the values
  # passed to the template; the implementation, for example, a FormElement
  # must enforce the constraints.
  settings: (array)
    FORM_KEY: (string) Name of the setting in the template.
      title : (string) Title of the setting.
      description : (string) Optional description of the setting.
      type : (string) Primitive type: string, number, integer, boolean.
      default: (mixed) Form default value, will not be used as default
        value in the template, template must use |default() twig filter.
      [...] Specific JSON Schema values like multipleOf, minimum, maximum...

For example:


my_icon_pack:
  label: "My icons"
  description: "My UI Icons pack to use everywhere."
  license:
    name: GPL3-or-later
    url: https://www.gnu.org/licenses/gpl-3.0.html
    gpl-compatible: true
  links:
    - https://homepage.com
    - https://homepage.com#usage
  version: 1.0.0
  enabled: true
  extractor: svg
  config:
    sources:
      - icons/{icon_id}.svg
      - icons_grouped/{group}/{icon_id}.svg
  settings:
    size:
      title: "Size"
      type: "integer"
      minimum: 24
      default: 32
  template: >
    <img src={{ source }} width="{{ size|default(32) }}" height="{{ size|default(32) }}"/>
  library: "my_theme/my_lib"

@internal The icon API is experimental and is not meant for production use. See https://www.drupal.org/core/experimental for more information.

Hierarchy

Expanded class hierarchy of IconPackManager

See also

\Drupal\Core\Theme\Icon\IconExtractorInterface

\Drupal\Core\Theme\Icon\IconExtractorWithFinderInterface

\Drupal\Core\Theme\Icon\IconExtractorSettingsForm

Plugin API

1 file declares its use of IconPackManager
IconPackManagerKernelTest.php in core/tests/Drupal/KernelTests/Core/Theme/Icon/IconPackManagerKernelTest.php

File

core/lib/Drupal/Core/Theme/Icon/Plugin/IconPackManager.php, line 146

Namespace

Drupal\Core\Theme\Icon\Plugin
View source
class IconPackManager extends DefaultPluginManager implements IconPackManagerInterface {
    private const SCHEMA_VALIDATE = 'core/assets/schemas/v1/icon_pack.schema.json';
    
    /**
     * The schema validator.
     *
     * This property will only be set if the validator library is available.
     *
     * @var \JsonSchema\Validator|null
     */
    private ?Validator $validator = NULL;
    
    /**
     * Constructs the IconPackPluginManager object.
     *
     * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
     *   The module handler.
     * @param \Drupal\Core\Extension\ThemeHandlerInterface $themeHandler
     *   The theme handler.
     * @param \Drupal\Core\Cache\CacheBackendInterface $cacheBackend
     *   The cache backend.
     * @param \Drupal\Core\Theme\Icon\IconExtractorPluginManager $iconPackExtractorManager
     *   The icon plugin extractor service.
     * @param \Drupal\Core\Theme\Icon\IconCollector $iconCollector
     *   The icon cache collector service.
     * @param string $appRoot
     *   The application root.
     */
    public function __construct(ModuleHandlerInterface $module_handler, ThemeHandlerInterface $themeHandler, CacheBackendInterface $cacheBackend, IconExtractorPluginManager $iconPackExtractorManager, IconCollector $iconCollector, string $appRoot) {
        $this->moduleHandler = $module_handler;
        $this->factory = new ContainerFactory($this);
        $this->alterInfo('icon_pack');
        $this->setCacheBackend($cacheBackend, 'icon_pack', [
            'icon_pack_plugin',
            'icon_pack_collector',
        ]);
    }
    
    /**
     * Sets the validator service if available.
     *
     * @param \JsonSchema\Validator|null $validator
     *   The JSON Validator class.
     */
    public function setValidator(?Validator $validator = NULL) : void {
        if (NULL !== $validator) {
            $this->validator = $validator;
            return;
        }
        if (class_exists(Validator::class)) {
            $this->validator = new Validator();
        }
    }
    
    /**
     * {@inheritdoc}
     */
    public function processDefinition(&$definition, $plugin_id) : void {
        if (preg_match('@[^a-z0-9_]@', $plugin_id)) {
            throw new IconPackConfigErrorException(sprintf('Invalid icon pack id in: %s, name: %s must contain only lowercase letters, numbers, and underscores.', $definition['provider'], $plugin_id));
        }
        $this->validateDefinition($definition);
        // Do not include disabled definition with `enabled: false`.
        if (!($definition['enabled'] ?? TRUE)) {
            return;
        }
        if (!isset($definition['provider'])) {
            return;
        }
        // Provide path information for extractors.
        $relative_path = $this->moduleHandler
            ->moduleExists($definition['provider']) ? $this->moduleHandler
            ->getModule($definition['provider'])
            ->getPath() : $this->themeHandler
            ->getTheme($definition['provider'])
            ->getPath();
        $definition['relative_path'] = $relative_path;
        // To avoid the need for appRoot in extractors.
        $definition['absolute_path'] = sprintf('%s/%s', $this->appRoot, $relative_path);
        // Load all discovered icon ids in the definition so they are cached.
        $definition['icons'] = $this->getIconsFromDefinition($definition);
    }
    
    /**
     * {@inheritdoc}
     */
    public function getIcons(array $allowed_icon_pack = []) : array {
        $definitions = $this->getDefinitions();
        if (NULL === $definitions) {
            return [];
        }
        $icons = [];
        foreach ($definitions as $definition) {
            if ($allowed_icon_pack && !in_array($definition['id'], $allowed_icon_pack, TRUE)) {
                continue;
            }
            $icons = array_merge($icons, $definition['icons'] ?? []);
        }
        return $icons;
    }
    
    /**
     * {@inheritdoc}
     */
    public function getIcon(string $icon_full_id) : ?IconDefinitionInterface {
        return $this->iconCollector
            ->get($icon_full_id, $this->getDefinitions());
    }
    
    /**
     * {@inheritdoc}
     */
    public function getExtractorFormDefaults(string $pack_id) : array {
        $icon_pack_definitions = $this->getDefinitions();
        if (!isset($icon_pack_definitions[$pack_id]) || !isset($icon_pack_definitions[$pack_id]['settings'])) {
            return [];
        }
        $default = [];
        foreach ($icon_pack_definitions[$pack_id]['settings'] as $name => $definition) {
            if (isset($definition['default'])) {
                $default[$name] = $definition['default'];
            }
        }
        return $default;
    }
    
    /**
     * {@inheritdoc}
     */
    public function getExtractorPluginForms(array &$form, FormStateInterface $form_state, array $default_settings = [], array $allowed_icon_pack = [], bool $wrap_details = FALSE) : void {
        $icon_pack_definitions = $this->getDefinitions();
        if (NULL === $icon_pack_definitions) {
            return;
        }
        if (!empty($allowed_icon_pack)) {
            $icon_pack_definitions = array_intersect_key($icon_pack_definitions, $allowed_icon_pack);
        }
        $extractor_forms = $this->iconPackExtractorManager
            ->getExtractorForms($icon_pack_definitions);
        if (empty($extractor_forms)) {
            return;
        }
        foreach ($icon_pack_definitions as $pack_id => $definition) {
            // Simply skip if no settings declared in definition.
            if (count($definition['settings'] ?? []) === 0) {
                continue;
            }
            // Create the container for each extractor settings used to have the
            // extractor form.
            $form[$pack_id] = [
                '#type' => $wrap_details ? 'details' : 'container',
                '#title' => $definition['label'] ?? $pack_id,
            ];
            // Create the extractor form and set settings so we can build with values.
            $subform_state = SubformState::createForSubform($form[$pack_id], $form, $form_state);
            $subform_state->getCompleteFormState()
                ->setValue('saved_values', $default_settings[$pack_id] ?? []);
            if (is_a($extractor_forms[$pack_id], '\\Drupal\\Core\\Plugin\\PluginFormInterface')) {
                $form[$pack_id] += $extractor_forms[$pack_id]->buildConfigurationForm($form[$pack_id], $subform_state);
            }
        }
    }
    
    /**
     * {@inheritdoc}
     */
    public function listIconPackOptions(bool $include_description = FALSE) : array {
        $icon_pack_definitions = $this->getDefinitions();
        if (NULL === $icon_pack_definitions) {
            return [];
        }
        $options = [];
        foreach ($icon_pack_definitions as $definition) {
            if (empty($definition['icons'])) {
                continue;
            }
            $label = $definition['label'] ?? $definition['id'];
            if ($include_description && isset($definition['description'])) {
                $label = sprintf('%s - %s', $label, $definition['description']);
            }
            $options[$definition['id']] = sprintf('%s (%u)', $label, count($definition['icons']));
        }
        natsort($options);
        return $options;
    }
    
    /**
     * {@inheritdoc}
     */
    protected function getDiscovery() : DiscoveryInterface {
        if (!$this->discovery) {
            $this->discovery = new YamlDiscovery('icons', $this->moduleHandler
                ->getModuleDirectories() + $this->themeHandler
                ->getThemeDirectories());
            $this->discovery
                ->addTranslatableProperty('label')
                ->addTranslatableProperty('description');
            $this->discovery = new ContainerDerivativeDiscoveryDecorator($this->discovery);
        }
        return $this->discovery;
    }
    
    /**
     * {@inheritdoc}
     */
    protected function providerExists(mixed $provider) : bool {
        return $this->moduleHandler
            ->moduleExists($provider) || $this->themeHandler
            ->themeExists($provider);
    }
    
    /**
     * Discover list of icons from definition extractor.
     *
     * @param array $definition
     *   The definition.
     *
     * @return array
     *   Discovered icons.
     */
    private function getIconsFromDefinition(array $definition) : array {
        if (!isset($definition['extractor'])) {
            return [];
        }
        
        /** @var \Drupal\Core\Theme\Icon\IconExtractorInterface $extractor */
        $extractor = $this->iconPackExtractorManager
            ->createInstance($definition['extractor'], $definition);
        return $extractor->discoverIcons();
    }
    
    /**
     * Validates a definition against the JSON schema specification.
     *
     * @param array $definition
     *   The definition to alter.
     *
     * @return bool
     *   FALSE if the response failed validation, otherwise TRUE.
     *
     * @throws \Drupal\Core\Theme\Icon\Exception\IconPackConfigErrorException
     *   Thrown when the definition is not valid.
     */
    private function validateDefinition(array $definition) : bool {
        // If the validator isn't set, then the validation library is not installed.
        if (!$this->validator) {
            return TRUE;
        }
        $schema_ref = sprintf('file://%s/%s', $this->appRoot, self::SCHEMA_VALIDATE);
        $schema = (object) [
            '$ref' => $schema_ref,
        ];
        $definition_object = Validator::arrayToObjectRecursive($definition);
        $this->validator
            ->validate($definition_object, $schema, Constraint::CHECK_MODE_COERCE_TYPES);
        if ($this->validator
            ->isValid()) {
            return TRUE;
        }
        $message_parts = array_map(static fn(array $error): string => sprintf("[%s] %s", $error['property'], $error['message']), $this->validator
            ->getErrors());
        $message = implode(", ", $message_parts);
        throw new IconPackConfigErrorException(sprintf('%s:%s Error in definition `%s`:%s', $definition['provider'], $definition['id'], $definition_object->id, $message));
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title Overrides
DefaultPluginManager::$additionalAnnotationNamespaces protected property Additional annotation namespaces.
DefaultPluginManager::$alterHook protected property Name of the alter hook if one should be invoked.
DefaultPluginManager::$cacheKey protected property The cache key.
DefaultPluginManager::$cacheTags protected property An array of cache tags to use for the cached definitions.
DefaultPluginManager::$defaults protected property A set of defaults to be referenced by $this-&gt;processDefinition(). 12
DefaultPluginManager::$moduleExtensionList protected property The module extension list.
DefaultPluginManager::$moduleHandler protected property The module handler to invoke the alter hook. 1
DefaultPluginManager::$namespaces protected property An object of root paths that are traversable.
DefaultPluginManager::$pluginDefinitionAnnotationName protected property The name of the annotation that contains the plugin definition.
DefaultPluginManager::$pluginDefinitionAttributeName protected property The name of the attribute that contains the plugin definition.
DefaultPluginManager::$pluginInterface protected property The interface each plugin should implement. 1
DefaultPluginManager::$subdir protected property The subdirectory within a namespace to look for plugins.
DefaultPluginManager::alterDefinitions protected function Invokes the hook to alter the definitions if the alter hook is set. 5
DefaultPluginManager::alterInfo protected function Sets the alter hook name.
DefaultPluginManager::clearCachedDefinitions public function Clears static and persistent plugin definition caches. Overrides CachedDiscoveryInterface::clearCachedDefinitions 11
DefaultPluginManager::extractProviderFromDefinition protected function Extracts the provider from a plugin definition.
DefaultPluginManager::findDefinitions protected function Finds plugin definitions. 7
DefaultPluginManager::getCacheContexts public function The cache contexts associated with this object. Overrides CacheableDependencyInterface::getCacheContexts
DefaultPluginManager::getCachedDefinitions protected function Returns the cached plugin definitions of the decorated discovery class.
DefaultPluginManager::getCacheMaxAge public function The maximum age for which this object may be cached. Overrides CacheableDependencyInterface::getCacheMaxAge
DefaultPluginManager::getCacheTags public function The cache tags associated with this object. Overrides CacheableDependencyInterface::getCacheTags
DefaultPluginManager::getDefinitions public function Gets the definition of all plugins for this type. Overrides DiscoveryTrait::getDefinitions 1
DefaultPluginManager::getFactory protected function Gets the plugin factory. Overrides PluginManagerBase::getFactory
DefaultPluginManager::setCacheBackend public function Initialize the cache backend.
DefaultPluginManager::setCachedDefinitions protected function Sets a cache of plugin definitions for the decorated discovery class.
DefaultPluginManager::useCaches public function Disable the use of caches. Overrides CachedDiscoveryInterface::useCaches 1
DiscoveryCachedTrait::$definitions protected property Cached definitions array. 1
DiscoveryCachedTrait::getDefinition public function Overrides DiscoveryTrait::getDefinition 3
DiscoveryTrait::doGetDefinition protected function Gets a specific plugin definition.
DiscoveryTrait::hasDefinition public function
IconPackManager::$validator private property The schema validator.
IconPackManager::getDiscovery protected function Gets the plugin discovery. Overrides DefaultPluginManager::getDiscovery
IconPackManager::getExtractorFormDefaults public function Retrieve extractor default options. Overrides IconPackManagerInterface::getExtractorFormDefaults
IconPackManager::getExtractorPluginForms public function Retrieve extractor forms based on the provided icon set limit. Overrides IconPackManagerInterface::getExtractorPluginForms
IconPackManager::getIcon public function Get definition of a specific icon. Overrides IconPackManagerInterface::getIcon
IconPackManager::getIcons public function Get a list of all the icons within definitions. Overrides IconPackManagerInterface::getIcons
IconPackManager::getIconsFromDefinition private function Discover list of icons from definition extractor.
IconPackManager::listIconPackOptions public function Populates a key-value pair of available icon pack. Overrides IconPackManagerInterface::listIconPackOptions
IconPackManager::processDefinition public function Performs extra processing on plugin definitions. Overrides DefaultPluginManager::processDefinition
IconPackManager::providerExists protected function Determines if the provider of a definition exists. Overrides DefaultPluginManager::providerExists
IconPackManager::SCHEMA_VALIDATE private constant
IconPackManager::setValidator public function Sets the validator service if available.
IconPackManager::validateDefinition private function Validates a definition against the JSON schema specification.
IconPackManager::__construct public function Constructs the IconPackPluginManager object. Overrides DefaultPluginManager::__construct
PluginManagerBase::$discovery protected property The object that discovers plugins managed by this manager.
PluginManagerBase::$factory protected property The object that instantiates plugins managed by this manager.
PluginManagerBase::$mapper protected property The object that returns the preconfigured plugin instance appropriate for a particular runtime condition.
PluginManagerBase::createInstance public function 15
PluginManagerBase::getFallbackPluginId protected function 6
PluginManagerBase::getInstance public function 6
PluginManagerBase::handlePluginNotFound protected function Allows plugin managers to specify custom behavior if a plugin is not found. 1
UseCacheBackendTrait::$cacheBackend protected property Cache backend instance.
UseCacheBackendTrait::$useCaches protected property Flag whether caches should be used or skipped.
UseCacheBackendTrait::cacheGet protected function Fetches from the cache backend, respecting the use caches flag.
UseCacheBackendTrait::cacheSet protected function Stores data in the persistent cache, respecting the use caches flag.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.