display_fields-8.x-1.x-dev/src/Element/EntityFieldSelect.php
src/Element/EntityFieldSelect.php
<?php
namespace Drupal\display_fields\Element;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\FormElement;
/**
* Form element for selection of entity fields through entities.
*
* Declaration of the following element are mandatory:
* $form['your_element'] = [
* '#type' => 'entity_field_select',
* '#title' => t('Select a field'),
* '#entity_type' => $this->getEntityTypeId(),
* '#bundle' => $this->bundle(),
* ];
*
* Return multiple values:
*
* $element['reference_key']
* - Contains a string that contains the path to the field through the parent
* entity_type; each part of the keys are separated via ':'. Reference key
* data is split with '|'.
* For example:
* - {field_article_references|node|article:field_tags|taxonomy_term|tags:name}
* Refers to the term name referenced via two reference entities of
* specified type|bundle fields.
* - {field_image}
* Refers to the field_image of the source entity type / bundle.
* - {field_references|node|*:author|user|user:user_picture}
* Refers to the user_picture via two referenced entities of specified
* types but for every bundle of the node references.
*
* $element['entity_type']
* - Contains the entity_type of the field selected.
* $element['bundle'];
* - Contains the bundle of the field selected. This value can be an
* asterisk.
* $element['source_entity_type']
* - Contains the source entity type as passed in the form element array
* definitions.
* $element['source_bundle']
* - Contains the source entity bundle as passed in the form element array
* definitions.
* $element['field_name']
* - Contains the machine_name of the field selected.
*
* @FormElement("entity_field_select")
*/
class EntityFieldSelect extends FormElement {
protected $entityTypeId;
protected $bundle;
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
return [
'#input' => TRUE,
'#process' => [
[$class, 'processEntityFieldSelect'],
],
'#title_display' => 'form-no-label',
'#theme_wrappers' => ['form_element'],
];
}
/**
* Expand the entity_field_select form element into the final form elements.
*/
public static function processEntityFieldSelect(&$element, FormStateInterface $form_state, &$complete_form) {
$source_entity_type = $element['#entity_type'];
$source_bundle = $element['#bundle'];
$values = $form_state->getValue($element['#array_parents']);
$triggering_element = $form_state->getTriggeringElement();
$default_value = [
'reference_key' => '',
'source_entity_type' => $source_entity_type,
'source_bundle' => $source_bundle,
'entity_type' => $source_entity_type,
'bundle' => $source_bundle,
'field_name' => '',
];
if (isset($element['#default_value']) && empty($values) && empty($triggering_element['#df_action'])) {
// Set the value from #default_value.
$default_value = array_merge($default_value, $element['#default_value']);
}
$values += $default_value;
$element['#prefix'] = '<div id="' . EntityFieldSelect::getElementHTMLId($element, $complete_form) . '">';
$element['#suffix'] = '</div>';
if ($element['#title_display'] != 'invisible') {
$element['#prefix'] .= '<h5>' . $element['#title'] . '</h5>';
}
$element['reference_key'] = [
'#type' => 'hidden',
'#value' => $values['reference_key'],
];
$element['entity_type'] = [
'#type' => 'hidden',
'#value' => $values['entity_type'],
];
$element['bundle'] = [
'#type' => 'hidden',
'#value' => $values['bundle'],
];
$element['source_entity_type'] = [
'#type' => 'hidden',
'#value' => $values['source_entity_type'],
];
$element['source_bundle'] = [
'#type' => 'hidden',
'#value' => $values['source_bundle'],
];
$element['reference_breadcrumb'] = [
'#type' => 'fieldset',
'#title' => (string) t('Entity field selection'),
'#attributes' => [],
'element' => [
'#markup' => !empty($values['field_name']) ? EntityFieldSelect::getBreadcrumbReferences($values) : '',
],
'#weight' => -10,
];
if (empty($values['reference_key']) || substr($values['reference_key'], strlen($values['reference_key']) - 1, 1) === ':') {
$fields_options = EntityFieldSelect::getOptionsFromValues($values);
$element['field_name'] = [
'#type' => 'select',
'#title' => (string) t('Select a field'),
'#options' => $fields_options,
'#ajax' => [
'callback' => [get_called_class(), 'ajaxEntityFieldSelect'],
'wrapper' => EntityFieldSelect::getElementHTMLId($element, $complete_form),
],
'#weight' => -5,
'#required' => $element['#required'] ?? FALSE,
'#df_action' => 'field_selection',
'#attributes' => ['autocomplete' => 'off'],
];
$element['field_name_reset'] = [
'#type' => 'hidden',
'#value' => (string) t('Reset'),
'#limit_validation_errors' => [],
'#df_action' => 'reset',
'#ajax' => [
'callback' => [get_called_class(), 'ajaxEntityFieldSelect'],
'wrapper' => EntityFieldSelect::getElementHTMLId($element, $complete_form),
],
];
}
else {
$element['field_name'] = [
'#type' => 'hidden',
'#value' => $values['field_name'],
'#df_action' => 'field_selection',
'#ajax' => [
'callback' => [get_called_class(), 'ajaxEntityFieldSelect'],
'wrapper' => EntityFieldSelect::getElementHTMLId($element, $complete_form),
],
];
$element['field_name_reset'] = [
'#type' => 'button',
'#value' => (string) t('Reset'),
'#df_action' => 'reset',
'#limit_validation_errors' => ['field_name'],
'#weight' => -1,
'#ajax' => [
'callback' => [get_called_class(), 'ajaxEntityFieldSelect'],
'wrapper' => EntityFieldSelect::getElementHTMLId($element, $complete_form),
],
];
}
$element['#element_validate'] = [[get_called_class(), 'validateEntityFieldSelect']];
$element['#tree'] = TRUE;
return $element;
}
public static function getElementHTMLId($element, $complete_form) {
return 'entity-field-select-wrapper-' . md5(implode('', $element['#array_parents']) . $complete_form['form_id']['#value']);
}
/**
* Ajax form callback.
*/
public static function ajaxEntityFieldSelect($form, FormStateInterface $form_state) {
$triggering_element = $form_state->getTriggeringElement();
if (isset($triggering_element['#df_action'])) {
array_pop($triggering_element['#array_parents']);
return NestedArray::getValue($form, $triggering_element['#array_parents']);
}
}
/**
* Validates a password_confirm element.
*/
public static function validateEntityFieldSelect(&$element, FormStateInterface $form_state, &$complete_form) {
$reference_key = $element['reference_key']['#value'];
$entity_type = $element['entity_type']['#value'];
$bundle = $element['bundle']['#value'];
$source_entity_type = $element['source_entity_type']['#value'];
$source_bundle = $element['source_bundle']['#value'];
$field_name = $element['field_name']['#value'];
$triggering_element = $form_state->getTriggeringElement();
if (!isset($triggering_element['#df_action'])) {
return;
}
if ($triggering_element['#df_action'] === 'reset' || empty($field_name) || $field_name == '**RESET**') {
$form_state->setValueForElement($element['entity_type'], $source_entity_type);
$form_state->setValueForElement($element['bundle'], $source_bundle);
$form_state->setValueForElement($element['field_name'], '');
$form_state->setValueForElement($element['reference_key'], '');
return $element;
}
if ((strpos($field_name, '|') === FALSE && (empty($reference_key)) ||
(strpos($field_name, '|') === FALSE && substr($reference_key, strlen($reference_key) - 1, 1) === ':'))) {
// Field name selected to be added.
$form_state->setValueForElement($element['reference_key'], $reference_key . $field_name);
}
elseif (strpos($field_name, '|') !== FALSE) {
// A field has been selected through a reference.
// We add the field_name|entity_type|bundle at this point.
// And we add a ':' suffix to indicate that is a reference
// and a field name must be select after it.
$field_name_infos = explode('|', $field_name);
$form_state->setValueForElement($element['entity_type'], $field_name_infos[1]);
$form_state->setValueForElement($element['bundle'], $field_name_infos[2]);
$form_state->setValueForElement($element['reference_key'], "{$reference_key}{$field_name}:");
}
return $element;
}
/**
* Get options fields.
*
* @param array $values
* The form element values.
*
* @return array
* An array of options for the element[field_name].
*/
public static function getOptionsFromValues($values) {
$reference_key = $values['reference_key'];
$current_entity_type = $values['entity_type'];
$current_bundle = $values['bundle'];
$current_field_name = $values['field_name'];
if ($current_bundle == '*') {
$bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo($current_entity_type);
$fields = [];
foreach ($bundles as $bundle_name => $bundle) {
$fields = array_merge($fields, \Drupal::service('entity_field.manager')->getFieldDefinitions($current_entity_type, $bundle_name));
}
}
else {
$fields = \Drupal::service('entity_field.manager')->getFieldDefinitions($current_entity_type, $current_bundle);
}
$fields_options = [];
foreach ($fields as $field_name => $field) {
if (!$field->isDisplayConfigurable('view')) {
// @todo Add a $display_context settings to display only the field that have a display configurable context.
}
$fieldStorage = $field->getFieldStorageDefinition();
$field_cardinality = $fieldStorage->getCardinality();
// Add this field options.
$fields_options[(string) t('Fields')][$field_name] = $field->getLabel();
// Add 'actions' options, for select referenced fields.
if (in_array($field->getType(), EntityFieldSelect::entityReferenceFieldType())) {
// Set options for referenced entities.
$field_settings = $field->getSettings();
$target_entity_type = $field_settings['target_type'];
$targetEntityType = \Drupal::service('entity_type.manager')->getDefinition($target_entity_type);
// If this is a content entity.
if ($targetEntityType->entityClassImplements('\Drupal\Core\Entity\ContentEntityInterface')) {
$target_entity_type_bundles = EntityFieldSelect::getReferenceFieldsAllowedBundles($field->getType(), $field_settings);
$target_bundles_infos = \Drupal::service('entity_type.bundle.info')->getBundleInfo($target_entity_type);
if (empty($target_entity_type_bundles)) {
// Empty target_bundles means all bundles. Get those.
$target_entity_type_bundles = array_keys($target_bundles_infos);
}
// Create "actions" options.
$fields_options[(string) t('Entity reference')]["$field_name|$target_entity_type|*"] = $field->getLabel() . ':' . t('All entities referenced');
if ($field_cardinality !== 1) {
$fields_options[(string) t('Entity reference')]["$field_name|$target_entity_type|*|0"] = $field->getLabel() . ' : ' . t('First of all entities referenced');
}
foreach ($target_entity_type_bundles as $target_bundle) {
$bundle_label = $target_bundles_infos[$target_bundle]['label'];
$fields_options[(string) t('Entity reference')]["$field_name|$target_entity_type|$target_bundle"] = $field->getLabel() . ' : ' . t('All entities referenced of bundle: @bundle_label', ['@bundle_label' => strtolower($bundle_label)]);
if ($field_cardinality !== 1) {
$fields_options[(string) t('Entity reference')]["$field_name|$target_entity_type|$target_bundle|0"] = $field->getLabel() . ' : ' . t('First of all entities referenced of bundle: @bundle_label', ['@bundle_label' => strtolower($bundle_label)]);
}
}
}
}
}
// Add an extra options to reset the values.
if (!empty($reference_key)) {
$fields_options[(string) t('Action')] = [
'**RESET**' => (string) t('Reset'),
];
}
return $fields_options;
}
/**
* Return the allowed bundles for an entity reference field type.
*
* @param string $type
* A field type.
* @param array $settings
* The field definition settings.
*
* @return array
* An array of bundles that can be referenced by the entity reference field.
*/
public static function getReferenceFieldsAllowedBundles($type, $settings) {
if ($type == 'entity_reference') {
return $settings['handler_settings']['target_bundles'] ?? [];
}
elseif ($type == 'taxonomy_term_reference') {
$bundles = [];
foreach ($settings['allowed_values'] as $allowed_value) {
$bundles[] = $allowed_value['vocabulary'];
}
return $bundles;
}
return [];
}
/**
* The entity reference field type, this element can handle.
* @return array
* An array of field types.
*/
public static function entityReferenceFieldType() {
return [
'taxonomy_term_reference',
'entity_reference',
];
}
/**
* Get a display breadcrumb to indicate the parent reference selected.
* @param array $values
*
* @return string
* The breadcrumb html.
*/
public static function getBreadcrumbReferences($values) {
$reference_key = $values['reference_key'];
$source_entity_type = $values['source_entity_type'];
$source_bundle = $values['source_bundle'];
$current_entity_type = $source_entity_type;
$current_bundle = $source_bundle;
if (empty($reference_key)) {
return t('No field selected');
}
$references = explode(':', $reference_key);
$breadcrumb = [];
foreach ($references as $field_name) {
if (empty($field_name)) {
$breadcrumb[] = '?';
continue;
}
if (strpos($field_name, '|') !== FALSE) {
$field_name_infos = explode('|', $field_name);
$fields = \Drupal::service('entity_field.manager')->getFieldDefinitions($current_entity_type, $current_bundle);
$field = $fields[$field_name_infos[0]];
$current_entity_type = $field_name_infos[1];
$breadcrumb_desc = $field->getLabel();
if (isset($field_name_infos[3]) && $field_name_infos[3] == 0) {
$breadcrumb_desc .= ' [0]';
}
$breadcrumb[] = str_repeat(' ', count($breadcrumb) + 1) . '> ' . $breadcrumb_desc;
}
else {
$fields = \Drupal::service('entity_field.manager')->getFieldDefinitions($current_entity_type, $current_bundle);
if (isset($fields[$field_name])) {
$field = $fields[$field_name];
$breadcrumb_desc = $field->getLabel();
$breadcrumb[] = str_repeat(' ', count($breadcrumb) + 1) . '> <strong>' . $breadcrumb_desc . '</strong>';
}
}
}
$source_bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo($source_entity_type);
$bundle_label = $source_bundles[$source_bundle]['label'];
return $bundle_label . '<br>' . implode('<br>', $breadcrumb);
}
}
