a12s-1.0.0-beta7/modules/layout/src/Plugin/A12sLayoutDisplayOptionsSet/BackgroundImage.php

modules/layout/src/Plugin/A12sLayoutDisplayOptionsSet/BackgroundImage.php
<?php

namespace Drupal\a12s_layout\Plugin\A12sLayoutDisplayOptionsSet;

use Drupal\a12s_layout\DisplayOptions\DisplayOptionsSetInterface;
use Drupal\a12s_layout\DisplayOptions\DisplayOptionsSetPluginBase;
use Drupal\breakpoint\BreakpointManagerInterface;
use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Utility\Bytes;
use Drupal\Component\Utility\Environment;
use Drupal\Component\Utility\Html;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\File\FileUrlGeneratorInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Render\Element\FormElement;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
use Drupal\Core\Utility\Error;
use Drupal\file\FileInterface;
use Drupal\file\FileUsage\FileUsageInterface;
use Drupal\image\Entity\ImageStyle;
use Drupal\paragraphs\ParagraphInterface;
use Drupal\responsive_image\Entity\ResponsiveImageStyle;
use Drupal\responsive_image\ResponsiveImageStyleInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Plugin implementation of Display Options Set for Background Image.
 *
 * @A12sLayoutDisplayOptionsSet(
 *   id = "background_image",
 *   label = @Translation("Background image"),
 *   description = @Translation("Provides options for background image."),
 *   category = @Translation("Background"),
 *   applies_to = {"layout", "paragraph"},
 *   target_template = "paragraph"
 * )
 *
 * @todo Manage file usage.
 *
 * @noinspection AnnotationMissingUseInspection
 */
class BackgroundImage extends DisplayOptionsSetPluginBase implements ContainerFactoryPluginInterface {

  /**
   * {@inheritDoc}
   *
   * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $streamWrapperManager
   *   The stream wrapper manager.
   * @param \Drupal\Core\Entity\EntityStorageInterface $fileStorage
   *   The file storage.
   * @param \Drupal\Core\Entity\EntityStorageInterface $responsiveImageStyleStorage
   *   The responsive image style storage.
   * @param \Drupal\Core\File\FileUrlGeneratorInterface $fileUrlGenerator
   *   The file URL generator.
   * @param \Drupal\breakpoint\BreakpointManagerInterface $breakpointManager
   *   The breakpoint manager.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
   *   The module handler.
   * @param \Drupal\file\FileUsage\FileUsageInterface $fileUsage
   *   The file usage service.
   */
  public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
    ConfigFactoryInterface $configFactory,
    protected StreamWrapperManagerInterface $streamWrapperManager,
    protected EntityStorageInterface $fileStorage,
    protected EntityStorageInterface $responsiveImageStyleStorage,
    protected FileUrlGeneratorInterface $fileUrlGenerator,
    protected BreakpointManagerInterface $breakpointManager,
    protected ModuleHandlerInterface $moduleHandler,
    protected FileUsageInterface $fileUsage
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $configFactory);
  }

  /**
   * {@inheritDoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): DisplayOptionsSetInterface {
    $entityTypeManager = $container->get('entity_type.manager');

    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('config.factory'),
      $container->get('stream_wrapper_manager'),
      $entityTypeManager->getStorage('file'),
      $entityTypeManager->getStorage('responsive_image_style'),
      $container->get('file_url_generator'),
      $container->get('breakpoint.manager'),
      $container->get('module_handler'),
      $container->get('file.usage')
    );
  }

  /**
   * {@inheritDoc}
   */
  public function defaultValues(): array {
    return [
      'background_size' => '',
      'background_position' => '',
      'responsive_image_style' => '',
      'scheme' => \Drupal::config('system.file')->get('default_scheme'),
      'directory' => 'background-images',
      'max_size' => '',
      'max_dimensions' => ['width' => '', 'height' => ''],
    ];
  }

  /**
   * {@inheritDoc}
   */
  public function preprocessVariables(array &$variables, array $configuration = []): void {
    parent::preprocessVariables($variables, $configuration);

    if (!empty($configuration['background_image'])) {
      $responsiveImageStyleId = $configuration['responsive_image_style'] ?? $this->globalConfiguration['responsive_image_style'] ?? NULL;

      if ($responsiveImageStyleId && ($file = $this->getImage($configuration))) {
        $fid = $file->id();

        if (isset($variables['attributes']['style'])) {
          unset($variables['attributes']['style']);
        }

        $hash = sha1($fid . '-' . $responsiveImageStyleId);
        $cssSelector = 'background-image-' . $hash;
        $style = $this->buildBackgroundImageCss('html.loaded .' . $cssSelector . ':before', $file, $responsiveImageStyleId, $configuration);

        if ($style) {
          $variables['attributes']['class'][] = $cssSelector;
          $variables['attributes']['class'][] = 'background-image';
          $variables['content']['#attached']['html_head'][] = [
            ['#tag' => 'style', '#value' => $style],
            'display-options--background-image--' . $hash,
          ];
        }
      }
    }
  }

  /**
   * Build the CSS code for the background image, using a responsive style.
   *
   * @param string $cssSelector
   *   The CSS selector.
   * @param \Drupal\file\fileInterface $file
   *   The file instance.
   * @param string $responsiveImageStyleId
   *   The responsive image style ID.
   * @param array $options
   *   The extra options.
   *
   * @return string
   *   The CSS code.
   */
  public function buildBackgroundImageCss(string $cssSelector, FileInterface $file, string $responsiveImageStyleId, array $options = []): string {
    if (empty($cssSelector)) {
      return '';
    }

    $options += [
      'background_repeat' => 'no-repeat',
      'separator' => "\n",
    ];

    $uri = $file->getFileUri();
    $css = [];

    if ($responsiveImageStyle = ResponsiveImageStyle::load($responsiveImageStyleId)) {
      $fallbackStyleId = $responsiveImageStyle->getFallbackImageStyle();

      // Build CSS for the fallback image, if defined.
      if ($fallbackStyleId !== ResponsiveImageStyleInterface::EMPTY_IMAGE) {
        if ($fallbackStyleId && ($fallbackStyle = ImageStyle::load($fallbackStyleId))) {
          $url = $fallbackStyle->buildUrl($uri);
        }
        else {
          $url = $this->fileUrlGenerator->generateAbsoluteString($uri);
        }

        if ($url) {
          $css[] = $cssSelector . ' { background-image: url("' . $url . '"); }';
        }
      }

      // Then build CSS for each defined breakpoint.
      $breakpoints = $this->breakpointManager->getBreakpointsByGroup($responsiveImageStyle->getBreakpointGroup());

      foreach (array_reverse($responsiveImageStyle->getKeyedImageStyleMappings()) as $bid => $multipliers) {
        if (isset($breakpoints[$bid])) {
          foreach ($multipliers as $multiplier => $mapping) {
            // "image_mapping" may be either string or array.
            $styles = (array) ($mapping['image_mapping'] ?? []);
            $styleId = reset($styles);

            if ($styleId && $styleId !== ResponsiveImageStyleInterface::EMPTY_IMAGE) {
              $url = ImageStyle::load($styleId)?->buildUrl($uri);

              if ($url) {
                $query = $breakpoints[$bid]->getMediaQuery();
                $query = $this->multiplierMediaQuerySelectors($query, $multiplier);

                // @todo should we add the URL without media query? This sounds
                //   wrong, as for this we already use the fallback image...
                if ($query !== '') {
                  $css[] = "@media $query { $cssSelector { background-image: url('$url'); } }";
                }
              }
            }
          }
        }
      }
    }

    if ($css) {
      $sizePositionRepeatArray = $this->getBackgroundSizeAndPosition($options);
      $sizePositionRepeatArray[] = "background-repeat:{$options['background_repeat']};";
      $sizePositionRepeat = implode(' ', $sizePositionRepeatArray);
      $css[] = "$cssSelector:before { $sizePositionRepeat }";
    }

    return implode($options['separator'], $css);
  }

  /**
   * Add multipliers to a media query.
   *
   * @param string $query
   *   The base media query (it may be an empty string).
   * @param string $multiplier
   *   The multiplier value.
   *
   * @return string
   *   The media query multipliers.
   */
  protected function multiplierMediaQuerySelectors(string $query, string $multiplier): string {
    $multiplier = (float) $multiplier;

    if ($multiplier && $multiplier !== 1.) {
      $selector = [];
      $rules = [
        [
          'property' => '-webkit-min-device-pixel-ratio',
          'string' => '@multiplier',
        ],
        [
          'property' => 'min--moz-device-pixel-ratio',
          'string' => '@multiplier',
        ],
        [
          'property' => '-o-min-device-pixel-ratio',
          'string' => '@multiplier/1',
        ],
        [
          'property' => 'min-device-pixel-ratio',
          'string' => '@multiplier',
        ],
        [
          'property' => 'min-resolution',
          'string' => '192dpi',
          'callback' => fn(float $multiplier) => $multiplier * 96,
        ],
        [
          'property' => 'min-resolution',
          'string' => '@multiplierdppx',
        ],
      ];

      foreach ($rules as $rule) {
        $placeholders = [
          '@multiplier' => (string) $multiplier,
          '@value' => (string) isset($rule['callback']) ? $rule['callback']($multiplier) : $multiplier,
        ];

        $value = new FormattableMarkup($rule['string'], $placeholders);
        $multiplierQuery = "({$rule['property']}: {$value})";
        $selector[] = $query ? ($query . ' and ' . $multiplierQuery) : $multiplierQuery;
      }

      $query = implode(',', $selector);
    }

    return $query;
  }

  /**
   * Build the background-size and background-position properties.
   *
   * It relies on  the provided settings.
   *
   * @param array $settings
   *   The main settings.
   * @param string $default
   *   By default, is uses the default value. If the property should be skipped,
   *   this variable may be set to NULL.
   *
   * @return array
   *   The CSS position as array.
   */
  protected function getBackgroundSizeAndPosition(array $settings = [], string $default = ''): array {
    $style = [];

    foreach (['background_size', 'background_position'] as $cssProperty) {
      $cssProperty_name = strtr($cssProperty, '_', '-');
      $option = $default;

      if (!empty($settings[$cssProperty]['option'])) {
        $option = $settings[$cssProperty]['option'];
        $custom_value = $settings[$cssProperty]['custom'] ?? '';
      }

      if ($option === '') {
        $option = $this->globalConfiguration[$cssProperty] ?? NULL;
        $custom_value = $this->globalConfiguration[$cssProperty] ?? NULL;
      }

      switch ($option) {
        case NULL:
          // Ignore...
          break;

        case 'custom':
          if (!empty($custom_value)) {
            $style[] = $cssProperty_name . ':' . $custom_value . ';';
          }
          break;

        default:
          $style[] = $cssProperty_name . ':' . $option . ';';
      }
    }

    return $style;
  }

  /**
   * {@inheritDoc}
   */
  public function globalSettingsForm(array &$form, FormStateInterface $formState, array $config = []): void {
    $default = $this->mergeConfigWithDefaults($config);

    /* @noinspection HtmlUnknownTarget */
    $form['background_size'] = [
      '#type' => 'select_or_other_select',
      '#title' => $this->t('Background size'),
      '#description' => $this->t('You may find further details about the @name CSS property on <a href=":url">this page</a>.', [
        '@name' => 'background-size',
        ':url' => 'https://developer.mozilla.org/fr/docs/Web/CSS/background-size',
      ]),
      '#options' => [
        'cover' => $this->t('Cover'),
        'contain' => $this->t('Contain'),
      ],
      '#regex' => '^(?:(?:(?:(?:\d+)(?:%|r?em|px|cm|ch|vw|vh)|auto)(?:\s+(?:(?:\d+)(?:%|r?em|px|cm|ch|vw|vh)|auto)?))|cover|contain)$',
      '#element_validate' => [[static::class, 'validateSelectOrOtherRegex']],
      // @todo create issue for select_or_other, as the module forces to define
      //   a default_value even if not necessary.
      //   @see ElementBase::addSelectField().
      '#default_value' => NULL,
    ];

    $form['background_position'] = [
      '#type' => 'select_or_other_select',
      '#title' => $this->t('Background position'),
      '#description' => $this->t('You may find further details about the @name CSS property on <a href=":url">this page</a>.', [
        '@name' => 'background-position',
        ':url' => 'https://developer.mozilla.org/fr/docs/Web/CSS/background-position',
      ]),
      '#options' => [
        'center' => $this->t('Center'),
        'top' => $this->t('Center Top'),
        'bottom' => $this->t('Center Bottom'),
        'left' => $this->t('Left Center'),
        'right' => $this->t('Right Center'),
      ],
      // Note that this allows some wrong values like "left left", but those are
      // so obvious errors that we can ignore it.
      '#regex' => '^(?:(?:(?:(?:\d+)(?:%|r?em|px|cm|ch|vw|vh)|0|top|bottom|left|right|center)(?:\h+(?:(?:\d+)(?:%|r?em|px|cm|ch|vw|vh)|0|top|bottom|left|right|center))?)|inherit|initial|unset)$',
      '#element_validate' => [[static::class, 'validateSelectOrOtherRegex']],
      '#default_value' => NULL,
    ];

    // Handle default values for "select_or_other".
    foreach (['background_size', 'background_position'] as $key) {
      $form[$key]['#other_option'] = $this->t('- Other -');
      $value = $default[$key];
      $options = $form[$key]['#options'];

      if (array_key_exists($value, $options)) {
        $form[$key]['#default_value'] = [$value];
      }
      elseif (!empty($value)) {
        $form[$key]['#other_options'] = $value;
      }
    }

    // Any visible, writable wrapper can potentially be used for uploads,
    // including a remote file system that integrates with a CDN.
    $options = $this->streamWrapperManager->getDescriptions(StreamWrapperInterface::WRITE_VISIBLE);
    if (!empty($options)) {
      $form['scheme'] = [
        '#type' => 'radios',
        '#title' => $this->t('File storage'),
        '#default_value' => $default['scheme'],
        '#options' => $options,
        '#access' => count($options) > 1,
      ];
    }
    else {
      $form['scheme'] = ['#type' => 'value', '#value' => $default['scheme']];
    }

    $form['directory'] = [
      '#type' => 'textfield',
      '#default_value' => $default['directory'],
      '#title' => $this->t('Upload directory'),
      '#description' => $this->t("A directory relative to Drupal's files directory where uploaded images are stored."),
    ];

    $default_max_size = format_size(Environment::getUploadMaxSize());
    $form['max_size'] = [
      '#type' => 'textfield',
      '#default_value' => $default['max_size'],
      '#title' => $this->t('Maximum file size'),
      '#description' => $this->t('If this is left empty, then the file size will be limited by the PHP maximum upload size of @size.', ['@size' => $default_max_size]),
      '#maxlength' => 20,
      '#size' => 10,
      '#placeholder' => $default_max_size,
    ];

    $form['max_dimensions'] = [
      '#type' => 'item',
      '#title' => $this->t('Maximum dimensions'),
      '#field_prefix' => '<div class="container-inline clearfix">',
      '#field_suffix' => '</div>',
      '#description' => $this->t('Images larger than these dimensions will be scaled down.'),
    ];

    $form['max_dimensions']['width'] = [
      '#title' => $this->t('Width'),
      '#title_display' => 'invisible',
      '#type' => 'number',
      '#default_value' => $default['max_dimensions']['width'],
      '#size' => 8,
      '#maxlength' => 8,
      '#min' => 1,
      '#max' => 99999,
      '#placeholder' => $this->t('width'),
      '#field_suffix' => ' x ',
    ];

    $form['max_dimensions']['height'] = [
      '#title' => $this->t('Height'),
      '#title_display' => 'invisible',
      '#type' => 'number',
      '#default_value' => $default['max_dimensions']['height'],
      '#size' => 8,
      '#maxlength' => 8,
      '#min' => 1,
      '#max' => 99999,
      '#placeholder' => $this->t('height'),
      '#field_suffix' => $this->t('pixels'),
    ];

    $options = $this->getResponsiveImageOptions();
    $form['responsive_image_style'] = [
      '#type' => 'select',
      '#title' => $this->t('Default responsive image style'),
      '#empty_option' => $this->t('- None -'),
      '#default_value' => $default['responsive_image_style'] ?? '',
      '#options' => $options,
      '#access' => !empty($options),
    ];
  }

  /**
   * {@inheritDoc}
   */
  public function submitGlobalSettingsForm(array $form, FormStateInterface $formState): void {
    // Transform "select_or_other" values.
    foreach (['background_size', 'background_position'] as $key) {
      if ($formState->hasValue($key)) {
        $value = $formState->getValue($key);

        if (!empty($value['other']) && ($value['select'] ?? '') === 'select_or_other') {
          $value = $value['other'];
        }
        elseif (!empty($value['select'])) {
          $value = $value['select'];
        }
        else {
          $value = '';
        }

        $formState->setValue($key, $value);
      }
    }

    parent::submitGlobalSettingsForm($form, $formState);
  }

  /**
   * {@inheritDoc}
   */
  public function form(array $form, FormStateInterface $formState, array $values = [], array $parents = []): array {
    $form['#type'] = 'details';
    $form['#open'] = !empty($values['background_image']);

    $max_filesize = min(Bytes::toNumber($this->globalConfiguration['max_size']), Environment::getUploadMaxSize());
    $max_dimensions = '0';

    if (!empty($this->globalConfiguration['max_dimensions']['width']) || !empty($this->globalConfiguration['max_dimensions']['height'])) {
      $max_dimensions = $this->globalConfiguration['max_dimensions']['width'] . 'x' . $this->globalConfiguration['max_dimensions']['height'];
    }

    $form['background_image'] = [
      '#type' => 'managed_file',
      '#title' => $this->t('Background image'),
      '#default_value' => $values['background_image'] ?? NULL,
      '#upload_location' => $this->globalConfiguration['scheme'] . '://' . $this->globalConfiguration['directory'],
      '#upload_validators' => [
        'file_validate_extensions' => ['gif png jpg jpeg'],
        'file_validate_size' => [$max_filesize],
        'file_validate_image_resolution' => [$max_dimensions],
      ],
    ];

    $form['background_size'] = [
      '#type' => 'select_or_other_select',
      '#title' => $this->t('Background size'),
      '#empty_option' => $this->t('- Use default value -'),
      '#default_value' => $values['background_size'] ?? NULL,
      '#description' => $this->t('You may find further details about the @name CSS property on <a href=":url">this page</a>.', [
        '@name' => 'background-size',
        ':url' => 'https://developer.mozilla.org/fr/docs/Web/CSS/background-size',
      ]),
      '#options' => [
        'cover' => $this->t('Cover'),
        'contain' => $this->t('Contain'),
      ],
      '#regex' => '^(?:(?:(?:(?:\d+)(?:%|r?em|px|cm|ch|vw|vh)|auto)(?:\s+(?:(?:\d+)(?:%|r?em|px|cm|ch|vw|vh)|auto)?))|cover|contain)$',
      '#element_validate' => [[static::class, 'validateSelectOrOtherRegex']],
    ];

    $form['background_position'] = [
      '#type' => 'select_or_other_select',
      '#title' => $this->t('Background position'),
      '#empty_option' => $this->t('- Use default value -'),
      '#default_value' => $values['background_position'] ?? NULL,
      '#description' => $this->t('You may find further details about the @name CSS property on <a href=":url">this page</a>.', [
        '@name' => 'background-position',
        ':url' => 'https://developer.mozilla.org/fr/docs/Web/CSS/background-position',
      ]),
      '#options' => [
        'center' => $this->t('Center'),
        'top' => $this->t('Center Top'),
        'bottom' => $this->t('Center Bottom'),
        'left' => $this->t('Left Center'),
        'right' => $this->t('Right Center'),
      ],
      // Note that this allows some wrong values like "left left", but those are
      // so obvious errors that we can ignore it.
      '#regex' => '^(?:(?:(?:(?:\d+)(?:%|r?em|px|cm|ch|vw|vh)|0|top|bottom|left|right|center)(?:\h+(?:(?:\d+)(?:%|r?em|px|cm|ch|vw|vh)|0|top|bottom|left|right|center))?)|inherit|initial|unset)$',
      '#element_validate' => [[static::class, 'validateSelectOrOtherRegex']],
    ];

    $form['background_repeat'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Background repeat'),
      '#default_value' => $values['background_repeat'] ?? FALSE,
    ];

    $options = $this->getResponsiveImageOptions();
    // @todo check if the global value is set. If not, add a warning, or simply
    //   do not add the empty option.
    $form['responsive_image_style'] = [
      '#type' => 'select',
      '#title' => $this->t('Responsive image style'),
      '#empty_option' => $this->t('- Use default value -'),
      '#default_value' => $values['responsive_image_style'] ?? '',
      '#options' => $options,
      '#access' => !empty($options),
    ];

    return $form;
  }

  /**
   * Form API callback "after_build": handle regex validation for "other" field.
   */
  public static function validateSelectOrOtherRegex($element, FormStateInterface $formState, array $completeForm): void {
    if (isset($element['#regex']) && isset($element['other'])) {
      $element['other']['#pattern'] = $element['#regex'];
      // Title is required in FormElement::validatePattern().
      $element['other']['#title'] = $element['#title'];
      FormElement::validatePattern($element['other'], $formState, $completeForm);
    }
  }

  /**
   * Returns Responsive image for select options.
   */
  protected function getResponsiveImageOptions(): array {
    $options = [];

    if ($this->moduleHandler->moduleExists('responsive_image')) {
      foreach ($this->responsiveImageStyleStorage->loadMultiple() as $name => $imageStyle) {
        if ($imageStyle->hasImageStyleMappings()) {
          $options[$name] = Html::escape($imageStyle->label());
        }
      }
    }

    return $options;
  }

  /**
   * Load the background image from the configuration, when possible.
   *
   * @param array $configuration
   *   The related configuration.
   *
   * @return \Drupal\file\FileInterface|null
   *   The file entity.
   */
  protected function getImage(array $configuration): ?FileInterface {
    if (!empty($configuration['background_image'])) {
      $fid = reset($configuration['background_image']);

      try {
        $file = $this->fileStorage->load($fid);
        return $file instanceof FileInterface ? $file : NULL;
      }
      catch (\Exception $e) {
        Error::logException(\Drupal::logger('a12s_layout'), $e);
      }
    }

    return NULL;
  }

  /**
   * Load the background image for the given paragraph, when possible.
   *
   * @param \Drupal\paragraphs\ParagraphInterface $paragraph
   *   The paragraph entity.
   *
   * @return \Drupal\file\FileInterface|null
   *   The file entity.
   */
  protected function getImageFromParagraph(ParagraphInterface $paragraph): ?FileInterface {
    $configuration = $paragraph->getBehaviorSetting('a12s_layout_display_options', 'display_options', [])['background_image'] ?? [];
    return $this->getImage($configuration);
  }

  /**
   * Get an instance of BackgroundImage for the given paragraph, if applicable.
   *
   * @param \Drupal\paragraphs\ParagraphInterface $paragraph
   *   The paragraph entity.
   *
   * @return \Drupal\a12s_layout\Plugin\A12sLayoutDisplayOptionsSet\BackgroundImage|null
   *   The plugin instance.
   */
  public static function getInstance(ParagraphInterface $paragraph): ?BackgroundImage {
    try {
      /** @var \Drupal\a12s_layout\DisplayOptions\DisplayOptionsSetPluginManager $pluginManagerDisplayOptionsSet */
      $pluginManagerDisplayOptionsSet = \Drupal::service('plugin.manager.a12s_layout_display_options_set');
      $configuration = ['id' => 'background_image', 'paragraph' => $paragraph];
      $backgroundImage = $pluginManagerDisplayOptionsSet->createInstance('background_image', $configuration);
      return $backgroundImage instanceof BackgroundImage ? $backgroundImage : NULL;
    }
    catch (PluginException $e) {
      Error::logException(\Drupal::logger('a12s_layout'), $e);
    }

    return NULL;
  }

  /**
   * Add file usage of files referenced by background images.
   *
   * Every referenced file that does not yet have the FILE_STATUS_PERMANENT
   * state, will be given that state.
   *
   * @param \Drupal\paragraphs\ParagraphInterface $paragraph
   *   The paragraph entity to inspect for file references on behavior plugins.
   */
  public function addFileUsage(ParagraphInterface $paragraph): void {
    if ($file = $this->getImageFromParagraph($paragraph)) {
      if ($file->get('status')->value !== FileInterface::STATUS_PERMANENT) {
        $file->setPermanent();

        try {
          $file->save();
        }
        catch (EntityStorageException $e) {
          Error::logException(\Drupal::logger('a12s_layout'), $e);
        }
      }

      $this->fileUsage->add($file, 'a12s_layout', $paragraph->getEntityTypeId(), $paragraph->id());
    }
  }

  /**
   * Merge file usage of files referenced by background images.
   *
   * @param \Drupal\paragraphs\ParagraphInterface $paragraph
   *   The paragraph entity to inspect for file references on behavior plugins.
   */
  public function mergeFileUsage(ParagraphInterface $paragraph): void {
    if (!empty($paragraph->original)) {
      // On new revisions, all files are considered to be a new usage and no
      // deletion of previous file usages are necessary.
      if ($paragraph->getRevisionId() != $paragraph->original->getRevisionId()) {
        $this->addFileUsage($paragraph);
      }
      // On modified revisions, detect which file references have been added
      // (and record their usage) and which ones have been removed (delete their
      // usage).
      // File references that existed both in the previous version of the
      // revision and in the new one don't need their usage to be updated.
      else {
        $original_file = $this->getImageFromParagraph($paragraph->original);
        $file = $this->getImageFromParagraph($paragraph);

        if ($original_file && (empty($file) || $original_file->uuid() != $file->uuid())) {
          $this->deleteFileUsage($paragraph->original);
        }

        if ($file && (empty($original_file) || $original_file->uuid() != $file->uuid())) {
          $this->addFileUsage($paragraph);
        }
      }
    }
    else {
      $this->addFileUsage($paragraph);
    }
  }

  /**
   * Deletes file usage of files referenced by background images.
   *
   * @param \Drupal\paragraphs\ParagraphInterface $paragraph
   *   The paragraph entity to inspect for file references on behavior plugins.
   * @param int $count
   *   The number of references to delete. Should be 1 when deleting a single
   *   revision and 0 when deleting an entity entirely.
   *
   * @see \Drupal\file\FileUsage\FileUsageInterface::delete()
   */
  public function deleteFileUsage(ParagraphInterface $paragraph, int $count = 1): void {
    if ($file = $this->getImageFromParagraph($paragraph)) {
      $this->fileUsage->delete($file, 'a12s_layout', $paragraph->getEntityTypeId(), $paragraph->id(), $count);
    }
  }

}

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

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