diff --git a/config/schema/dynamic_entity_reference.schema.yml b/config/schema/dynamic_entity_reference.schema.yml index a99d9b7..cd1ebbf 100644 --- a/config/schema/dynamic_entity_reference.schema.yml +++ b/config/schema/dynamic_entity_reference.schema.yml @@ -18,15 +18,18 @@ field.dynamic_entity_reference.storage_settings: label: 'Entity Type ID' field.dynamic_entity_reference.field_settings: - type: mapping + type: sequence label: 'Dynamic entity reference settings' - mapping: - handler: - type: string - label: 'Reference method' - handler_settings: - type: entity_reference.[%parent.handler].handler_settings - label: 'Reference method settings' + sequence: + - type: mapping + label: 'Entity Type ID' + mapping: + handler: + type: string + label: 'Reference method' + handler_settings: + type: entity_reference.[%parent.handler].handler_settings + label: 'Reference method settings' field.dynamic_entity_reference.value: type: sequence diff --git a/src/Plugin/Field/FieldType/DynamicEntityReferenceItem.php b/src/Plugin/Field/FieldType/DynamicEntityReferenceItem.php index e48bf98..d312e0a 100644 --- a/src/Plugin/Field/FieldType/DynamicEntityReferenceItem.php +++ b/src/Plugin/Field/FieldType/DynamicEntityReferenceItem.php @@ -1,4 +1,5 @@ TRUE, 'entity_type_ids' => array(), - ) + parent::defaultStorageSettings(); + ); } /** * {@inheritdoc} */ public static function defaultFieldSettings() { - return array( - 'handler' => 'default', - ) + parent::defaultFieldSettings(); + return array(); } /** @@ -126,7 +127,6 @@ class DynamicEntityReferenceItem extends ConfigurableEntityReferenceItem { /** * {@inheritdoc} - * @todo update */ public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) { // @todo inject this. @@ -155,7 +155,26 @@ class DynamicEntityReferenceItem extends ConfigurableEntityReferenceItem { * {@inheritdoc} */ public function fieldSettingsForm(array $form, FormStateInterface $form_state) { - return array(); + + $settings_form = array(); + $field = $form_state->get('field'); + $settings = $this->getSettings(); + $entity_type_ids = $this->getAllEntityTypeIds($settings); + foreach (array_keys($entity_type_ids) as $entity_type_id) { + $settings += array( + $entity_type_id => array( + 'handler' => 'default', + 'handler_settings' => array(), + ), + ); + // We put the dummy value here so selection plugins can work. + $field->settings['target_type'] = $entity_type_id; + $field->settings['handler'] = $settings[$entity_type_id]['handler']; + $field->settings['handler_settings'] = $settings[$entity_type_id]['handler_settings']; + $settings_form[$entity_type_id] = parent::fieldSettingsForm($form, $form_state); + $settings_form[$entity_type_id]['handler']['#title'] = t('Reference type for @entity_type_id', array('@entity_type_id' => $entity_type_ids[$entity_type_id])); + } + return $settings_form; } /** @@ -165,11 +184,18 @@ class DynamicEntityReferenceItem extends ConfigurableEntityReferenceItem { * The form where the settings form is being included in. * @param \Drupal\Core\Form\FormStateInterface $form_state * The form state of the (entire) configuration form. - * @todo update */ public static function fieldSettingsFormValidate(array $form, FormStateInterface $form_state) { if ($form_state->hasValue('field')) { - $form_state->unsetValue(array('field', 'settings', 'handler_submit')); + $settings = $form_state->getValue(array('field', 'settings')); + foreach (array_keys($settings) as $entity_typ_id) { + $form_state->unsetValue(array( + 'field', + 'settings', + $entity_typ_id, + 'handler_submit', + )); + } $form_state->get('field')->settings = $form_state->getValue(array('field', 'settings')); } } @@ -217,4 +243,48 @@ class DynamicEntityReferenceItem extends ConfigurableEntityReferenceItem { } } + /** + * Helper function to get all the entity type ids that can be referenced. + * + * @return string[] + * All the entity type ids that can be referenced. + */ + protected static function getAllEntityTypeIds($settings) { + $labels = \Drupal::entityManager()->getEntityTypeLabels(TRUE); + $options = $labels['Content']; + + if ($settings['exclude_entity_types']) { + $entity_type_ids = array_diff_key($options, $settings['entity_type_ids'] ?: array()); + } + else { + $entity_type_ids = array_intersect_key($options, $settings['entity_type_ids'] ?: array()); + } + return $entity_type_ids; + } + + /** + * {@inheritdoc} + */ + public static function calculateDependencies(FieldDefinitionInterface $field_definition) { + $dependencies = []; + + if (is_array($field_definition->default_value) && count($field_definition->default_value)) { + $target_entity_types = static::getAllEntityTypeIds($field_definition->getFieldStorageDefinition()->getSettings()); + foreach ($target_entity_types as $target_entity_type) { + $key = $target_entity_type instanceof ConfigEntityType ? 'config' : 'content'; + foreach ($field_definition->default_value as $default_value) { + if (is_array($default_value) && isset($default_value['target_uuid'])) { + $entity = \Drupal::entityManager()->loadEntityByUuid($target_entity_type->id(), $default_value['target_uuid']); + // If the entity does not exist do not create the dependency. + // @see \Drupal\Core\Field\EntityReferenceFieldItemList::processDefaultValue() + if ($entity) { + $dependencies[$key][] = $entity->getConfigDependencyName(); + } + } + } + } + } + return $dependencies; + } + } diff --git a/src/Plugin/Field/FieldWidget/DynamicEntityReferenceWidget.php b/src/Plugin/Field/FieldWidget/DynamicEntityReferenceWidget.php index c197546..a3f3ce0 100644 --- a/src/Plugin/Field/FieldWidget/DynamicEntityReferenceWidget.php +++ b/src/Plugin/Field/FieldWidget/DynamicEntityReferenceWidget.php @@ -108,8 +108,10 @@ class DynamicEntityReferenceWidget extends AutocompleteWidget { * Checks whether a content entity is referenced. * * @param string $target_type - * The value target entity type + * The value target entity type. + * * @return bool + * TRUE if a content entity is referenced. */ protected function isContentReferenced($target_type = NULL) { $target_type_info = \Drupal::entityManager()->getDefinition($target_type); @@ -125,10 +127,17 @@ class DynamicEntityReferenceWidget extends AutocompleteWidget { if (!empty($element['#value'])) { // If this is the default value of the field. if ($form_state->hasValue('default_value_input')) { - $values = $form_state->getValue(array('default_value_input', $element['#field_name'], $element['#delta'])); + $values = $form_state->getValue(array( + 'default_value_input', + $element['#field_name'], + $element['#delta'], + )); } else { - $values = $form_state->getValue(array($element['#field_name'], $element['#delta'])); + $values = $form_state->getValue(array( + $element['#field_name'], + $element['#delta'], + )); } // Take "label (entity id)', match the id from parenthesis. if ($this->isContentReferenced($values['target_type']) && preg_match("/.+\((\d+)\)/", $element['#value'], $matches)) { @@ -137,10 +146,27 @@ class DynamicEntityReferenceWidget extends AutocompleteWidget { elseif (preg_match("/.+\(([\w.]+)\)/", $element['#value'], $matches)) { $value = $matches[1]; } - if (!$value) { + $auto_create = $this->getHandlerSetting('auto_create', $values['target_type']); + if ($value === NULL) { // Try to get a match from the input string when the user didn't use the // autocomplete but filled in a value manually. - $value = $this->validateAutocompleteInput($values['target_type'], $element['#value'], $element, $form_state, $form); + $this->fakeFieldSettings($values['target_type']); + /** @var \Drupal\entity_reference\Plugin\Type\Selection\SelectionInterface $handler */ + $handler = \Drupal::service('plugin.manager.entity_reference.selection')->getSelectionHandler($this->fieldDefinition); + $value = $handler->validateAutocompleteInput($element['#value'], $element, $form_state, $form, !$auto_create); + } + if (!$value && $auto_create && (count($this->getHandlerSetting('target_bundles', $values['target_type'])) == 1)) { + // Auto-create item. See + // \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::presave(). + $value = array( + 'target_id' => NULL, + 'entity' => $this->createNewEntity($element['#value'], $element['#autocreate_uid']), + // Keep the weight property. + '_weight' => $element['#weight'], + ); + // Change the element['#parents'], so in form_set_value() we + // populate the correct key. + array_pop($element['#parents']); } } @@ -148,55 +174,32 @@ class DynamicEntityReferenceWidget extends AutocompleteWidget { } /** - * {@inheritdoc} + * Sets the fake field settings values. + * + * @param string $entity_type_id + * The id of the entity type. */ - public function validateAutocompleteInput($target_type, $input, &$element, FormStateInterface $form_state, $form) { - // @todo Make this a service. - $controller = new DynamicEntityReferenceController(\Drupal::service('entity.query')); - $bundled_entities = $controller->getReferenceableEntities($target_type, $input, '=', 6); - $params = array( - '%value' => $input, - '@value' => $input, - ); - $entities = array(); - foreach ($bundled_entities as $entities_list) { - $entities += $entities_list; - } - if (empty($entities)) { - // Error if there are no entities available for a required field. - $form_state->setError($element, t('There are no entities matching "%value".', $params)); - } - elseif (count($entities) > 5) { - $params['@id'] = key($entities); - // Error if there are more than 5 matching entities. - $form_state->setError($element, t('Many entities are called %value. Specify the one you want by appending the id in parentheses, like "@value (@id)".', $params)); - } - elseif (count($entities) > 1) { - // More helpful error if there are only a few matching entities. - $multiples = array(); - foreach ($entities as $id => $name) { - $multiples[] = $name . ' (' . $id . ')'; - } - $params['@id'] = $id; - $params['%multiple'] = implode('", "', $multiples); - $form_state->setError($element, t('Multiple entities match this reference; "%multiple". Specify the one you want by appending the id in parentheses, like "@value (@id)".', $params)); - } - else { - // Take the one and only matching entity. - return key($entities); - } + protected function fakeFieldSettings($entity_type_id) { + $settings = $this->getFieldSettings(); + $this->fieldDefinition->settings['target_type'] = $entity_type_id; + $this->fieldDefinition->settings['handler'] = $settings[$entity_type_id]['handler']; + $this->fieldDefinition->settings['handler_settings'] = $settings[$entity_type_id]['handler_settings']; } /** - * {@inheritdoc} + * Returns the value of a setting for the entity reference selection handler. + * + * @param string $setting_name + * The setting name. + * @param string $entity_type_id + * The id of the entity type. + * + * @return mixed + * The setting value. */ - protected function getEntityType(FieldItemListInterface $items, $delta) { - // The autocomplete widget outputs one entity label per form element. - if (isset($items[$delta])) { - return $items[$delta]->target_type; - } - - return FALSE; + protected function getHandlerSetting($setting_name, $entity_type_id) { + $settings = $this->getFieldSettings(); + return isset($settings[$entity_type_id]['handler_settings'][$setting_name]) ? $settings[$entity_type_id]['handler_settings'][$setting_name] : NULL; } } diff --git a/src/Tests/DynamicEntityReferenceSchemaTest.php b/src/Tests/DynamicEntityReferenceSchemaTest.php index 3fa73b1..221b72f 100644 --- a/src/Tests/DynamicEntityReferenceSchemaTest.php +++ b/src/Tests/DynamicEntityReferenceSchemaTest.php @@ -60,7 +60,20 @@ class DynamicEntityReferenceSchemaTest extends EntityUnitTestBase { 'entity_type' => 'entity_test', 'field_name' => 'field_test', 'bundle' => 'entity_test', - 'settings' => array(), + 'settings' => array( + 'entity_test' => array( + 'handler' => 'default', + 'handler_settings' => array( + 'target_bundles' => array( + 'entity_test' => 'entity_test', + ), + 'sort' => array( + 'field' => '_none', + ), + 'auto_create' => FALSE, + ), + ), + ), )); $entity_storage= \Drupal::entityManager()->getStorage('entity_test'); $referenced_entity = $entity_storage->create(array()); diff --git a/src/Tests/DynamicEntityReferenceTest.php b/src/Tests/DynamicEntityReferenceTest.php index 37a96a6..83b0401 100644 --- a/src/Tests/DynamicEntityReferenceTest.php +++ b/src/Tests/DynamicEntityReferenceTest.php @@ -78,7 +78,25 @@ class DynamicEntityReferenceTest extends WebTestBase { $this->assertFieldByName('default_value_input[field_foobar][0][target_type]'); $this->assertFieldByXPath(CssSelector::toXPath('select[name="default_value_input[field_foobar][0][target_type]"] > option[value=entity_test]'), 'entity_test'); $this->assertNoFieldByXPath(CssSelector::toXPath('select[name="default_value_input[field_foobar][0][target_type]"] > option[value=user]'), 'user'); - $this->drupalPostForm(NULL, array(), t('Save settings')); + $edit = array( + 'field[settings][entity_test_label][handler_settings][target_bundles][entity_test_label]' => TRUE, + 'field[settings][entity_test_no_id][handler_settings][target_bundles][entity_test_no_id]' => TRUE, + 'field[settings][entity_test_no_label][handler_settings][target_bundles][entity_test_no_label]' => TRUE, + 'field[settings][entity_test_label_callback][handler_settings][target_bundles][entity_test_label_callback]' => TRUE, + 'field[settings][entity_test][handler_settings][target_bundles][entity_test]' => TRUE, + 'field[settings][entity_test_base_field_display][handler_settings][target_bundles][entity_test_base_field_display]' => TRUE, + 'field[settings][entity_test_mul][handler_settings][target_bundles][entity_test_mul]' => TRUE, + 'field[settings][entity_test_rev][handler_settings][target_bundles][entity_test_rev]' => TRUE, + 'field[settings][entity_test_mulrev][handler_settings][target_bundles][entity_test_mulrev]' => TRUE, + 'field[settings][entity_test_constraint_violation][handler_settings][target_bundles][entity_test_constraint_violation]' => TRUE, + 'field[settings][entity_test_field_override][handler_settings][target_bundles][entity_test_field_override]' => TRUE, + 'field[settings][entity_test_default_value][handler_settings][target_bundles][entity_test_default_value]' => TRUE, + 'field[settings][entity_test_update][handler_settings][target_bundles][entity_test_update]' => TRUE, + 'field[settings][entity_test_default_access][handler_settings][target_bundles][entity_test_default_access]' => TRUE, + 'field[settings][entity_test_cache][handler_settings][target_bundles][entity_test_cache]' => TRUE, + 'field[settings][entity_test_string_id][handler_settings][target_bundles][entity_test_string_id]' => TRUE, + ); + $this->drupalPostForm(NULL, $edit, t('Save settings')); $this->assertRaw(t('Saved %name configuration', array('%name' => 'Foobar'))); $excluded_entity_type_ids = FieldStorageConfig::loadByName('entity_test', 'field_foobar') ->getSetting('entity_type_ids'); @@ -130,7 +148,25 @@ class DynamicEntityReferenceTest extends WebTestBase { 'field_storage[cardinality]' => '-1', ), t('Save field settings')); - $this->drupalPostForm(NULL, array(), t('Save settings')); + $edit = array( + 'field[settings][entity_test_label][handler_settings][target_bundles][entity_test_label]' => TRUE, + 'field[settings][entity_test_no_id][handler_settings][target_bundles][entity_test_no_id]' => TRUE, + 'field[settings][entity_test_no_label][handler_settings][target_bundles][entity_test_no_label]' => TRUE, + 'field[settings][entity_test_label_callback][handler_settings][target_bundles][entity_test_label_callback]' => TRUE, + 'field[settings][entity_test][handler_settings][target_bundles][entity_test]' => TRUE, + 'field[settings][entity_test_base_field_display][handler_settings][target_bundles][entity_test_base_field_display]' => TRUE, + 'field[settings][entity_test_mul][handler_settings][target_bundles][entity_test_mul]' => TRUE, + 'field[settings][entity_test_rev][handler_settings][target_bundles][entity_test_rev]' => TRUE, + 'field[settings][entity_test_mulrev][handler_settings][target_bundles][entity_test_mulrev]' => TRUE, + 'field[settings][entity_test_constraint_violation][handler_settings][target_bundles][entity_test_constraint_violation]' => TRUE, + 'field[settings][entity_test_field_override][handler_settings][target_bundles][entity_test_field_override]' => TRUE, + 'field[settings][entity_test_default_value][handler_settings][target_bundles][entity_test_default_value]' => TRUE, + 'field[settings][entity_test_update][handler_settings][target_bundles][entity_test_update]' => TRUE, + 'field[settings][entity_test_default_access][handler_settings][target_bundles][entity_test_default_access]' => TRUE, + 'field[settings][entity_test_cache][handler_settings][target_bundles][entity_test_cache]' => TRUE, + 'field[settings][entity_test_string_id][handler_settings][target_bundles][entity_test_string_id]' => TRUE, + ); + $this->drupalPostForm(NULL, $edit, t('Save settings')); $this->assertRaw(t('Saved %name configuration', array('%name' => 'Foobar'))); // Create some items to reference.