class EntityReferenceBrowserWidget in Entity Browser 8
Same name and namespace in other branches
- 8.2 src/Plugin/Field/FieldWidget/EntityReferenceBrowserWidget.php \Drupal\entity_browser\Plugin\Field\FieldWidget\EntityReferenceBrowserWidget
Plugin implementation of the 'entity_reference' widget for entity browser.
Plugin annotation
@FieldWidget(
id = "entity_browser_entity_reference",
label = @Translation("Entity browser"),
description = @Translation("Uses entity browser to select entities."),
multiple_values = TRUE,
field_types = {
"entity_reference"
}
)
Hierarchy
- class \Drupal\Component\Plugin\PluginBase implements DerivativeInspectionInterface, PluginInspectionInterface
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
- class \Drupal\Core\Field\PluginSettingsBase implements DependentPluginInterface, PluginSettingsInterface
- class \Drupal\Core\Field\WidgetBase implements WidgetInterface, ContainerFactoryPluginInterface uses AllowedTagsXssTrait
- class \Drupal\entity_browser\Plugin\Field\FieldWidget\EntityReferenceBrowserWidget implements ContainerFactoryPluginInterface
- class \Drupal\Core\Field\WidgetBase implements WidgetInterface, ContainerFactoryPluginInterface uses AllowedTagsXssTrait
- class \Drupal\Core\Field\PluginSettingsBase implements DependentPluginInterface, PluginSettingsInterface
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
Expanded class hierarchy of EntityReferenceBrowserWidget
File
- src/
Plugin/ Field/ FieldWidget/ EntityReferenceBrowserWidget.php, line 41
Namespace
Drupal\entity_browser\Plugin\Field\FieldWidgetView source
class EntityReferenceBrowserWidget extends WidgetBase implements ContainerFactoryPluginInterface {
/**
* Entity type manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Field widget display plugin manager.
*
* @var \Drupal\entity_browser\FieldWidgetDisplayManager
*/
protected $fieldDisplayManager;
/**
* The depth of the delete button.
*
* This property exists so it can be changed if subclasses.
*
* @var int
*/
protected static $deleteDepth = 4;
/**
* The module handler interface.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Constructs widget plugin.
*
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The definition of the field to which the widget is associated.
* @param array $settings
* The widget settings.
* @param array $third_party_settings
* Any third party settings.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* Entity type manager service.
* @param \Drupal\entity_browser\FieldWidgetDisplayManager $field_display_manager
* Field widget display plugin manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler service.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*/
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, EntityTypeManagerInterface $entity_type_manager, FieldWidgetDisplayManager $field_display_manager, ModuleHandlerInterface $module_handler, AccountInterface $current_user, MessengerInterface $messenger) {
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
$this->entityTypeManager = $entity_type_manager;
$this->fieldDisplayManager = $field_display_manager;
$this->moduleHandler = $module_handler;
$this->currentUser = $current_user;
$this->messenger = $messenger;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($plugin_id, $plugin_definition, $configuration['field_definition'], $configuration['settings'], $configuration['third_party_settings'], $container
->get('entity_type.manager'), $container
->get('plugin.manager.entity_browser.field_widget_display'), $container
->get('module_handler'), $container
->get('current_user'), $container
->get('messenger'));
}
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
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' => [],
'selection_mode' => EntityBrowserElement::SELECTION_MODE_APPEND,
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$element = parent::settingsForm($form, $form_state);
$browsers = [];
/** @var \Drupal\entity_browser\EntityBrowserInterface $browser */
foreach ($this->entityTypeManager
->getStorage('entity_browser')
->loadMultiple() as $browser) {
$browsers[$browser
->id()] = $browser
->label();
}
$element['entity_browser'] = [
'#title' => $this
->t('Entity browser'),
'#type' => 'select',
'#default_value' => $this
->getSetting('entity_browser'),
'#options' => $browsers,
];
$target_type = $this->fieldDefinition
->getFieldStorageDefinition()
->getSetting('target_type');
$entity_type = $this->entityTypeManager
->getStorage($target_type)
->getEntityType();
$displays = [];
foreach ($this->fieldDisplayManager
->getDefinitions() as $id => $definition) {
if ($this->fieldDisplayManager
->createInstance($id)
->isApplicable($entity_type)) {
$displays[$id] = $definition['label'];
}
}
$id = Html::getId($this->fieldDefinition
->getName()) . '-field-widget-display-settings-ajax-wrapper-' . md5($this->fieldDefinition
->getUniqueIdentifier());
$element['field_widget_display'] = [
'#title' => $this
->t('Entity display plugin'),
'#type' => 'radios',
'#default_value' => $this
->getSetting('field_widget_display'),
'#options' => $displays,
'#ajax' => [
'callback' => [
get_class($this),
'updateFieldWidgetDisplaySettings',
],
'wrapper' => $id,
],
'#limit_validation_errors' => [],
];
if ($this
->getSetting('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,
];
$element['field_widget_display_settings'] += $this->fieldDisplayManager
->createInstance($form_state
->getValue([
'fields',
$this->fieldDefinition
->getName(),
'settings_edit_form',
'settings',
'field_widget_display',
], $this
->getSetting('field_widget_display')), $form_state
->getValue([
'fields',
$this->fieldDefinition
->getName(),
'settings_edit_form',
'settings',
'field_widget_display_settings',
], $this
->getSetting('field_widget_display_settings')) + [
'entity_type' => $this->fieldDefinition
->getFieldStorageDefinition()
->getSetting('target_type'),
])
->settingsForm($form, $form_state);
}
$edit_button_access = TRUE;
if ($entity_type
->id() == 'file') {
// For entities of type "file", it only makes sense to have the edit
// button if the module "file_entity" is present.
$edit_button_access = $this->moduleHandler
->moduleExists('file_entity');
}
$element['field_widget_edit'] = [
'#title' => $this
->t('Display Edit button'),
'#type' => 'checkbox',
'#default_value' => $this
->getSetting('field_widget_edit'),
'#access' => $edit_button_access,
];
$element['field_widget_remove'] = [
'#title' => $this
->t('Display Remove button'),
'#type' => 'checkbox',
'#default_value' => $this
->getSetting('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' => $this
->getSetting('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' => $this
->getSetting('open'),
];
$element['selection_mode'] = [
'#title' => $this
->t('Selection mode'),
'#description' => $this
->t('Determines how selection in entity browser will be handled. Will selection be appended/prepended or it will be replaced in case of editing.'),
'#type' => 'select',
'#options' => EntityBrowserElement::getSelectionModeOptions(),
'#default_value' => $this
->getSetting('selection_mode'),
];
$element['#element_validate'] = [
[
get_class($this),
'validateSettingsForm',
],
];
return $element;
}
/**
* Validate the settings form.
*/
public static function validateSettingsForm($element, FormStateInterface $form_state, $form) {
$values = NestedArray::getValue($form_state
->getValues(), $element['#parents']);
if ($values['selection_mode'] == 'selection_edit') {
/** @var \Drupal\entity_browser\Entity\EntityBrowser $entity_browser */
$entity_browser = EntityBrowser::load($values['entity_browser']);
if ($entity_browser
->getSelectionDisplay()
->supportsPreselection() === FALSE) {
$tparams = [
'%selection_mode' => EntityBrowserElement::getSelectionModeOptions()[EntityBrowserElement::SELECTION_MODE_EDIT],
'@browser_link' => $entity_browser
->toLink($entity_browser
->label(), 'edit-form')
->toString(),
];
$form_state
->setError($element['entity_browser']);
$form_state
->setError($element['selection_mode'], t('The selection mode %selection_mode requires an entity browser with a selection display plugin that supports preselection. Either change the selection mode or update the @browser_link entity browser to use a selection display plugin that supports preselection.', $tparams));
}
}
}
/**
* Ajax callback that updates field widget display settings fieldset.
*/
public static function updateFieldWidgetDisplaySettings(array $form, FormStateInterface $form_state) {
$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',
]);
$settingsElement = NestedArray::getValue($form, $settings_path);
return $settingsElement;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = $this
->summaryBase();
$field_widget_display = $this
->getSetting('field_widget_display');
if (!empty($field_widget_display)) {
$pluginDefinition = $this->fieldDisplayManager
->getDefinition($field_widget_display);
$field_widget_display_settings = $this
->getSetting('field_widget_display_settings');
$field_widget_display_settings += [
'entity_type' => $this->fieldDefinition
->getFieldStorageDefinition()
->getSetting('target_type'),
];
$plugin = $this->fieldDisplayManager
->createInstance($field_widget_display, $field_widget_display_settings);
$summary[] = $this
->t('Entity display: @name', [
'@name' => $pluginDefinition['label'],
]);
if ($field_widget_display == 'rendered_entity') {
$view_mode_label = $plugin
->getViewModeLabel();
$summary[] = $this
->t('View Mode: @view_mode', [
'@view_mode' => $view_mode_label,
]);
}
}
return $summary;
}
/**
* {@inheritdoc}
*/
public function flagErrors(FieldItemListInterface $items, ConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) {
if ($violations
->count() > 0) {
/** @var \Symfony\Component\Validator\ConstraintViolation $violation */
foreach ($violations as $offset => $violation) {
// The value of the required field is checked through the "not null"
// constraint, whose message is not very useful. We override it here for
// better UX.
if ($violation
->getConstraint() instanceof NotNullConstraint) {
$violations
->set($offset, new ConstraintViolation($this
->t('@name field is required.', [
'@name' => $items
->getFieldDefinition()
->getLabel(),
]), '', [], $violation
->getRoot(), $violation
->getPropertyPath(), $violation
->getInvalidValue(), $violation
->getPlural(), $violation
->getCode(), $violation
->getConstraint(), $violation
->getCause()));
}
}
}
parent::flagErrors($items, $violations, $form, $form_state);
}
/**
* Returns a key used to store the previously loaded entity.
*
* @param \Drupal\Core\Field\FieldItemListInterface $items
* The field items.
*
* @return string
* A key for form state storage.
*/
protected function getFormStateKey(FieldItemListInterface $items) {
return $items
->getEntity()
->uuid() . ':' . $items
->getFieldDefinition()
->getName();
}
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$entities = $this
->formElementEntities($items, $element, $form_state);
// Get correct ordered list of entity IDs.
$ids = array_map(function (EntityInterface $entity) {
return $entity
->id();
}, $entities);
// We store current entity IDs 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.
$form_state
->set([
'entity_browser_widget',
$this
->getFormStateKey($items),
], $ids);
$hidden_id = Html::getUniqueId('edit-' . $this->fieldDefinition
->getName() . '-target-id');
$details_id = Html::getUniqueId('edit-' . $this->fieldDefinition
->getName());
$element += [
'#id' => $details_id,
'#type' => 'details',
'#open' => !empty($entities) || $this
->getSetting('open'),
'#required' => $this->fieldDefinition
->isRequired(),
// We are not using Entity browser's hidden element since we maintain
// selected entities in it during 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' => implode(' ', array_map(function (EntityInterface $item) {
return $item
->getEntityTypeId() . ':' . $item
->id();
}, $entities)),
// #ajax is officially not supported for hidden elements but if we
// specify event manually it works.
'#ajax' => [
'callback' => [
get_class($this),
'updateWidgetCallback',
],
'wrapper' => $details_id,
'event' => 'entity_browser_value_updated',
],
],
];
// Get configuration required to check entity browser availability.
$cardinality = $this->fieldDefinition
->getFieldStorageDefinition()
->getCardinality();
$selection_mode = $this
->getSetting('selection_mode');
// Enable entity browser if requirements for that are fulfilled.
if (EntityBrowserElement::isEntityBrowserAvailable($selection_mode, $cardinality, count($ids))) {
$persistentData = $this
->getPersistentData();
$element['entity_browser'] = [
'#type' => 'entity_browser',
'#entity_browser' => $this
->getSetting('entity_browser'),
'#cardinality' => $cardinality,
'#selection_mode' => $selection_mode,
'#default_value' => $entities,
'#entity_browser_validators' => $persistentData['validators'],
'#widget_context' => $persistentData['widget_context'],
'#custom_hidden_id' => $hidden_id,
'#process' => [
[
'\\Drupal\\entity_browser\\Element\\EntityBrowserElement',
'processEntityBrowser',
],
[
get_called_class(),
'processEntityBrowser',
],
],
];
}
$element['#attached']['library'][] = 'entity_browser/entity_reference';
$field_parents = $element['#field_parents'];
$element['current'] = $this
->displayCurrentSelection($details_id, $field_parents, $entities);
return $element;
}
/**
* Render API callback: Processes the entity browser element.
*/
public static function processEntityBrowser(&$element, FormStateInterface $form_state, &$complete_form) {
$uuid = key($element['#attached']['drupalSettings']['entity_browser']);
$element['#attached']['drupalSettings']['entity_browser'][$uuid]['selector'] = '#' . $element['#custom_hidden_id'];
return $element;
}
/**
* {@inheritdoc}
*/
public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
$entities = empty($values['target_id']) ? [] : explode(' ', trim($values['target_id']));
$return = [];
foreach ($entities as $entity) {
$return[]['target_id'] = explode(':', $entity)[1];
}
return $return;
}
/**
* AJAX form callback.
*/
public static function updateWidgetCallback(array &$form, FormStateInterface $form_state) {
$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 (!empty($trigger['#ajax']['event']) && $trigger['#ajax']['event'] == 'entity_browser_value_updated') {
$parents = array_slice($trigger['#array_parents'], 0, -1);
}
elseif ($trigger['#type'] == 'submit' && strpos($trigger['#name'], '_remove_')) {
$parents = array_slice($trigger['#array_parents'], 0, -static::$deleteDepth);
}
elseif ($trigger['#type'] == 'submit' && strpos($trigger['#name'], '_replace_')) {
$parents = array_slice($trigger['#array_parents'], 0, -static::$deleteDepth);
// 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::$deleteDepth));
}
$parents = NestedArray::getValue($form, $parents);
$parents['#attached']['drupalSettings']['entity_browser_reopen_browser'] = $reopen_browser;
return $parents;
}
/**
* {@inheritdoc}
*/
public function errorElement(array $element, ConstraintViolationInterface $violation, array $form, FormStateInterface $form_state) {
if ($trigger = $form_state
->getTriggeringElement()) {
// Can be triggered by "Remove" button.
if (end($trigger['#parents']) === 'remove_button') {
return FALSE;
}
}
return parent::errorElement($element, $violation, $form, $form_state);
}
/**
* Submit callback for remove buttons.
*/
public static function removeItemSubmit(&$form, FormStateInterface $form_state) {
$triggering_element = $form_state
->getTriggeringElement();
if (!empty($triggering_element['#attributes']['data-entity-id']) && isset($triggering_element['#attributes']['data-row-id'])) {
$id = $triggering_element['#attributes']['data-entity-id'];
$row_id = $triggering_element['#attributes']['data-row-id'];
$parents = array_slice($triggering_element['#parents'], 0, -static::$deleteDepth);
$array_parents = array_slice($triggering_element['#array_parents'], 0, -static::$deleteDepth);
// Find and remove correct entity.
$values = explode(' ', $form_state
->getValue(array_merge($parents, [
'target_id',
])));
foreach ($values as $index => $item) {
// @todo add weight field.
if ($item == $id) {
array_splice($values, $index, 1);
break;
}
}
$target_id_value = implode(' ', $values);
// Set new value for this widget.
$target_id_element =& NestedArray::getValue($form, array_merge($array_parents, [
'target_id',
]));
$form_state
->setValueForElement($target_id_element, $target_id_value);
NestedArray::setValue($form_state
->getUserInput(), $target_id_element['#parents'], $target_id_value);
// Rebuild form.
$form_state
->setRebuild();
}
}
/**
* Builds the render array for displaying the current results.
*
* @param string $details_id
* The ID for the details element.
* @param string[] $field_parents
* Field parents.
* @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
* Array of referenced entities.
*
* @return array
* The render array for the current selection.
*/
protected function displayCurrentSelection($details_id, array $field_parents, array $entities) {
$target_entity_type = $this->fieldDefinition
->getFieldStorageDefinition()
->getSetting('target_type');
$field_widget_display = $this->fieldDisplayManager
->createInstance($this
->getSetting('field_widget_display'), $this
->getSetting('field_widget_display_settings') + [
'entity_type' => $this->fieldDefinition
->getFieldStorageDefinition()
->getSetting('target_type'),
]);
$classes = [
'entities-list',
Html::cleanCssIdentifier("entity-type--{$target_entity_type}"),
];
if ($this->fieldDefinition
->getFieldStorageDefinition()
->getCardinality() != 1) {
$classes[] = 'sortable';
}
// The "Replace" button will only be shown if this setting is enabled in the
// widget, and there is only one entity in the current selection.
$replace_button_access = $this
->getSetting('field_widget_replace') && count($entities) === 1;
return [
'#theme_wrappers' => [
'container',
],
'#attributes' => [
'class' => $classes,
],
'#prefix' => '<p>' . $this
->getCardinalityMessage($entities) . '</p>',
'items' => array_map(function (ContentEntityInterface $entity, $row_id) use ($field_widget_display, $details_id, $field_parents, $replace_button_access) {
$display = $field_widget_display
->view($entity);
$edit_button_access = $this
->getSetting('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');
}
if (is_string($display)) {
$display = [
'#markup' => $display,
];
}
return [
'#theme_wrappers' => [
'container',
],
'#attributes' => [
'class' => [
'item-container',
Html::getClass($field_widget_display
->getPluginId()),
],
'data-entity-id' => $entity
->getEntityTypeId() . ':' . $entity
->id(),
'data-row-id' => $row_id,
],
'display' => $display,
'remove_button' => [
'#type' => 'submit',
'#value' => $this
->t('Remove'),
'#ajax' => [
'callback' => [
get_class($this),
'updateWidgetCallback',
],
'wrapper' => $details_id,
],
'#submit' => [
[
get_class($this),
'removeItemSubmit',
],
],
'#name' => $this->fieldDefinition
->getName() . '_remove_' . $entity
->id() . '_' . $row_id . '_' . md5(json_encode($field_parents)),
'#limit_validation_errors' => [
array_merge($field_parents, [
$this->fieldDefinition
->getName(),
]),
],
'#attributes' => [
'data-entity-id' => $entity
->getEntityTypeId() . ':' . $entity
->id(),
'data-row-id' => $row_id,
'class' => [
'remove-button',
],
],
'#access' => (bool) $this
->getSetting('field_widget_remove'),
],
'replace_button' => [
'#type' => 'submit',
'#value' => $this
->t('Replace'),
'#ajax' => [
'callback' => [
get_class($this),
'updateWidgetCallback',
],
'wrapper' => $details_id,
],
'#submit' => [
[
get_class($this),
'removeItemSubmit',
],
],
'#name' => $this->fieldDefinition
->getName() . '_replace_' . $entity
->id() . '_' . $row_id . '_' . md5(json_encode($field_parents)),
'#limit_validation_errors' => [
array_merge($field_parents, [
$this->fieldDefinition
->getName(),
]),
],
'#attributes' => [
'data-entity-id' => $entity
->getEntityTypeId() . ':' . $entity
->id(),
'data-row-id' => $row_id,
'class' => [
'replace-button',
],
],
'#access' => $replace_button_access,
],
'edit_button' => [
'#type' => 'submit',
'#value' => $this
->t('Edit'),
'#name' => $this->fieldDefinition
->getName() . '_edit_button_' . $entity
->id() . '_' . $row_id . '_' . md5(json_encode($field_parents)),
'#ajax' => [
'url' => Url::fromRoute('entity_browser.edit_form', [
'entity_type' => $entity
->getEntityTypeId(),
'entity' => $entity
->id(),
]),
'options' => [
'query' => [
'details_id' => $details_id,
],
],
],
'#attributes' => [
'class' => [
'edit-button',
],
],
'#access' => $edit_button_access,
],
];
}, $entities, empty($entities) ? [] : range(0, count($entities) - 1)),
];
}
/**
* Generates a message informing the user how many more items they can choose.
*
* @param array|int $selected
* The current selections, or how many items are selected.
*
* @return string
* A message informing the user who many more items they can select.
*/
protected function getCardinalityMessage($selected) {
$message = NULL;
$storage = $this->fieldDefinition
->getFieldStorageDefinition();
$cardinality = $storage
->getCardinality();
$target_type = $storage
->getSetting('target_type');
$target_type = $this->entityTypeManager
->getDefinition($target_type);
if (is_array($selected)) {
$selected = count($selected);
}
if ($cardinality === 1 && $selected === 0) {
$message = $this
->t('You can select one @entity_type.', [
'@entity_type' => $target_type
->getSingularLabel(),
]);
}
elseif ($cardinality >= $selected) {
$message = $this
->t('You can select up to @maximum @entity_type (@remaining left).', [
'@maximum' => $cardinality,
'@entity_type' => $target_type
->getPluralLabel(),
'@remaining' => $cardinality - $selected,
]);
}
return (string) $message;
}
/**
* Gets data that should persist across Entity Browser renders.
*
* @return array
* Data that should persist after the Entity Browser is rendered.
*/
protected function getPersistentData() {
$settings = $this->fieldDefinition
->getSettings();
$handler = $settings['handler_settings'];
return [
'validators' => [
'entity_type' => [
'type' => $settings['target_type'],
],
],
'widget_context' => [
'target_bundles' => !empty($handler['target_bundles']) ? $handler['target_bundles'] : [],
'target_entity_type' => $settings['target_type'],
'cardinality' => $this->fieldDefinition
->getFieldStorageDefinition()
->getCardinality(),
],
];
}
/**
* Gets options that define where newly added entities are inserted.
*
* @return array
* Mode labels indexed by key.
*/
protected function selectionModeOptions() {
return [
'append' => $this
->t('Append'),
'prepend' => $this
->t('Prepend'),
];
}
/**
* Provides base for settings summary shared by all EB widgets.
*
* @return array
* A short summary of the widget settings.
*/
protected function summaryBase() {
$summary = [];
$entity_browser_id = $this
->getSetting('entity_browser');
if (empty($entity_browser_id)) {
return [
$this
->t('No entity browser selected.'),
];
}
else {
if ($browser = $this->entityTypeManager
->getStorage('entity_browser')
->load($entity_browser_id)) {
$summary[] = $this
->t('Entity browser: @browser', [
'@browser' => $browser
->label(),
]);
}
else {
$this->messenger
->addError($this
->t('Missing entity browser!'));
return [
$this
->t('Missing entity browser!'),
];
}
}
$selection_mode = $this
->getSetting('selection_mode');
$selection_mode_options = EntityBrowserElement::getSelectionModeOptions();
if (isset($selection_mode_options[$selection_mode])) {
$summary[] = $this
->t('Selection mode: @selection_mode', [
'@selection_mode' => $selection_mode_options[$selection_mode],
]);
}
else {
$summary[] = $this
->t('Undefined selection mode.');
}
return $summary;
}
/**
* Determines the entities used for the form element.
*
* @param \Drupal\Core\Field\FieldItemListInterface $items
* The field item to extract the entities from.
* @param array $element
* The form element.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*
* @return \Drupal\Core\Entity\EntityInterface[]
* The list of entities for the form element.
*/
protected function formElementEntities(FieldItemListInterface $items, array $element, FormStateInterface $form_state) {
$entities = [];
$entity_type = $this->fieldDefinition
->getFieldStorageDefinition()
->getSetting('target_type');
$entity_storage = $this->entityTypeManager
->getStorage($entity_type);
// 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_entities = $this
->getEntitiesByTargetId($element, $form_state)) !== FALSE) {
return $target_id_entities;
}
// 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 hidden target_id element or "Remove" button.
$last_parent = end($trigger['#parents']);
if (in_array($last_parent, [
'target_id',
'remove_button',
'replace_button',
])) {
$is_relevant_submit = TRUE;
// In case there are more instances of this widget on the same page we
// need to check if submit came from this instance.
$field_name_key = end($trigger['#parents']) === 'target_id' ? 2 : static::$deleteDepth + 1;
$field_name_key = count($trigger['#parents']) - $field_name_key;
$is_relevant_submit &= $trigger['#parents'][$field_name_key] === $this->fieldDefinition
->getName() && array_slice($trigger['#parents'], 0, count($element['#field_parents'])) == $element['#field_parents'];
}
}
if ($is_relevant_submit) {
// Submit was triggered by hidden "target_id" element when entities were
// added via entity browser.
if (!empty($trigger['#ajax']['event']) && $trigger['#ajax']['event'] == 'entity_browser_value_updated') {
$parents = $trigger['#parents'];
}
elseif ($trigger['#type'] == 'submit' && strpos($trigger['#name'], $this->fieldDefinition
->getName() . '_remove_') === 0) {
$parents = array_merge(array_slice($trigger['#parents'], 0, -static::$deleteDepth), [
'target_id',
]);
}
if (isset($parents) && ($value = $form_state
->getValue($parents))) {
$entities = EntityBrowserElement::processEntityIds($value);
return $entities;
}
return $entities;
}
elseif ($form_state
->has([
'entity_browser_widget',
$this
->getFormStateKey($items),
])) {
$stored_ids = $form_state
->get([
'entity_browser_widget',
$this
->getFormStateKey($items),
]);
$indexed_entities = $entity_storage
->loadMultiple($stored_ids);
// Selection can contain same entity multiple times. Since loadMultiple()
// returns unique list of entities, it's necessary to recreate list of
// entities in order to preserve selection of duplicated entities.
foreach ($stored_ids as $entity_id) {
if (isset($indexed_entities[$entity_id])) {
$entities[] = $indexed_entities[$entity_id];
}
}
return $entities;
}
else {
foreach ($items as $item) {
if (isset($item->target_id)) {
$entity = $entity_storage
->load($item->target_id);
if (!empty($entity)) {
$entities[] = $entity;
}
}
}
return $entities;
}
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
$dependencies = parent::calculateDependencies();
// If an entity browser is being used in this widget, add it as a config
// dependency.
if ($browser_name = $this
->getSetting('entity_browser')) {
$dependencies['config'][] = 'entity_browser.browser.' . $browser_name;
}
return $dependencies;
}
/**
* Get selected elements from target_id element on form.
*
* @param array $element
* The form element.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*
* @return \Drupal\Core\Entity\EntityInterface[]|false
* Return list of entities if they are available or false.
*/
protected function getEntitiesByTargetId(array $element, FormStateInterface $form_state) {
$target_id_element_path = array_merge($element['#field_parents'], [
$this->fieldDefinition
->getName(),
'target_id',
]);
$user_input = $form_state
->getUserInput();
$ief_submit = !empty($user_input['_triggering_element_name']) && strpos($user_input['_triggering_element_name'], 'ief-edit-submit') === 0;
if (!$ief_submit || !NestedArray::keyExists($form_state
->getUserInput(), $target_id_element_path)) {
return FALSE;
}
// TODO Figure out how to avoid using raw user input.
$current_user_input = NestedArray::getValue($form_state
->getUserInput(), $target_id_element_path);
if (!is_array($current_user_input)) {
$entities = EntityBrowserElement::processEntityIds($current_user_input);
return $entities;
}
return FALSE;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
AllowedTagsXssTrait:: |
public | function | Returns a list of tags allowed by AllowedTagsXssTrait::fieldFilterXss(). | |
AllowedTagsXssTrait:: |
public | function | Returns a human-readable list of allowed tags for display in help texts. | |
AllowedTagsXssTrait:: |
public | function | Filters an HTML string to prevent XSS vulnerabilities. | |
DependencySerializationTrait:: |
protected | property | An array of entity type IDs keyed by the property name of their storages. | |
DependencySerializationTrait:: |
protected | property | An array of service IDs keyed by property name used for serialization. | |
DependencySerializationTrait:: |
public | function | 1 | |
DependencySerializationTrait:: |
public | function | 2 | |
EntityReferenceBrowserWidget:: |
protected | property | The current user. | |
EntityReferenceBrowserWidget:: |
protected static | property | The depth of the delete button. | 1 |
EntityReferenceBrowserWidget:: |
protected | property | Entity type manager service. | |
EntityReferenceBrowserWidget:: |
protected | property | Field widget display plugin manager. | |
EntityReferenceBrowserWidget:: |
protected | property | The module handler interface. | |
EntityReferenceBrowserWidget:: |
public | function |
Calculates dependencies for the configured plugin. Overrides PluginSettingsBase:: |
|
EntityReferenceBrowserWidget:: |
public static | function |
Creates an instance of the plugin. Overrides WidgetBase:: |
1 |
EntityReferenceBrowserWidget:: |
public static | function |
Defines the default settings for this plugin. Overrides PluginSettingsBase:: |
1 |
EntityReferenceBrowserWidget:: |
protected | function | Builds the render array for displaying the current results. | 1 |
EntityReferenceBrowserWidget:: |
public | function |
Assigns a field-level validation error to the right widget sub-element. Overrides WidgetBase:: |
|
EntityReferenceBrowserWidget:: |
public | function |
Reports field-level validation errors against actual form elements. Overrides WidgetBase:: |
|
EntityReferenceBrowserWidget:: |
public | function |
Returns the form for a single field widget. Overrides WidgetInterface:: |
1 |
EntityReferenceBrowserWidget:: |
protected | function | Determines the entities used for the form element. | |
EntityReferenceBrowserWidget:: |
protected | function | Generates a message informing the user how many more items they can choose. | |
EntityReferenceBrowserWidget:: |
protected | function | Get selected elements from target_id element on form. | |
EntityReferenceBrowserWidget:: |
protected | function | Returns a key used to store the previously loaded entity. | |
EntityReferenceBrowserWidget:: |
protected | function | Gets data that should persist across Entity Browser renders. | 1 |
EntityReferenceBrowserWidget:: |
public | function |
Massages the form values into the format expected for field values. Overrides WidgetBase:: |
1 |
EntityReferenceBrowserWidget:: |
public static | function | Render API callback: Processes the entity browser element. | |
EntityReferenceBrowserWidget:: |
public static | function | Submit callback for remove buttons. | |
EntityReferenceBrowserWidget:: |
protected | function | Gets options that define where newly added entities are inserted. | |
EntityReferenceBrowserWidget:: |
public | function |
Returns a form to configure settings for the widget. Overrides WidgetBase:: |
1 |
EntityReferenceBrowserWidget:: |
public | function |
Returns a short summary for the current widget settings. Overrides WidgetBase:: |
1 |
EntityReferenceBrowserWidget:: |
protected | function | Provides base for settings summary shared by all EB widgets. | |
EntityReferenceBrowserWidget:: |
public static | function | Ajax callback that updates field widget display settings fieldset. | |
EntityReferenceBrowserWidget:: |
public static | function | AJAX form callback. | |
EntityReferenceBrowserWidget:: |
public static | function | Validate the settings form. | |
EntityReferenceBrowserWidget:: |
public | function |
Constructs widget plugin. Overrides WidgetBase:: |
1 |
MessengerTrait:: |
protected | property | The messenger. | 29 |
MessengerTrait:: |
public | function | Gets the messenger. | 29 |
MessengerTrait:: |
public | function | Sets the messenger. | |
PluginBase:: |
protected | property | Configuration information passed into the plugin. | 1 |
PluginBase:: |
protected | property | The plugin implementation definition. | 1 |
PluginBase:: |
protected | property | The plugin_id. | |
PluginBase:: |
constant | A string which is used to separate base plugin IDs from the derivative ID. | ||
PluginBase:: |
public | function |
Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the definition of the plugin implementation. Overrides PluginInspectionInterface:: |
3 |
PluginBase:: |
public | function |
Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface:: |
|
PluginBase:: |
public | function | Determines if the plugin is configurable. | |
PluginSettingsBase:: |
protected | property | Whether default settings have been merged into the current $settings. | |
PluginSettingsBase:: |
protected | property | The plugin settings injected by third party modules. | |
PluginSettingsBase:: |
public | function |
Returns the value of a setting, or its default value if absent. Overrides PluginSettingsInterface:: |
|
PluginSettingsBase:: |
public | function |
Returns the array of settings, including defaults for missing settings. Overrides PluginSettingsInterface:: |
|
PluginSettingsBase:: |
public | function |
Gets the list of third parties that store information. Overrides ThirdPartySettingsInterface:: |
|
PluginSettingsBase:: |
public | function |
Gets the value of a third-party setting. Overrides ThirdPartySettingsInterface:: |
|
PluginSettingsBase:: |
public | function |
Gets all third-party settings of a given module. Overrides ThirdPartySettingsInterface:: |
|
PluginSettingsBase:: |
protected | function | Merges default settings values into $settings. | |
PluginSettingsBase:: |
public | function |
Informs the plugin that some configuration it depends on will be deleted. Overrides PluginSettingsInterface:: |
3 |
PluginSettingsBase:: |
public | function |
Sets the value of a setting for the plugin. Overrides PluginSettingsInterface:: |
|
PluginSettingsBase:: |
public | function |
Sets the settings for the plugin. Overrides PluginSettingsInterface:: |
|
PluginSettingsBase:: |
public | function |
Sets the value of a third-party setting. Overrides ThirdPartySettingsInterface:: |
|
PluginSettingsBase:: |
public | function |
Unsets a third-party setting. Overrides ThirdPartySettingsInterface:: |
|
StringTranslationTrait:: |
protected | property | The string translation service. | 1 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. | |
WidgetBase:: |
protected | property | The field definition. | |
WidgetBase:: |
protected | property |
The widget settings. Overrides PluginSettingsBase:: |
|
WidgetBase:: |
public static | function | Ajax callback for the "Add another item" button. | |
WidgetBase:: |
public static | function | Submission handler for the "Add another item" button. | |
WidgetBase:: |
public static | function | After-build handler for field elements in a form. | |
WidgetBase:: |
public | function |
Extracts field values from submitted form values. Overrides WidgetBaseInterface:: |
2 |
WidgetBase:: |
public | function |
Creates a form element for a field. Overrides WidgetBaseInterface:: |
3 |
WidgetBase:: |
protected | function | Special handling to create form elements for multiple values. | 1 |
WidgetBase:: |
protected | function | Generates the form element for a single copy of the widget. | |
WidgetBase:: |
protected | function | Returns the value of a field setting. | |
WidgetBase:: |
protected | function | Returns the array of field settings. | |
WidgetBase:: |
protected | function | Returns the filtered field description. | |
WidgetBase:: |
public static | function |
Retrieves processing information about the widget from $form_state. Overrides WidgetBaseInterface:: |
|
WidgetBase:: |
protected static | function | Returns the location of processing information within $form_state. | |
WidgetBase:: |
protected | function | Returns whether the widget handles multiple values. | |
WidgetBase:: |
public static | function |
Returns if the widget can be used for the provided field. Overrides WidgetInterface:: |
4 |
WidgetBase:: |
protected | function | Returns whether the widget used for default value form. | |
WidgetBase:: |
public static | function |
Stores processing information about the widget in $form_state. Overrides WidgetBaseInterface:: |