custom_field-1.0.x-dev/modules/custom_field_entity_browser/src/Plugin/CustomField/FieldWidget/EntityReferenceBrowserWidget.php
modules/custom_field_entity_browser/src/Plugin/CustomField/FieldWidget/EntityReferenceBrowserWidget.php
<?php
declare(strict_types=1);
namespace Drupal\custom_field_entity_browser\Plugin\CustomField\FieldWidget;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Drupal\custom_field\Attribute\CustomFieldWidget;
use Drupal\custom_field\Plugin\CustomField\FieldWidget\EntityReferenceWidgetBase;
use Drupal\custom_field\Plugin\CustomFieldTypeInterface;
use Drupal\entity_browser\Element\EntityBrowserElement;
use Drupal\entity_browser\FieldWidgetDisplayInterface;
use Drupal\entity_browser\FieldWidgetDisplayManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Plugin implementation of the 'entity_reference_entity_browser' field widget.
*
* Modified version of
* \Drupal\entity_browser\Plugin\Field\FieldWidget\EntityReferenceBrowserWidget
* with hardcoded limitation to cardinality = 1.
*/
#[CustomFieldWidget(
id: 'entity_reference_entity_browser',
label: new TranslatableMarkup('Entity browser'),
description: new TranslatableMarkup('Allows you to select items using Entity Browser.'),
category: new TranslatableMarkup('Reference'),
field_types: [
'entity_reference',
],
)]
class EntityReferenceBrowserWidget extends EntityReferenceWidgetBase {
/**
* The cardinality we can support.
*
* @var int
*/
protected const CARDINALITY = 1;
/**
* The depth of the delete button.
*
* This property exists so it can be changed if subclasses.
*
* @var int
*/
protected const DELETE_DEPTH = 4;
/**
* Field widget display plugin manager.
*
* @var \Drupal\entity_browser\FieldWidgetDisplayManager
*/
protected FieldWidgetDisplayManager $fieldDisplayManager;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
$instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$instance->fieldDisplayManager = $container->get('plugin.manager.entity_browser.field_widget_display');
return $instance;
}
/**
* {@inheritdoc}
*/
public static function defaultSettings(): array {
return [
'entity_browser' => NULL,
'open' => FALSE,
'field_widget_display' => 'label',
'field_widget_edit' => TRUE,
'field_widget_remove' => TRUE,
'field_widget_replace' => FALSE,
'field_widget_display_settings' => [],
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function widgetSettingsForm(FormStateInterface $form_state, CustomFieldTypeInterface $field): array {
$element = parent::widgetSettingsForm($form_state, $field);
$settings = $this->getSettings() + self::defaultSettings();
$target_type = $field->getTargetType();
$entity_type = $this->entityTypeManager->getStorage($target_type)->getEntityType();
$browsers = [];
try {
foreach ($this->entityTypeManager->getStorage('entity_browser')->loadMultiple() as $browser) {
$browsers[$browser->id()] = $browser->label();
}
}
catch (\Exception $exception) {
// Silent fail, for now.
}
$element['entity_browser'] = [
'#title' => $this->t('Entity browser'),
'#type' => 'select',
'#default_value' => $settings['entity_browser'],
'#options' => $browsers,
'#empty_option' => $this->t('- Select -'),
'#required' => TRUE,
];
unset($browser, $browsers);
$displays = [];
foreach ($this->fieldDisplayManager->getDefinitions() as $id => $definition) {
try {
$field_widget_display = $this->fieldDisplayManager->createInstance($id);
assert($field_widget_display instanceof FieldWidgetDisplayInterface);
if ($field_widget_display->isApplicable($entity_type)) {
$displays[$id] = $definition['label'];
}
}
catch (\Exception $exception) {
// Silent fail, for now.
}
}
unset($definition, $field_widget_display, $id);
$id = Html::getId($field->getName()) . '-field-widget-display-settings-ajax-wrapper-' . md5($this->getUniqueIdentifier($field));
$element['field_widget_display'] = [
'#title' => $this->t('Entity display plugin'),
'#type' => 'radios',
'#default_value' => $settings['field_widget_display'],
'#options' => $displays,
'#ajax' => [
'callback' => [static::class, 'updateFieldWidgetDisplaySettings'],
'wrapper' => $id,
],
'#limit_validation_errors' => [],
];
if ($settings['field_widget_display']) {
$element['field_widget_display_settings'] = [
'#type' => 'details',
'#title' => $this->t('Entity display plugin configuration'),
'#open' => TRUE,
'#prefix' => '<div id="' . $id . '">',
'#suffix' => '</div>',
'#tree' => TRUE,
];
try {
$value_keys = [
'fields',
$this->fieldName,
'settings_edit_form',
'settings',
'fields',
$field->getName(),
];
$field_widget_display = $this->fieldDisplayManager->createInstance(
$form_state->getValue(
[...$value_keys, 'field_widget_display'],
$settings['field_widget_display']
),
$form_state->getValue(
[...$value_keys, 'field_widget_display_settings'],
$settings['field_widget_display_settings']
) + [
'entity_type' => $target_type,
]
);
assert($field_widget_display instanceof FieldWidgetDisplayInterface);
$element['field_widget_display_settings'] += $field_widget_display->settingsForm($element, $form_state);
}
catch (\Exception $exception) {
// Silent fail, for now.
}
}
$element['field_widget_edit'] = [
'#title' => $this->t('Display Edit button'),
'#type' => 'checkbox',
'#default_value' => $settings['field_widget_edit'],
];
$element['field_widget_remove'] = [
'#title' => $this->t('Display Remove button'),
'#type' => 'checkbox',
'#default_value' => $settings['field_widget_remove'],
];
$element['field_widget_replace'] = [
'#title' => $this->t('Display Replace button'),
'#description' => $this->t('This button will only be displayed if there is a single entity in the current selection.'),
'#type' => 'checkbox',
'#default_value' => $settings['field_widget_replace'],
];
$element['open'] = [
'#title' => $this->t('Show widget details as open by default'),
'#description' => $this->t('If marked, the fieldset container that wraps the browser on the entity form will be loaded initially expanded.'),
'#type' => 'checkbox',
'#default_value' => $settings['open'],
];
return $element;
}
/**
* Ajax callback that updates the field widget display settings fieldset.
*
* @param array<string, mixed> $form
* The form definition for the widget settings.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state of the (entire) configuration form.
*/
public static function updateFieldWidgetDisplaySettings(array $form, FormStateInterface $form_state): mixed {
$array_parents = $form_state->getTriggeringElement()['#array_parents'];
$up_two_levels = array_slice($array_parents, 0, count($array_parents) - 2);
$settings_path = array_merge($up_two_levels, ['field_widget_display_settings']);
return NestedArray::getValue($form, $settings_path);
}
/**
* {@inheritdoc}
*/
public function widget(FieldItemListInterface $items, int $delta, array $element, array &$form, FormStateInterface $form_state, CustomFieldTypeInterface $field): array {
$element = parent::widget($items, $delta, $element, $form, $form_state, $field);
$field_settings = $field->getFieldSettings();
$settings = $this->getSettings() + self::defaultSettings();
$field_name = $items->getFieldDefinition()->getName();
$parents = is_array($form['#parents']) ? $form['#parents'] : [];
$entity = $this->formElementEntity($parents, $items, $delta, $form_state, $field);
// @todo Figure out a better way to enforce a 'clean start' when adding
// new field item for whose delta a value already may have been stored
// in the form state (below), as the field item 'remove' button does not
// exist when building the widget element, so we cannot attach
// 'removeItemSubmit' to it.
$triggering_element = $form_state->getTriggeringElement();
if (isset($triggering_element['#name'])) {
$match = implode('_', [...$parents, $field_name, 'add_more']);
if ($triggering_element['#name'] === $match) {
// If the delta is not present in the user input, it means it is new or
// is being added again. In both cases, we want to start with an empty
// selection.
// By forcing $entity to NULL, a selection stored in the form state (if
// any) will also be set to NULL hereafter.
$user_input = $form_state->getUserInput();
if (!NestedArray::keyExists($user_input, [
...$parents,
$field_name,
$delta,
])) {
$entity = NULL;
}
}
}
// We store current entity ID as we might need them in future requests. If
// some other part of the form triggers an AJAX request with
// #limit_validation_errors we won't have access to the value of the
// target_id element and won't be able to build the form as a result of
// that. This will cause missing submit (Remove, Edit, ...) elements, which
// might result in unpredictable results.
$parent_entity = $items->getEntity();
$form_state_key = static::getFormStateKey("{$parent_entity->getEntityTypeId()}:{$parent_entity->id()}", $field_name, $delta);
$form_state->set($form_state_key, $entity?->id());
$id_string = $this->getUniqueElementId($form, $field_name, $delta, $field->getName());
$hidden_id = Html::getUniqueId($id_string . '-target-id');
$element += [
'#id' => $id_string,
'#type' => 'details',
'#open' => (!is_null($entity) || $settings['open']),
'#required' => $field_settings['required'],
// We are not using Entity browser's hidden element since we maintain
// selected entities in it during the entire process.
'target_id' => [
'#type' => 'hidden',
'#id' => $hidden_id,
// We need to repeat ID here as it is otherwise skipped when rendering.
'#attributes' => [
'id' => $hidden_id,
],
'#default_value' => is_null($entity) ? '' : "{$entity->getEntityTypeId()}:{$entity->id()}",
// #ajax is officially not supported for hidden elements, but if we
// specify event manually, it works.
'#ajax' => [
'callback' => [static::class, 'updateWidgetCallback'],
'wrapper' => $id_string,
'event' => 'entity_browser_value_updated',
],
],
];
// Get configuration required to check entity browser availability.
$cardinality = static::CARDINALITY;
$selection_mode = EntityBrowserElement::SELECTION_MODE_APPEND;
// Enable entity browser if requirements for that are fulfilled.
if (EntityBrowserElement::isEntityBrowserAvailable($selection_mode, $cardinality, (int) !is_null($entity))) {
$persistentData = $this->getPersistentData($field);
$element['entity_browser'] = [
'#type' => 'entity_browser',
'#entity_browser' => $settings['entity_browser'],
'#cardinality' => $cardinality,
'#selection_mode' => $selection_mode,
'#default_value' => $entity,
'#entity_browser_validators' => $persistentData['validators'],
'#widget_context' => $persistentData['widget_context'],
'#custom_hidden_id' => $hidden_id,
'#process' => [
['\Drupal\entity_browser\Element\EntityBrowserElement', 'processEntityBrowser'],
[static::class, 'processEntityBrowser'],
],
];
$element['target_id']['#attributes']['data-entity-browser-available'] = 1;
}
else {
// Allow non-ajax remove button to trigger ajax refresh when
// cardinality.
$element['target_id']['#attributes']['data-entity-browser-visible'] = 0;
}
$element['#attached']['library'][] = 'entity_browser/entity_reference';
if (!is_null($entity)) {
$element['current'] = $this->displayCurrentSelection($id_string, [(string) $items->getName()], $entity, $delta, $field);
}
return $element;
}
/**
* Render API callback: Processes the entity browser element.
*
* @param array<string, mixed> $element
* The element.
*
* @return array<string, mixed>
* The updated element.
*/
public static function processEntityBrowser(array &$element): array {
if (NestedArray::keyExists($element, ['#attached', 'drupalSettings', 'entity_browser'])) {
$uuid = key($element['#attached']['drupalSettings']['entity_browser']);
$element['#attached']['drupalSettings']['entity_browser'][$uuid]['selector'] = '#' . $element['#custom_hidden_id'];
}
return $element;
}
/**
* {@inheritdoc}
*/
public function massageFormValue(mixed $value, array $column): mixed {
if (empty($value['target_id'])) {
return NULL;
}
$value['target_id'] = explode(':', $value['target_id'])[1];
unset($value['current']);
return $value;
}
/**
* AJAX form callback.
*
* @param array<string, mixed> $form
* The form structure where widgets are being attached to. This might be a
* full form structure, or a sub-element of a larger form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return array<string, mixed>
* The form part to update.
*/
public static function updateWidgetCallback(array $form, FormStateInterface $form_state): array {
$trigger = $form_state->getTriggeringElement();
$reopen_browser = FALSE;
// AJAX requests can be triggered by hidden "target_id" element when
// entities are added or by one of the "Remove" buttons. Depending on that
// we need to figure out where root of the widget is in the form structure
// and use this information to return correct part of the form.
$parents = [];
if (
NestedArray::keyExists($trigger, ['#ajax', 'event'])
&& $trigger['#ajax']['event'] === 'entity_browser_value_updated'
) {
$parents = array_slice($trigger['#array_parents'], 0, -1);
}
elseif ($trigger['#type'] === 'submit' && str_ends_with($trigger['#name'], '_entity_browser_remove')) {
$parents = array_slice($trigger['#array_parents'], 0, -static::DELETE_DEPTH);
}
elseif ($trigger['#type'] === 'submit' && str_ends_with($trigger['#name'], '_entity_browser_replace')) {
$parents = array_slice($trigger['#array_parents'], 0, -static::DELETE_DEPTH);
// We need to re-open the browser. Instead of just passing "TRUE", send
// to the JS the unique part of the button's name that needs to be clicked
// on to relaunch the browser.
$reopen_browser = implode('-', array_slice($trigger['#parents'], 0, -static::DELETE_DEPTH));
}
$parents = NestedArray::getValue($form, $parents);
$parents['#attached']['drupalSettings']['entity_browser_reopen_browser'] = $reopen_browser;
return $parents;
}
/**
* Submit callback for replace and remove button.
*
* @param array<string, mixed> $form
* The form structure where widgets are being attached to. This might be a
* full form structure, or a sub-element of a larger form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public static function removeItemSubmit(array $form, FormStateInterface $form_state): void {
$triggering_element = $form_state->getTriggeringElement();
if (!empty($triggering_element['#attributes']['data-entity-id']) && isset($triggering_element['#attributes']['data-row-id'])) {
$array_parents = array_slice($triggering_element['#array_parents'], 0, -static::DELETE_DEPTH);
// Set new value for this widget.
$target_id_element = &NestedArray::getValue($form, array_merge($array_parents, ['target_id']));
$form_state->setValueForElement($target_id_element, '');
$user_input = &$form_state->getUserInput();
NestedArray::setValue($user_input, $target_id_element['#parents'], '');
// Rebuild form.
$form_state->setRebuild();
}
}
/**
* Builds the render array for displaying the current results.
*
* @param string $id
* The ID for the details element and button key prefixes.
* @param string[] $field_parents
* Field parents.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The referenced entity.
* @param int $delta
* The order of this item in the array of sub-elements (0, 1, 2, etc.).
* @param \Drupal\custom_field\Plugin\CustomFieldTypeInterface $field
* The custom field type object.
*
* @return array<string, mixed>
* The render array for the current selection.
*/
protected function displayCurrentSelection(string $id, array $field_parents, EntityInterface $entity, int $delta, CustomFieldTypeInterface $field): array {
$settings = $this->getSettings() + self::defaultSettings();
$name_key = str_replace('-', '_', $id);
$target_entity_type = $field->getTargetType();
$field_widget_display_settings = $settings['field_widget_display_settings'] ?? [];
try {
$field_widget_display = $this->fieldDisplayManager->createInstance(
$settings['field_widget_display'],
$field_widget_display_settings + ['entity_type' => $target_entity_type]
);
assert($field_widget_display instanceof FieldWidgetDisplayInterface);
}
catch (\Exception $exception) {
return [];
}
$classes = [
'entities-list',
Html::cleanCssIdentifier("entity-type--$target_entity_type"),
];
$edit_button_access = $settings['field_widget_edit'] && $entity->access('update', $this->currentUser);
if ($entity->getEntityTypeId() === 'file') {
// On file entities, the "edit" button shouldn't be visible unless
// the module "file_entity" is present, which will allow them to be
// edited on their own form.
$edit_button_access &= $this->moduleHandler->moduleExists('file_entity');
}
$display = $field_widget_display->view($entity);
if (is_string($display)) {
$display = ['#markup' => $display];
}
return [
'#theme_wrappers' => ['container'],
'#attributes' => [
'class' => $classes,
'data-entity-browser-entities-list' => 1,
],
'items' => [
[
'#theme_wrappers' => ['container'],
'#attributes' => [
'class' => ['item-container', Html::getClass($field_widget_display->getPluginId())],
'data-entity-id' => $entity->getEntityTypeId() . ':' . $entity->id(),
'data-row-id' => $delta,
],
'display' => $display,
'remove_button' => [
'#type' => 'submit',
'#value' => $this->t('Remove'),
'#ajax' => [
'callback' => [static::class, 'updateWidgetCallback'],
'wrapper' => $id,
],
'#submit' => [[static::class, 'removeItemSubmit']],
'#name' => $name_key . '_entity_browser_remove',
'#limit_validation_errors' => [array_merge($field_parents, [$field->getName()])],
'#attributes' => [
'data-entity-id' => $entity->getEntityTypeId() . ':' . $entity->id(),
'data-row-id' => $delta,
'class' => ['remove-button'],
],
'#access' => (bool) $settings['field_widget_remove'],
],
'replace_button' => [
'#type' => 'submit',
'#value' => $this->t('Replace'),
'#ajax' => [
'callback' => [static::class, 'updateWidgetCallback'],
'wrapper' => $id,
],
'#submit' => [[static::class, 'removeItemSubmit']],
'#name' => $name_key . '_entity_browser_remove',
'#limit_validation_errors' => [array_merge($field_parents, [$field->getName()])],
'#attributes' => [
'data-entity-id' => $entity->getEntityTypeId() . ':' . $entity->id(),
'data-row-id' => $delta,
'class' => ['replace-button'],
],
'#access' => $settings['field_widget_replace'],
],
'edit_button' => [
'#type' => 'submit',
'#value' => $this->t('Edit'),
'#name' => $name_key . '_entity_browser_edit',
'#ajax' => [
'url' => Url::fromRoute(
'entity_browser.edit_form', [
'entity_type' => $entity->getEntityTypeId(),
'entity' => $entity->id(),
]
),
'options' => [
'query' => [
'details_id' => $id,
],
],
],
'#attributes' => [
'class' => ['edit-button'],
],
'#access' => $edit_button_access,
],
],
],
];
}
/**
* Determines the entity used for the form element.
*
* @param string[] $parents
* The field parents.
* @param \Drupal\Core\Field\FieldItemListInterface $items
* Array of default values for this field.
* @param int $delta
* The order of this item in the array of sub-elements (0, 1, 2, etc.).
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
* @param \Drupal\custom_field\Plugin\CustomFieldTypeInterface $field
* The custom field type object.
*
* @return \Drupal\Core\Entity\EntityInterface|null
* Return the entity if available.
*/
protected function formElementEntity(array $parents, FieldItemListInterface $items, int $delta, FormStateInterface $form_state, CustomFieldTypeInterface $field): ?EntityInterface {
$entity_type = $field->getTargetType();
try {
$entity_storage = $this->entityTypeManager->getStorage($entity_type);
}
catch (\Exception $exception) {
return NULL;
}
// Find IDs from target_id element (it stores selected entities in form).
// This was added to help solve a really edge casey bug in IEF.
if (($target_id_entity = $this->getEntityByTargetId($parents, $items, $delta, $form_state, $field)) !== NULL) {
return $target_id_entity;
}
// Determine if we're submitting and if submit came from this widget.
$is_relevant_submit = FALSE;
if ($trigger = $form_state->getTriggeringElement()) {
// Can be triggered by the hidden target_id element or "Remove" button.
$last_parent = end($trigger['#parents']);
if (in_array($last_parent, ['target_id', 'remove_button', 'replace_button'])) {
// In case there are more instances of this widget on the same page, we
// need to check if the submission came from this instance.
$field_name_key = count($trigger['#parents']) - (static::DELETE_DEPTH + 1);
$is_relevant_submit =
array_key_exists($field_name_key, $trigger['#parents'])
&& ($trigger['#parents'][$field_name_key] === $field->getName())
&& ($trigger['#parents'][$field_name_key - 1] === $delta);
}
}
if ($is_relevant_submit === TRUE) {
// Submit was triggered by hidden "target_id" element when entities were
// added via entity browser.
$parents = [];
if (!empty($trigger['#ajax']['event']) && $trigger['#ajax']['event'] === 'entity_browser_value_updated') {
$parents = $trigger['#parents'];
}
// Submit was triggered by one of the "Remove" buttons. We need to walk
// a few levels up to read the value of the "target_id" element.
elseif ($trigger['#type'] === 'submit' && str_ends_with($trigger['#name'], '_entity_browser_remove')) {
$parents = array_merge(array_slice($trigger['#parents'], 0, -static::DELETE_DEPTH), ['target_id']);
}
$value = ($parents !== []) ? $form_state->getValue($parents) : NULL;
if (is_string($value)) {
return $this->processEntityId($value);
}
return NULL;
}
// ID from a previous request might be saved in the form state.
else {
$parent_entity = $items->getEntity();
$form_state_key = static::getFormStateKey("{$parent_entity->getEntityTypeId()}:{$parent_entity->id()}", $items->getFieldDefinition()->getName(), $delta);
if ($form_state->has($form_state_key)) {
$stored_id = $form_state->get($form_state_key);
if (is_string($stored_id)) {
return $entity_storage->load($stored_id);
}
}
}
// We are loading for the first time, so we need to load any existing values
// that might already exist on the entity.
return $items[$delta]->{$field->getName() . '__entity'};
}
/**
* Get the selected element from the target_id element on form.
*
* @param string[] $parents
* The field parents.
* @param \Drupal\Core\Field\FieldItemListInterface $items
* Array of default values for this field.
* @param int $delta
* The order of this item in the array of sub-elements (0, 1, 2, etc.).
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
* @param \Drupal\custom_field\Plugin\CustomFieldTypeInterface $field
* The custom field type object.
*
* @return \Drupal\Core\Entity\EntityInterface|null
* Return the entity if available.
*/
protected function getEntityByTargetId(array $parents, FieldItemListInterface $items, int $delta, FormStateInterface $form_state, CustomFieldTypeInterface $field): ?EntityInterface {
$target_id_element_path = [...$parents, $items->getName(), $delta, $field->getName(), 'target_id'];
$user_input = $form_state->getUserInput();
if (!NestedArray::keyExists($user_input, $target_id_element_path)) {
return NULL;
}
// @todo Figure out how to avoid using raw user input.
// (this comment is copied from \Drupal\entity_browser\Plugin\Field\FieldWidget\EntityReferenceBrowserWidget::getEntitiesByTargetId)
$value = NestedArray::getValue($user_input, $target_id_element_path);
if (is_string($value)) {
return $this->processEntityId($value);
}
return NULL;
}
/**
* Generate md5 hash using field parent keys array.
*
* @param string[] $field_parents
* The field parents.
*
* @return string
* The hash.
*/
protected function getFieldParentsMd5Hash(array $field_parents): string {
return md5((string) json_encode($field_parents));
}
/**
* Gets data that should persist across Entity Browser renders.
*
* @param \Drupal\custom_field\Plugin\CustomFieldTypeInterface $field
* The custom field type object.
*
* @return array<string, array<string, mixed>>
* Data that should persist after the Entity Browser is rendered.
*/
protected function getPersistentData(CustomFieldTypeInterface $field): array {
$field_settings = $field->getFieldSettings();
$handler = $field_settings['handler_settings'];
return [
'validators' => [
'entity_type' => ['type' => $field->getTargetType()],
],
'widget_context' => [
'target_bundles' => !empty($handler['target_bundles']) ? $handler['target_bundles'] : [],
'target_entity_type' => $field->getTargetType(),
'cardinality' => static::CARDINALITY,
],
];
}
/**
* Returns a unique identifier for the field.
*
* Based on \Drupal\Core\Field\FieldDefinition::getUniqueIdentifier.
*
* @param \Drupal\custom_field\Plugin\CustomFieldTypeInterface $field
* The custom field type object.
*
* @return string
* The identifier.
*/
protected function getUniqueIdentifier(CustomFieldTypeInterface $field): string {
return $field->getDataType() . '-' . $field->getTargetType() . '-' . $field->getName();
}
/**
* Processes 'raw' entity ID input and loads the corresponding entity.
*
* @param string $user_input
* The string containing the entity type and ID.
*
* @return \Drupal\Core\Entity\EntityInterface|null
* Return the entity if available.
*/
protected function processEntityId(string $user_input): ?EntityInterface {
$entities = EntityBrowserElement::processEntityIds($user_input);
return $entities !== [] ? reset($entities) : NULL;
}
/**
* Returns a key used to store the previously loaded entity.
*
* @param string $id
* The 'raw' entity ID of the parent entity.
* @param string $field_name
* The (custom field) field name on the parent entity.
* @param int $delta
* The order of this item in the array of sub-elements (0, 1, 2, etc.).
*
* @return string[]
* A key for form state storage.
*/
protected static function getFormStateKey(string $id, string $field_name, int $delta): array {
$parts = [
$id,
$field_name,
$delta,
];
return ['entity_browser_widget', implode(':', $parts)];
}
/**
* {@inheritdoc}
*/
public function calculateWidgetDependencies(): array {
$dependencies = parent::calculateWidgetDependencies();
$browser = $this->getSetting('entity_browser') ?? NULL;
if ($browser) {
/** @var \Drupal\entity_browser\Entity\EntityBrowser $entity_browser */
$entity_browser = $this->entityTypeManager->getStorage('entity_browser')->load($browser);
if ($entity_browser) {
$dependencies[$entity_browser->getConfigDependencyKey()][] = $entity_browser->getConfigDependencyName();
}
}
return $dependencies;
}
/**
* {@inheritdoc}
*/
public function onWidgetDependencyRemoval(array $dependencies): array {
$settings = $this->getSettings();
$changed = FALSE;
$changed_settings = [];
$browser = $this->getSetting('entity_browser') ?? NULL;
if ($browser) {
/** @var \Drupal\entity_browser\Entity\EntityBrowser $entity_browser */
$entity_browser = $this->entityTypeManager->getStorage('entity_browser')->load($browser);
if ($entity_browser && !empty($dependencies[$entity_browser->getConfigDependencyKey()][$entity_browser->getConfigDependencyName()])) {
$settings['entity_browser'] = NULL;
$changed = TRUE;
}
}
if ($changed) {
$changed_settings = $settings;
}
return $changed_settings;
}
}
