Commit dfd1d2f6 authored by Jeroen Tubex's avatar Jeroen Tubex
Browse files

Issue #3292445 by JeroenT: Add support for base fields

parent 54530f63
Loading
Loading
Loading
Loading
+8 −3
Original line number Diff line number Diff line
services:
  field_fallback.field_config_form:
    class: \Drupal\field_fallback\Form\FieldFallbackFieldConfigForm
    arguments: ['@entity_type.manager', '@plugin.manager.field_fallback_converter']
    class: Drupal\field_fallback\Form\FieldFallbackFieldConfigForm
    arguments: ['@entity_field.manager', '@entity_type.manager', '@plugin.manager.field_fallback_converter']
  field_fallback.service:
    class: \Drupal\field_fallback\FieldFallbackService
    class: Drupal\field_fallback\FieldFallbackService
    arguments: ['@entity_type.manager', '@plugin.manager.field_fallback_converter', '@language_manager']
  plugin.manager.field_fallback_converter:
    class: Drupal\field_fallback\Plugin\FieldFallbackConverterManager
    arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@entity_type.manager']
  field_fallback.field_storage_subscriber:
    class: Drupal\field_fallback\EventSubscriber\FieldStorageSubscriber
    arguments: ['@field_fallback.service']
    tags:
      - { name: event_subscriber }
+58 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\field_fallback\EventSubscriber;

use Drupal\Core\Field\FieldStorageDefinitionEvent;
use Drupal\Core\Field\FieldStorageDefinitionEvents;
use Drupal\field_fallback\FieldFallbackService;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Subscriber for field storage deletions.
 */
class FieldStorageSubscriber implements EventSubscriberInterface {

  /**
   * The field fallback service.
   *
   * @var \Drupal\field_fallback\FieldFallbackService
   */
  protected $fieldFallbackService;

  /**
   * Constructs a FieldStorageSubscriber object.
   *
   * @param \Drupal\field_fallback\FieldFallbackService $field_fallback_service
   *   The field fallback service.
   */
  public function __construct(FieldFallbackService $field_fallback_service) {
    $this->fieldFallbackService = $field_fallback_service;
  }

  /**
   * Listens to field storage deletions.
   *
   * When a base field is deleted, we check the field_fallback configs that
   * depend on the base field and clean the config.
   *
   * @param \Drupal\Core\Field\FieldStorageDefinitionEvent $event
   *   The triggered event.
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  public function onFieldStorageDeletion(FieldStorageDefinitionEvent $event): void {
    if ($event->getFieldStorageDefinition()->isBaseField()) {
      $this->fieldFallbackService->cleanupConfigBaseFields($event->getFieldStorageDefinition());
    }
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    return [
      FieldStorageDefinitionEvents::DELETE => 'onFieldStorageDeletion',
    ];
  }

}
+53 −15
Original line number Diff line number Diff line
@@ -6,8 +6,9 @@ use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Entity\TranslatableInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\field\FieldConfigInterface;
use Drupal\field_fallback\Plugin\FieldFallbackConverterManagerInterface;

/**
@@ -184,17 +185,49 @@ class FieldFallbackService {
  /**
   * Cleanup the config when a field is deleted.
   *
   * @param \Drupal\field\FieldConfigInterface $deleted_field_config
   *   The field_config that's being deleted.
   * @param \Drupal\Core\Field\FieldDefinitionInterface $deleted_field
   *   The field that's being deleted.
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  public function cleanupConfigFields(FieldConfigInterface $deleted_field_config): void {
    $field_config_ids = $this->getFieldConfigIdsWithFallback(
      $deleted_field_config->getTargetEntityTypeId(),
      $deleted_field_config->getTargetBundle() ?? $deleted_field_config->getTargetEntityTypeId()
  public function cleanupConfigFields(FieldDefinitionInterface $deleted_field): void {
    $this->doCleanupConfigOnDeletion(
      $deleted_field->getTargetEntityTypeId(),
      $deleted_field->getTargetBundle() ?? $deleted_field->getTargetEntityTypeId(),
      $deleted_field->getName()
    );
  }

  /**
   * Cleanup the config when a base field is deleted.
   *
   * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $deleted_base_field
   *   The base field that's being deleted.
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  public function cleanupConfigBaseFields(FieldStorageDefinitionInterface $deleted_base_field): void {
    $this->doCleanupConfigOnDeletion(
      $deleted_base_field->getTargetEntityTypeId(),
      NULL,
      $deleted_base_field->getName()
    );
  }

  /**
   * Method that does the actual cleanup of the field configs.
   *
   * @param string $entity_type_id
   *   The entity type ID.
   * @param string|null $bundle_id
   *   The bundle ID, when available.
   * @param string $deleted_field_name
   *   The name of the field that's being deleted.
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function doCleanupConfigOnDeletion(string $entity_type_id, ?string $bundle_id, string $deleted_field_name): void {
    $field_config_ids = $this->getFieldConfigIdsWithFallback($entity_type_id, $bundle_id);
    if (empty($field_config_ids)) {
      return;
    }
@@ -203,7 +236,7 @@ class FieldFallbackService {
    $field_configs = $this->fieldConfigStorage->loadMultiple($field_config_ids);

    foreach ($field_configs as $field_config) {
      if ($field_config->getThirdPartySetting('field_fallback', 'field') === $deleted_field_config->getName()) {
      if ($field_config->getThirdPartySetting('field_fallback', 'field') === $deleted_field_name) {
        $field_config->unsetThirdPartySetting('field_fallback', 'field');
        $field_config->unsetThirdPartySetting('field_fallback', 'converter');
        $field_config->save();
@@ -216,18 +249,23 @@ class FieldFallbackService {
   *
   * @param string $entity_type_id
   *   The entity type ID.
   * @param string $bundle
   * @param string|null $bundle
   *   The bundle.
   *
   * @return array
   *   An array of field config IDs.
   */
  protected function getFieldConfigIdsWithFallback(string $entity_type_id, string $bundle): array {
    return $this->fieldConfigStorage->getQuery()
      ->condition('entity_type', $entity_type_id)
      ->condition('bundle', $bundle)
      ->condition('third_party_settings.field_fallback.field', NULL, 'IS NOT NULL')
      ->execute();
  protected function getFieldConfigIdsWithFallback(string $entity_type_id, ?string $bundle): array {
    $query = $this->fieldConfigStorage->getQuery();
    $query->condition('entity_type', $entity_type_id);

    if ($bundle !== NULL) {
      $query->condition('bundle', $bundle);
    }

    $query->condition('third_party_settings.field_fallback.field', NULL, 'IS NOT NULL');
    $result = $query->execute();
    return is_array($result) ? $result : [];
  }

}
+43 −15
Original line number Diff line number Diff line
@@ -3,9 +3,12 @@
namespace Drupal\field_fallback\Form;

use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Config\Entity\ThirdPartySettingsInterface;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityFormInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
@@ -21,6 +24,13 @@ class FieldFallbackFieldConfigForm {
  use DependencySerializationTrait;
  use StringTranslationTrait;

  /**
   * The entity field manager service.
   *
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
   */
  protected $entityFieldManager;

  /**
   * The field config storage.
   *
@@ -38,6 +48,8 @@ class FieldFallbackFieldConfigForm {
  /**
   * FieldConfigForm constructor.
   *
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
   *   The entity field manager service.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\field_fallback\Plugin\FieldFallbackConverterManagerInterface $field_fallback_converter_manager
@@ -46,7 +58,8 @@ class FieldFallbackFieldConfigForm {
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager, FieldFallbackConverterManagerInterface $field_fallback_converter_manager) {
  public function __construct(EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager, FieldFallbackConverterManagerInterface $field_fallback_converter_manager) {
    $this->entityFieldManager = $entity_field_manager;
    $this->fieldConfigStorage = $entity_type_manager->getStorage('field_config');
    $this->fieldFallbackConverterManager = $field_fallback_converter_manager;
  }
@@ -103,11 +116,9 @@ class FieldFallbackFieldConfigForm {
    $field_value = isset($user_input['third_party_settings']['field_fallback']['field']) ? $user_input['third_party_settings']['field_fallback']['field'] : ($settings['field'] ?? NULL);

    if (!empty($field_value)) {
      $fallback_field = $this->fieldConfigStorage->load($field_config->getTargetEntityTypeId() . '.' . $field_config->getTargetBundle() . '.' . $field_value);

      if ($fallback_field instanceof FieldConfigInterface) {
        $converter_options = $this->buildConverterOptions($field_config, $fallback_field);
      $converter_options = $this->buildConverterOptions($field_config, $field_value);

      if (!empty($converter_options)) {
        $default_converter = $settings['converter'] ?? NULL;
        if ($default_converter === NULL) {
          $default_converter = count($converter_options) === 1 ? key($converter_options) : NULL;
@@ -270,6 +281,13 @@ class FieldFallbackFieldConfigForm {
      }
    }

    $base_field_definitions = $this->entityFieldManager->getBaseFieldDefinitions($configured_field->getTargetEntityTypeId());
    foreach ($base_field_definitions as $base_field_definition) {
      if (in_array($base_field_definition->getType(), $source_field_types, TRUE) && $this->isFieldConfigApplicable($configured_field, $base_field_definition)) {
        $options[$base_field_definition->getName()] = $base_field_definition->getLabel();
      }
    }

    natcasesort($options);

    return $options;
@@ -280,13 +298,24 @@ class FieldFallbackFieldConfigForm {
   *
   * @param \Drupal\field\FieldConfigInterface $target_field
   *   The target field.
   * @param \Drupal\field\FieldConfigInterface $source_field
   *   The source field on which the value will be based.
   * @param string $source_field_name
   *   The name of the source field on which the value will be based.
   *
   * @return array
   *   An option array containing field configs.
   */
  protected function buildConverterOptions(FieldConfigInterface $target_field, FieldConfigInterface $source_field): array {
  protected function buildConverterOptions(FieldConfigInterface $target_field, string $source_field_name): array {
    $source_field = $this->fieldConfigStorage->load($target_field->getTargetEntityTypeId() . '.' . $target_field->getTargetBundle() . '.' . $source_field_name);

    if (!$source_field instanceof FieldConfigInterface) {
      $base_fields = $this->entityFieldManager->getBaseFieldDefinitions($target_field->getTargetEntityTypeId());
      $source_field = $base_fields[$source_field_name] ?? NULL;
    }

    if ($source_field === NULL) {
      return [];
    }

    $converter_definitions = $this->fieldFallbackConverterManager->getDefinitionsBySourceAndTarget(
        $source_field->getType(),
        $target_field->getType()
@@ -303,30 +332,29 @@ class FieldFallbackFieldConfigForm {
  /**
   * Checks if a field can be configured as a fallback field.
   *
   * @param \Drupal\field\FieldConfigInterface $configured_field
   * @param \Drupal\Core\Field\FieldDefinitionInterface $configured_field
   *   The currently configured field.
   * @param \Drupal\field\FieldConfigInterface $fallback_field
   * @param \Drupal\Core\Field\FieldDefinitionInterface $fallback_field
   *   The fallback field.
   *
   * @return bool
   *   True, when the field can be configured as a fallback field, else FALSE.
   */
  protected function isFieldConfigApplicable(FieldConfigInterface $configured_field, FieldConfigInterface $fallback_field): bool {
  protected function isFieldConfigApplicable(FieldDefinitionInterface $configured_field, FieldDefinitionInterface $fallback_field): bool {
    // Chaining multiple fields is not supported right now.
    if ($fallback_field->getThirdPartySetting('field_fallback', 'field') !== NULL) {
    if ($fallback_field instanceof ThirdPartySettingsInterface && $fallback_field->getThirdPartySetting('field_fallback', 'field') !== NULL) {
      return FALSE;
    }

    // You can't use the same field as a fallback field.
    if ($configured_field->id() === $fallback_field->id()) {
    if ($configured_field->getName() === $fallback_field->getName()) {
      return FALSE;
    }

    // When a field has the current field configured as a fallback, you can't
    // use that field as a fallback field, since that would result in an
    // infinite loop.
    $fallback_field_value = (string) $fallback_field->getThirdPartySetting('field_fallback', 'field');
    if ($fallback_field_value === $configured_field->getName()) {
    if ($fallback_field instanceof ThirdPartySettingsInterface && (string) $fallback_field->getThirdPartySetting('field_fallback', 'field') === $configured_field->getName()) {
      return FALSE;
    }

+4 −4
Original line number Diff line number Diff line
@@ -4,10 +4,10 @@ namespace Drupal\field_fallback\Plugin\FieldFallbackConverter;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\field\FieldConfigInterface;
use Drupal\field_fallback\Plugin\FieldFallbackConverterBase;
use Symfony\Component\DependencyInjection\ContainerInterface;

@@ -75,7 +75,7 @@ class ParagraphsSummaryFieldFallbackConverter extends FieldFallbackConverterBase
        // formatter.
        'value' => \Drupal::service('paragraphs_summary_token.text_summary_builder')->build($field, NULL, $configuration['format']),
        'format' => $configuration['format'] ?? filter_default_format(),
      ]
      ],
    ];
  }

@@ -105,7 +105,7 @@ class ParagraphsSummaryFieldFallbackConverter extends FieldFallbackConverterBase

    $form['format'] = [
      '#type' => 'select',
      '#title' => t('Text format'),
      '#title' => $this->t('Text format'),
      '#options' => $options,
      '#default_value' => $default_format,
      '#access' => count($options) >= 1,
@@ -136,7 +136,7 @@ class ParagraphsSummaryFieldFallbackConverter extends FieldFallbackConverterBase
  /**
   * {@inheritdoc}
   */
  public function isApplicable(FieldConfigInterface $target_field, FieldConfigInterface $source_field): bool {
  public function isApplicable(FieldDefinitionInterface $target_field, FieldDefinitionInterface $source_field): bool {
    return $source_field->getSetting('target_type') === 'paragraph' && $this->moduleHandler->moduleExists('paragraphs_summary_token');
  }

Loading