You are here

class ComponentBlock in Component blocks 1.0.x

Same name and namespace in other branches
  1. 1.x src/Plugin/Block/ComponentBlock.php \Drupal\component_blocks\Plugin\Block\ComponentBlock
  2. 1.1.x src/Plugin/Block/ComponentBlock.php \Drupal\component_blocks\Plugin\Block\ComponentBlock

Defines a class for a specially shaped block.

Plugin annotation


@Block(
 id = "component_blocks",
 admin_label = @Translation("Component blocks"),
 category = @Translation("Component blocks"),
 deriver = "Drupal\component_blocks\Plugin\Deriver\ComponentBlockBlockDeriver",
)

Hierarchy

Expanded class hierarchy of ComponentBlock

File

src/Plugin/Block/ComponentBlock.php, line 36

Namespace

Drupal\component_blocks\Plugin\Block
View source
class ComponentBlock extends BlockBase implements ContainerFactoryPluginInterface {
  use LayoutBuilderContextTrait;
  const FIXED = '__fixed';

  /**
   * Plugin manager.
   *
   * @var \Drupal\ui_patterns\UiPatternsManager
   */
  private $uiPatternsManager;

  /**
   * Entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * Context handler.
   *
   * @var \Drupal\Core\Plugin\Context\ContextHandlerInterface
   */
  private $contextHandler;

  /**
   * Formatter manager.
   *
   * @var \Drupal\Core\Field\FormatterPluginManager
   */
  private $formatterPluginManager;

  /**
   * Token service.
   *
   * @var \Drupal\Core\Utility\Token
   */
  private $token;

  /**
   * Constructs a new ComponentBlock.
   *
   * @param array $configuration
   *   Configuration.
   * @param string $plugin_id
   *   Plugin Id.
   * @param array $plugin_definition
   *   Plugin definition.
   * @param \Drupal\ui_patterns\UiPatternsManager $uiPatternsManager
   *   Plugin manager.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   Entity type manager.
   * @param \Drupal\Core\Plugin\Context\ContextHandlerInterface $contextHandler
   *   Context handler.
   * @param \Drupal\Core\Field\FormatterPluginManager $formatterPluginManager
   *   Formatter manager.
   * @param \Drupal\Core\Utility\Token $token
   *   Token service.
   */
  public function __construct(array $configuration, $plugin_id, array $plugin_definition, UiPatternsManager $uiPatternsManager, EntityTypeManagerInterface $entityTypeManager, ContextHandlerInterface $contextHandler, FormatterPluginManager $formatterPluginManager, Token $token) {
    $this->uiPatternsManager = $uiPatternsManager;
    $this->entityTypeManager = $entityTypeManager;
    $this->contextHandler = $contextHandler;
    $this->formatterPluginManager = $formatterPluginManager;
    $this->token = $token;

    // This has to be last because the parent constructor calls
    // ::setConfiguration.
    parent::__construct($configuration, $plugin_id, $plugin_definition);
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static($configuration, $plugin_id, $plugin_definition, $container
      ->get('plugin.manager.ui_patterns'), $container
      ->get('entity_type.manager'), $container
      ->get('context.handler'), $container
      ->get('plugin.manager.field.formatter'), $container
      ->get('token'));
  }

  /**
   * {@inheritdoc}
   */
  public function setConfiguration(array $configuration) {
    $defaultConfiguration = $this
      ->defaultConfiguration();
    $plugin = $this
      ->uiPatternsManager()
      ->getDefinition($this->pluginDefinition['ui_pattern_id']);
    foreach ($plugin['fields'] as $item) {
      if (!($item['ui'] ?? TRUE)) {

        // We don't want duplicates for no-ui items - default is enough.
        unset($configuration['variables'][$item
          ->getName()]['value']);
      }
    }
    $this->configuration = NestedArray::mergeDeep($this
      ->baseConfigurationDefaults(), $defaultConfiguration, $configuration);
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    $plugin = $this
      ->uiPatternsManager()
      ->getDefinition($this->pluginDefinition['ui_pattern_id']);
    $defaults = array_map(function (PatternDefinitionField $item) {
      return [
        'source' => self::FIXED,
        'value' => $item['default'] ?? '',
      ];
    }, $plugin['fields']);
    return [
      'variables' => $defaults,
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function build() {
    $definition = $this
      ->uiPatternsManager()
      ->getDefinition($this->pluginDefinition['ui_pattern_id']);
    $context = [];

    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
    $entity = $this
      ->getContextValue('entity');
    $view_builder = $this->entityTypeManager
      ->getViewBuilder($entity
      ->getEntityTypeId());
    $metadata = new BubbleableMetadata();
    $metadata
      ->addCacheableDependency($entity);
    foreach ($this
      ->getConfiguration()['variables'] as $context_id => $details) {
      if ($details['source'] === self::FIXED) {
        if (!is_scalar($details['value'])) {

          // Allow for array default values.
          $context[$context_id] = $details['value'];
          continue;
        }
        try {
          $value = $this->token
            ->replace($details['value'], [
            $entity
              ->getEntityTypeId() => $entity,
          ], [], $metadata);
          if ($value !== $details['value']) {

            // Token replacement sanitizes, so we need to flag as such.
            $value = Markup::create($value);
          }
        } catch (EntityMalformedException $e) {

          // Attempt to get e.g an entity URL without a saved entity in layout
          // builder.
          $value = '';
        }
        $context[$context_id] = $value;
        continue;
      }
      try {
        $formatter_output = $view_builder
          ->viewField($entity
          ->get($details['source']), array_intersect_key($details, [
          'type' => TRUE,
          'settings' => TRUE,
        ]) + [
          'label' => 'hidden',
        ]);
        if (Element::isEmpty($formatter_output)) {

          // No output other than cache metadata.
          $metadata
            ->merge(CacheableMetadata::createFromRenderArray($formatter_output));
          continue;
        }
        $context[$context_id] = [
          '#theme' => 'field__component_block',
        ] + $formatter_output;
      } catch (EntityMalformedException $e) {

        // Attempt to get e.g an entity URL without a saved entity in layout
        // builder.
        $context[$context_id] = '';
      }
    }
    $build = [
      '#type' => 'pattern',
      '#id' => $this->pluginDefinition['ui_pattern_id'],
      '#fields' => $context,
      '#context' => [
        'type' => 'entity',
        'entity' => $entity,
      ],
    ];

    // Attach libraries to the block;.
    if (!empty($definition['libraries'])) {
      $metadata
        ->addAttachments([
        'library' => $definition['libraries'],
      ]);
    }
    $metadata
      ->applyTo($build);
    return $build;
  }

  /**
   * {@inheritdoc}
   */
  public function blockForm($form, FormStateInterface $form_state) {
    $plugin = $this
      ->uiPatternsManager()
      ->getDefinition($this->pluginDefinition['ui_pattern_id']);
    $form = parent::blockForm($form, $form_state);
    $form['variables'] = [
      '#type' => 'details',
      '#title' => $this
        ->t('Context variables'),
      '#open' => TRUE,
      '#tree' => TRUE,
    ];
    $contexts = $this
      ->contextHandler()
      ->getMatchingContexts($form_state
      ->getTemporaryValue('gathered_contexts') ?: [], $this
      ->getContextDefinition('entity'));
    $context = reset($contexts);

    /** @var \Drupal\Core\Entity\ContentEntityInterface $sample_entity */
    $sample_entity = $context
      ->getContextData()
      ->getValue();
    $fields = array_map(function (FieldDefinitionInterface $field) {
      return $field
        ->getLabel();
    }, $sample_entity
      ->getFieldDefinitions());
    $fields[self::FIXED] = $this
      ->t('Fixed input');
    foreach ($plugin['fields'] as $id => $details) {
      if (!($details['ui'] ?? TRUE)) {
        $form['variables'][$id] = [
          'source' => [
            '#type' => 'value',
            '#value' => self::FIXED,
          ],
          'value' => [
            '#type' => 'value',
            '#value' => $details['default'],
          ],
        ];
        continue;
      }
      $form['variables'][$id] = [
        '#type' => 'container',
        '#process' => [
          [
            $this,
            'formatterSettingsProcessCallback',
          ],
        ],
        '#prefix' => '<div id="component-settings-' . $id . '">',
        '#suffix' => '</div>',
        'label' => [
          '#type' => 'item',
          '#markup' => $details['label'],
        ],
        'source' => [
          '#type' => 'select',
          '#options' => $fields,
          '#default_value' => self::FIXED,
          '#title' => $this
            ->t('Source'),
          '#ajax' => [
            'callback' => [
              get_class($this),
              'updateElementValue',
            ],
            'wrapper' => 'component-settings-' . $id,
          ],
        ],
      ];
    }
    return $form;
  }

  /**
   * Render API callback: builds the formatter settings elements.
   */
  public function formatterSettingsProcessCallback(array &$element, FormStateInterface $form_state, array &$complete_form) {
    if ($configuration = $this
      ->getCurrentConfiguration($element['#parents'], $form_state)) {
      if ($configuration['source'] === self::FIXED) {
        $element['value'] = [
          '#type' => 'textfield',
          '#default_value' => $configuration['value'] ?? '',
          '#title' => $this
            ->t('Fixed value'),
        ];
        return $element;
      }
      $contexts = $this
        ->contextHandler()
        ->getMatchingContexts($form_state
        ->getTemporaryValue('gathered_contexts') ?: [], $this
        ->getContextDefinition('entity'));

      // Contexts can become empty on subsequent ajax requests with layout
      // builder.
      if (!$contexts) {
        $contexts = $this
          ->contextHandler()
          ->getMatchingContexts($this
          ->getAvailableContexts($form_state
          ->getBuildInfo()['args'][0]), $this
          ->getContextDefinition('entity'));
      }
      $context = reset($contexts);

      /** @var \Drupal\Core\Entity\ContentEntityInterface $sample_entity */
      $sample_entity = $context
        ->getContextData()
        ->getValue();
      $field_definition = $sample_entity
        ->getFieldDefinition($configuration['source']);
      $formatter_configuration = array_intersect_key($configuration, [
        'type' => TRUE,
        'settings' => TRUE,
      ]) + [
        'label' => 'hidden',
      ];
      $options = $this
        ->getApplicablePluginOptions($field_definition);
      $keys = array_keys($options);
      $formatter_configuration += [
        'type' => reset($keys),
        'settings' => $this
          ->formatterPluginManager()
          ->getDefaultSettings(reset($keys)),
      ];
      $formatter = $this
        ->formatterPluginManager()
        ->getInstance([
        'configuration' => $formatter_configuration,
        'field_definition' => $field_definition,
        'view_mode' => EntityDisplayBase::CUSTOM_MODE,
        'prepare' => TRUE,
      ]);
      $element['source']['#default_value'] = $configuration['source'];
      $element['type'] = [
        '#type' => 'select',
        '#options' => $options,
        '#default_value' => $formatter_configuration['type'],
        '#required' => TRUE,
        '#title' => $this
          ->t('Formatter'),
        '#ajax' => [
          'callback' => [
            static::class,
            'updateElementValue',
          ],
          'wrapper' => 'component-settings-' . end($element['#parents']),
        ],
      ];
      $element['settings'] = $formatter
        ->settingsForm($complete_form, $form_state);
      $element['settings']['#parents'] = array_merge($element['#parents'], [
        'settings',
      ]);
    }
    return $element;
  }

  /**
   * Gets the current configuration for given parents.
   *
   * @param array $parents
   *   The #parents of the element representing the formatter.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   *
   * @return array|null
   *   The current configuration.
   */
  protected function getCurrentConfiguration(array $parents, FormStateInterface $form_state) : ?array {

    // Use the processed values, if available.
    $configuration = NestedArray::getValue($form_state
      ->getValues(), $parents);
    $variable = end($parents);
    if (!$configuration) {

      // Next check the raw user input.
      $configuration = NestedArray::getValue($form_state
        ->getUserInput(), $parents);
      if (!$configuration) {

        // If no user input exists, use the default values.
        $settings = $this
          ->getConfiguration()['variables'][$variable];
        return $settings;
      }
    }
    return $configuration;
  }

  /**
   * Ajax callback that updates options.
   */
  public static function updateElementValue(array $form, FormStateInterface $form_state) {
    $array_parents = $form_state
      ->getTriggeringElement()['#array_parents'];
    array_pop($array_parents);
    return NestedArray::getValue($form, $array_parents);
  }

  /**
   * {@inheritdoc}
   */
  public function blockSubmit($form, FormStateInterface $form_state) {
    $this->configuration['variables'] = $form_state
      ->getValue('variables');
  }

  /**
   * Returns an array of applicable formatter options for a field.
   *
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
   *   The field definition.
   *
   * @return array
   *   An array of applicable formatter options.
   *
   * @see \Drupal\field_ui\Form\EntityDisplayFormBase::getApplicablePluginOptions()
   */
  protected function getApplicablePluginOptions(FieldDefinitionInterface $field_definition) {
    $options = $this
      ->formatterPluginManager()
      ->getOptions($field_definition
      ->getType());
    $applicable_options = [];
    foreach ($options as $option => $label) {
      $plugin_class = DefaultFactory::getPluginClass($option, $this
        ->formatterPluginManager()
        ->getDefinition($option));
      if ($plugin_class::isApplicable($field_definition)) {
        $applicable_options[$option] = $label;
      }
    }
    return $applicable_options;
  }

  /**
   * {@inheritdoc}
   */
  protected function contextHandler() {

    // phpcs:disable DrupalPractice.Objects.GlobalDrupal.GlobalDrupal
    return $this->contextHandler ?: \Drupal::service('context.handler');
  }

  /**
   * Gets the formatter plugin manager.
   *
   * In some AJAX contexts, the constructor is not called.
   *
   * @return \Drupal\Core\Field\FormatterPluginManager
   *   Manager.
   */
  protected function formatterPluginManager() : FormatterPluginManager {
    if (!$this->formatterPluginManager) {
      $this->formatterPluginManager = \Drupal::service('plugin.manager.field.formatter');
    }
    return $this->formatterPluginManager;
  }

  /**
   * Gets the UI patterns manager.
   *
   * In some AJAX contexts, the constructor is not called.
   *
   * @return \Drupal\ui_patterns\UiPatternsManager
   *   Manager.
   */
  protected function uiPatternsManager() : UiPatternsManager {
    if (!$this->uiPatternsManager) {
      $this->uiPatternsManager = \Drupal::service('plugin.manager.ui_patterns');
    }
    return $this->uiPatternsManager;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
BlockBase::buildConfigurationForm public function Form constructor. Overrides PluginFormInterface::buildConfigurationForm 2
BlockPluginInterface::BLOCK_LABEL_VISIBLE constant Indicates the block label (title) should be displayed to end users.
BlockPluginTrait::$transliteration protected property The transliteration service.
BlockPluginTrait::access public function
BlockPluginTrait::baseConfigurationDefaults protected function Returns generic default configuration for block plugins.
BlockPluginTrait::blockAccess protected function Indicates whether the block should be shown. 16
BlockPluginTrait::blockValidate public function 3
BlockPluginTrait::buildConfigurationForm public function Creates a generic configuration form for all block types. Individual block plugins can add elements to this form by overriding BlockBase::blockForm(). Most block plugins should not override this method unless they need to alter the generic form elements. Aliased as: traitBuildConfigurationForm
BlockPluginTrait::calculateDependencies public function
BlockPluginTrait::getConfiguration public function 1
BlockPluginTrait::getMachineNameSuggestion public function 1
BlockPluginTrait::getPreviewFallbackString public function 3
BlockPluginTrait::label public function
BlockPluginTrait::setConfigurationValue public function
BlockPluginTrait::setTransliteration public function Sets the transliteration service.
BlockPluginTrait::submitConfigurationForm public function Most block plugins should not override this method. To add submission handling for a specific block type, override BlockBase::blockSubmit().
BlockPluginTrait::transliteration protected function Wraps the transliteration service.
BlockPluginTrait::validateConfigurationForm public function Most block plugins should not override this method. To add validation for a specific block type, override BlockBase::blockValidate(). 1
ComponentBlock::$contextHandler private property Context handler.
ComponentBlock::$entityTypeManager protected property Entity type manager.
ComponentBlock::$formatterPluginManager private property Formatter manager.
ComponentBlock::$token private property Token service.
ComponentBlock::$uiPatternsManager private property Plugin manager.
ComponentBlock::blockForm public function Overrides BlockPluginTrait::blockForm
ComponentBlock::blockSubmit public function Overrides BlockPluginTrait::blockSubmit
ComponentBlock::build public function Builds and returns the renderable array for this block plugin. Overrides BlockPluginInterface::build
ComponentBlock::contextHandler protected function Wraps the context handler. Overrides ContextAwarePluginAssignmentTrait::contextHandler
ComponentBlock::create public static function Creates an instance of the plugin. Overrides ContainerFactoryPluginInterface::create
ComponentBlock::defaultConfiguration public function Overrides BlockPluginTrait::defaultConfiguration
ComponentBlock::FIXED constant
ComponentBlock::formatterPluginManager protected function Gets the formatter plugin manager.
ComponentBlock::formatterSettingsProcessCallback public function Render API callback: builds the formatter settings elements.
ComponentBlock::getApplicablePluginOptions protected function Returns an array of applicable formatter options for a field.
ComponentBlock::getCurrentConfiguration protected function Gets the current configuration for given parents.
ComponentBlock::setConfiguration public function Overrides BlockPluginTrait::setConfiguration
ComponentBlock::uiPatternsManager protected function Gets the UI patterns manager.
ComponentBlock::updateElementValue public static function Ajax callback that updates options.
ComponentBlock::__construct public function Constructs a new ComponentBlock. Overrides BlockPluginTrait::__construct
ContextAwarePluginAssignmentTrait::addContextAssignmentElement protected function Builds a form element for assigning a context to a given slot.
ContextAwarePluginTrait::$context protected property The data objects representing the context of this plugin.
ContextAwarePluginTrait::$initializedContextConfig protected property Tracks whether the context has been initialized from configuration.
ContextAwarePluginTrait::getCacheContexts public function 9
ContextAwarePluginTrait::getCacheMaxAge public function 7
ContextAwarePluginTrait::getCacheTags public function 4
ContextAwarePluginTrait::getContext public function
ContextAwarePluginTrait::getContextDefinition public function
ContextAwarePluginTrait::getContextDefinitions public function
ContextAwarePluginTrait::getContextMapping public function
ContextAwarePluginTrait::getContexts public function
ContextAwarePluginTrait::getContextValue public function
ContextAwarePluginTrait::getContextValues public function
ContextAwarePluginTrait::getPluginDefinition abstract protected function 1
ContextAwarePluginTrait::setContext public function 1
ContextAwarePluginTrait::setContextMapping public function
ContextAwarePluginTrait::setContextValue public function
ContextAwarePluginTrait::validateContexts public function
DependencySerializationTrait::$_entityStorages protected property
DependencySerializationTrait::$_serviceIds protected property
DependencySerializationTrait::__sleep public function 2
DependencySerializationTrait::__wakeup public function 2
LayoutBuilderContextTrait::$contextRepository protected property The context repository.
LayoutBuilderContextTrait::contextRepository protected function Gets the context repository service.
LayoutBuilderContextTrait::getAvailableContexts Deprecated protected function Provides all available contexts, both global and section_storage-specific.
LayoutBuilderContextTrait::getPopulatedContexts protected function Returns all populated contexts, both global and section-storage-specific.
MessengerTrait::$messenger protected property The messenger. 27
MessengerTrait::messenger public function Gets the messenger. 27
MessengerTrait::setMessenger public function Sets the messenger.
PluginBase::$configuration protected property Configuration information passed into the plugin. 1
PluginBase::$pluginDefinition protected property The plugin implementation definition. 1
PluginBase::$pluginId protected property The plugin_id.
PluginBase::DERIVATIVE_SEPARATOR constant A string which is used to separate base plugin IDs from the derivative ID.
PluginBase::getBaseId public function Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface::getBaseId
PluginBase::getDerivativeId public function Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface::getDerivativeId
PluginBase::getPluginId public function Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface::getPluginId
PluginBase::isConfigurable public function Determines if the plugin is configurable.
PluginWithFormsTrait::getFormClass public function Implements \Drupal\Core\Plugin\PluginWithFormsInterface::getFormClass().
PluginWithFormsTrait::hasFormClass public function Implements \Drupal\Core\Plugin\PluginWithFormsInterface::hasFormClass().
StringTranslationTrait::$stringTranslation protected property The string translation service. 4
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.