custom_field-1.0.x-dev/src/Plugin/CustomField/FieldType/EntityReference.php

src/Plugin/CustomField/FieldType/EntityReference.php
<?php

declare(strict_types=1);

namespace Drupal\custom_field\Plugin\CustomField\FieldType;

use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\InvokeCommand;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Entity\ContentEntityStorageInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Entity\TypedData\EntityDataDefinition;
use Drupal\Core\Field\FieldException;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\TypedData\DataReferenceDefinition;
use Drupal\custom_field\Attribute\CustomFieldType;
use Drupal\custom_field\Plugin\CustomFieldTypeBase;
use Drupal\custom_field\Plugin\CustomFieldTypeInterface;
use Drupal\custom_field\TypedData\CustomFieldDataDefinition;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Plugin implementation of the 'entity_reference' field type.
 */
#[CustomFieldType(
  id: 'entity_reference',
  label: new TranslatableMarkup('Entity reference'),
  description: new TranslatableMarkup('A field containing an entity reference.'),
  category: new TranslatableMarkup('Reference'),
  default_widget: 'entity_reference_autocomplete',
  default_formatter: 'entity_reference_label',
)]
class EntityReference extends CustomFieldTypeBase {

  /**
   * The entity reference selection plugin manager.
   *
   * @var \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface
   */
  protected SelectionPluginManagerInterface $selectionPluginManager;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
    $instance->selectionPluginManager = $container->get('plugin.manager.entity_reference_selection');

    return $instance;
  }

  /**
   * {@inheritdoc}
   */
  public static function schema(array $settings): array {
    ['name' => $name, 'target_type' => $target_type] = $settings;

    try {
      $target_type_info = \Drupal::entityTypeManager()
        ->getDefinition($target_type);
    }
    catch (PluginNotFoundException $e) {
      throw new FieldException(sprintf("Field '%s' references a target entity type '%s' which does not exist.",
        $name,
        $target_type
      ));
    }
    /** @var array<\Drupal\Core\TypedData\DataDefinitionInterface> $properties */
    $properties = static::propertyDefinitions($settings);
    if ($target_type_info->entityClassImplements(FieldableEntityInterface::class) && $properties[(string) $name]->getSetting('data_type') === 'integer') {
      $columns[$name] = [
        'type' => 'int',
        'description' => 'The ID of the target entity.',
        'unsigned' => TRUE,
      ];
    }
    else {
      $columns[$name] = [
        'type' => 'varchar_ascii',
        'description' => 'The ID of the target entity.',
        // If the target entities act as bundles for another entity type,
        // their IDs should not exceed the maximum length for bundles.
        'length' => $target_type_info->getBundleOf() ? EntityTypeInterface::BUNDLE_MAX_LENGTH : 255,
      ];
    }

    return $columns;
  }

  /**
   * {@inheritdoc}
   */
  public static function propertyDefinitions(array $settings): mixed {
    ['name' => $name, 'target_type' => $target_type] = $settings;
    $target_type_info = \Drupal::entityTypeManager()->getDefinition($target_type);

    // If the target entity type doesn't have an ID key, we cannot determine
    // the target_id data type.
    if (!$target_type_info->hasKey('id')) {
      throw new FieldException('Entity type "' . $target_type_info->id() . '" has no ID key and cannot be targeted by entity reference field "' . $name . '"');
    }

    $target_id_data_type = 'string';
    if ($target_type_info->entityClassImplements(FieldableEntityInterface::class)) {
      $id_definition = \Drupal::service('entity_field.manager')->getBaseFieldDefinitions($target_type)[$target_type_info->getKey('id')];
      if ($id_definition->getType() === 'integer') {
        $target_id_data_type = 'integer';
      }
    }

    $target_id_definition = CustomFieldDataDefinition::create('custom_field_entity_reference')
      ->setLabel(new TranslatableMarkup('@label ID', ['@label' => $name]))
      ->setSetting('data_type', $target_id_data_type)
      ->setSetting('target_type', $target_type)
      ->setRequired(FALSE);

    if ($target_id_data_type === 'integer') {
      $target_id_definition->setSetting('unsigned', TRUE);
    }

    $properties[$name] = $target_id_definition;
    $properties[$name . self::SEPARATOR . 'entity'] = DataReferenceDefinition::create('entity')
      ->setLabel($target_type_info->getLabel())
      ->setDescription(new TranslatableMarkup('The referenced entity'))
      ->setComputed(TRUE)
      ->setSettings(['target_id' => $name, 'target_type' => $target_type])
      ->setClass('\Drupal\custom_field\Plugin\CustomField\EntityReferenceComputed')
      ->setReadOnly(FALSE)
      ->setTargetDefinition(EntityDataDefinition::create($target_type))
      ->addConstraint('EntityType', $target_type);

    return $properties;
  }

  /**
   * {@inheritdoc}
   */
  public static function defaultFieldSettings(): array {
    return [
      'handler_settings' => [],
    ] + parent::defaultFieldSettings();
  }

  /**
   * {@inheritdoc}
   */
  public function fieldSettingsForm(array &$form, FormStateInterface $form_state): array {
    $element = parent::fieldSettingsForm($form, $form_state);
    $settings = $this->getFieldSettings();
    $target_type = $this->getTargetType();
    if (!isset($settings['handler'])) {
      $settings['handler'] = 'default:' . $target_type;
    }
    // Get all selection plugins for this entity type.
    $selection_plugins = $this->selectionPluginManager->getSelectionGroups($target_type);
    $handlers_options = [];
    foreach (array_keys($selection_plugins) as $selection_group_id) {
      // We only display base plugins (e.g. 'default', 'views', ...) and not
      // entity type specific plugins (e.g. 'default:node', 'default:user',
      // ...).
      if (array_key_exists($selection_group_id, $selection_plugins[$selection_group_id])) {
        $label = $selection_plugins[$selection_group_id][$selection_group_id]['label'];
        $handlers_options[$selection_group_id] = Html::escape((string) $label);
      }
      elseif (array_key_exists($selection_group_id . ':' . $target_type, $selection_plugins[$selection_group_id])) {
        $selection_group_plugin = $selection_group_id . ':' . $target_type;
        $label = $selection_plugins[$selection_group_id][$selection_group_plugin]['base_plugin_label'] ?? '';
        $handlers_options[$selection_group_plugin] = Html::escape((string) $label);
      }
    }
    $wrapper_id = 'reference-wrapper-' . $this->getName();
    $element['handler'] = [
      '#type' => 'details',
      '#title' => $this->t('Reference type'),
      '#open' => TRUE,
      '#tree' => TRUE,
      '#process' => [[static::class, 'formProcessMergeParent']],
      '#prefix' => '<div id="' . $wrapper_id . '">',
      '#suffix' => '</div>',
    ];

    $element['handler']['handler'] = [
      '#type' => 'select',
      '#title' => $this->t('Reference method'),
      '#options' => $handlers_options,
      '#default_value' => $settings['handler'],
      '#required' => TRUE,
      '#ajax' => [
        'wrapper' => $wrapper_id,
        'callback' => [static::class, 'actionCallback'],
      ],
    ];

    $element['handler']['handler_submit'] = [
      '#type' => 'submit',
      '#value' => $this->t('Change handler'),
      '#limit_validation_errors' => [],
      '#attributes' => [
        'class' => ['js-hide'],
      ],
      '#submit' => [[static::class, 'settingsAjaxSubmit']],
    ];

    $element['handler']['handler_settings'] = [
      '#type' => 'container',
      '#attributes' => ['class' => ['entity_reference-settings']],
    ];

    $handler = $this->getSelectionHandler($settings, $target_type);
    $configuration_form = $handler ? $handler->buildConfigurationForm([], $form_state) : [];

    // Alter configuration to use our custom callback.
    foreach ($configuration_form as $key => $item) {
      if (isset($item['#limit_validation_errors'])) {
        unset($item['#limit_validation_errors']);
      }
      if (isset($item['#ajax'])) {
        $item['#ajax'] = [
          'wrapper' => $wrapper_id,
          'callback' => [static::class, 'actionCallback'],
        ];
      }
      if (is_array($item)) {
        foreach ($item as $prop_key => $prop) {
          if (!is_array($prop)) {
            continue;
          }
          if (isset($prop['#limit_validation_errors'])) {
            unset($prop['#limit_validation_errors']);
          }
          if (isset($prop['#ajax'])) {
            $prop['#ajax'] = [
              'wrapper' => $wrapper_id,
              'callback' => [static::class, 'actionCallback'],
            ];
          }
          $item[(string) $prop_key] = $prop;
        }
      }
      $configuration_form[(string) $key] = $item;
    }

    $element['handler']['handler_settings'] += $configuration_form;

    return $element;
  }

  /**
   * Render API callback that moves entity reference elements up a level.
   *
   * The elements (i.e. 'handler_settings') are moved for easier processing by
   * the validation and submission handlers.
   *
   * @param array<string, mixed> $element
   *   The form element.
   *
   * @return array<string, mixed>
   *   The modified form element.
   *
   * @see _entity_reference_field_settings_process()
   */
  public static function formProcessMergeParent(array $element): array {
    $parents = $element['#parents'];
    array_pop($parents);
    $element['#parents'] = $parents;
    return $element;
  }

  /**
   * Ajax callback for the handler settings form.
   *
   * @param array|array<string, mixed> $form
   *   The form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   *
   * @return \Drupal\Core\Ajax\AjaxResponse
   *   The form element.
   */
  public static function actionCallback(array &$form, FormStateInterface $form_state): AjaxResponse {
    $triggering_element = $form_state->getTriggeringElement();
    $wrapper_id = $triggering_element['#ajax']['wrapper'];
    $parents = $triggering_element['#array_parents'];
    $sliced_parents = array_slice($parents, 0, 4, TRUE);
    $element = NestedArray::getValue($form, $sliced_parents);
    $response = new AjaxResponse();
    $response->addCommand(new ReplaceCommand('#' . $wrapper_id, $element));
    if (end($parents) === 'handler') {
      $focus_input = $element['handler']['#name'];
      $response->addCommand(new InvokeCommand(':input[name="' . $focus_input . '"]', 'focus'));
    }

    return $response;
  }

  /**
   * Submit handler for the non-JS case.
   *
   * @param array<string, mixed> $form
   *   The form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   *
   * @see static::fieldSettingsForm()
   */
  public static function settingsAjaxSubmit(array $form, FormStateInterface $form_state): void {
    $form_state->setRebuild();
  }

  /**
   * Gets the selection handler for a given entity_reference field.
   *
   * @param array<string, mixed> $settings
   *   An array of field settings.
   * @param string|null $target_type
   *   The target entity type.
   * @param \Drupal\Core\Entity\EntityInterface|null $entity
   *   The entity containing the reference field.
   *
   * @return mixed
   *   The selection handler.
   */
  public function getSelectionHandler(array $settings, ?string $target_type = NULL, ?EntityInterface $entity = NULL): mixed {
    if (!$target_type) {
      return NULL;
    }
    $options = $settings['handler_settings'] ?: [];
    $options += [
      'target_type' => $target_type,
      'handler' => $settings['handler'] ?? 'default:' . $target_type,
      'entity' => $entity,
    ];

    return $this->selectionPluginManager->getInstance($options);
  }

  /**
   * {@inheritdoc}
   */
  public static function calculateDependencies(CustomFieldTypeInterface $item, array $default_value): array {
    $entity_type_manager = \Drupal::entityTypeManager();
    $target_entity_type_id = $item->getTargetType();
    $target_entity_type = $entity_type_manager->getDefinition($target_entity_type_id);
    $field_settings = $item->getFieldSettings();
    $target_bundles = $field_settings['handler_settings']['target_bundles'] ?? [];
    $dependencies = [];
    $field_name = $item->getName();
    // Depend on default values entity types configurations.
    if (!empty($default_value)) {
      foreach ($default_value as $value) {
        if (isset($value[$field_name])) {
          $entity = $entity_type_manager->getStorage($target_entity_type_id)->load($value[$field_name]);
          if ($entity) {
            $dependencies[$entity->getConfigDependencyKey()][] = $entity->getConfigDependencyName();
          }
        }
      }
    }
    // Depend on target bundle configurations. Dependencies for 'target_bundles'
    // also covers the 'auto_create_bundle' setting, if any, because its value
    // is included in the 'target_bundles' list.
    if (!empty($target_bundles)) {
      if ($bundle_entity_type_id = $target_entity_type->getBundleEntityType()) {
        if ($storage = $entity_type_manager->getStorage($bundle_entity_type_id)) {
          foreach ($storage->loadMultiple($target_bundles) as $bundle) {
            $dependencies[$bundle->getConfigDependencyKey()][] = $bundle->getConfigDependencyName();
          }
        }
      }
    }

    return $dependencies;
  }

  /**
   * {@inheritdoc}
   */
  public static function onDependencyRemoval(CustomFieldTypeInterface $item, array $dependencies): array {
    $entity_type_manager = \Drupal::entityTypeManager();
    $bundles_changed = FALSE;
    $target_entity_type_id = $item->getTargetType();
    $target_entity_type = $entity_type_manager->getDefinition($target_entity_type_id);
    $field_settings = $item->getFieldSettings();
    $handler_settings = $field_settings['handler_settings'] ?? [];
    $changed_settings = [];

    if (!empty($handler_settings['target_bundles'])) {
      if ($bundle_entity_type_id = $target_entity_type->getBundleEntityType()) {
        if ($storage = $entity_type_manager->getStorage($bundle_entity_type_id)) {
          foreach ($storage->loadMultiple($handler_settings['target_bundles']) as $bundle) {
            if (isset($dependencies[$bundle->getConfigDependencyKey()][$bundle->getConfigDependencyName()])) {
              unset($handler_settings['target_bundles'][$bundle->id()]);

              // If this bundle is also used in the 'auto_create_bundle'
              // setting, disable the auto-creation feature completely.
              $auto_create_bundle = !empty($handler_settings['auto_create_bundle']) ? $handler_settings['auto_create_bundle'] : FALSE;
              if ($auto_create_bundle && $auto_create_bundle == $bundle->id()) {
                $handler_settings['auto_create'] = FALSE;
                $handler_settings['auto_create_bundle'] = NULL;
              }

              $bundles_changed = TRUE;
            }
          }
        }
      }
    }
    if ($bundles_changed) {
      $field_settings['handler_settings'] = $handler_settings;
      $changed_settings = $field_settings;
    }

    return $changed_settings;
  }

  /**
   * {@inheritdoc}
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  public static function generateSampleValue(CustomFieldTypeInterface $field, string $target_entity_type): mixed {
    $field_settings = $field->getFieldSettings();
    $handler_settings = $field_settings['handler_settings'] ?? [];

    // If the field hasn't been configured yet, return early.
    if (empty($handler_settings)) {
      return NULL;
    }

    $target_type = $field->getTargetType();
    // An associative array keyed by the reference type, target type, and
    // bundle.
    static $recursion_tracker = [];

    $manager = \Drupal::service('plugin.manager.entity_reference_selection');

    // Instead of calling $manager->getSelectionHandler($field_definition)
    // replicate the behavior to be able to override the sorting settings.
    $options = [
      'target_type' => $target_type,
      'handler' => $field_settings['handler'],
      'entity' => NULL,
    ] + $handler_settings;

    $entity_type = \Drupal::entityTypeManager()->getDefinition($options['target_type']);
    $options['sort'] = [
      'field' => $entity_type->getKey('id'),
      'direction' => 'DESC',
    ];

    /** @var \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface $selection_handler */
    $selection_handler = $manager->getInstance($options);

    // Select a random number of references between the last 50 referenceable
    // entities created.
    if ($referenceable = $selection_handler->getReferenceableEntities(NULL, 'CONTAINS', 50)) {
      $group = array_rand($referenceable);

      return array_rand($referenceable[$group]);
    }

    // Attempt to create a sample entity, avoiding recursion.
    $entity_storage = \Drupal::entityTypeManager()->getStorage($options['target_type']);
    if ($entity_storage instanceof ContentEntityStorageInterface) {
      $bundle = static::getRandomBundle($entity_type, $options);

      // Track the generated entity by reference type, target type, and bundle.
      $key = $target_entity_type . ':' . $options['target_type'] . ':' . $bundle;

      // If entity generation was attempted but did not finish, do not continue.
      if (isset($recursion_tracker[$key])) {
        return [];
      }

      // Mark this as an attempt at generation.
      $recursion_tracker[$key] = TRUE;

      // Mark the sample entity as being a preview.
      $entity = $entity_storage->createWithSampleValues($bundle, [
        'in_preview' => TRUE,
      ]);

      // Remove the indicator once the entity is successfully generated.
      unset($recursion_tracker[$key]);
      return ['entity' => $entity];
    }

    return NULL;
  }

  /**
   * Gets a bundle for a given entity type and selection options.
   *
   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
   *   The entity type.
   * @param array $selection_settings
   *   An array of selection settings.
   *
   * @return string|null
   *   Either the bundle string, or NULL if there is no bundle.
   */
  protected static function getRandomBundle(EntityTypeInterface $entity_type, array $selection_settings): ?string {
    if ($entity_type->getKey('bundle')) {
      if (!empty($selection_settings['target_bundles'])) {
        $bundle_ids = $selection_settings['target_bundles'];
      }
      else {
        $bundle_ids = \Drupal::service('entity_type.bundle.info')->getBundleInfo($entity_type->id());
      }
      return (string) array_rand($bundle_ids);
    }

    return NULL;
  }

}

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

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