diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFieldFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFieldFormatter.php index 31006f1..0e38d33 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFieldFormatter.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/EntityReferenceFieldFormatter.php @@ -3,13 +3,13 @@ namespace Drupal\Core\Field\Plugin\Field\FieldFormatter; use Drupal\Core\Entity\EntityDisplayBase; +use Drupal\Core\Entity\EntityFieldManager; use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface; -use Drupal\Core\Entity\EntityFieldManager; use Drupal\Core\Field\FormatterPluginManager; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; @@ -65,6 +65,13 @@ class EntityReferenceFieldFormatter extends EntityReferenceFormatterBase impleme protected $availableFieldOptions; /** + * The list of available formatters, keyed by field type. + * + * @var array + */ + protected $availableFormatterOptions; + + /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { @@ -147,7 +154,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) { $build = []; foreach ($entities as $delta => $entity) { if ($entity->hasField($field_name)) { - $build[$delta] = $this->entityTypeManager->getViewBuilder($entity->getEntityTypeId())->viewField($entity->get($field_name), array_filter($formatter_settings)); + $build[$delta] = $this->entityTypeManager->getViewBuilder($entity->getEntityTypeId())->viewField($entity->get($field_name), $formatter_settings); } } return $build; @@ -167,7 +174,10 @@ protected function getAvailableFieldOptions() { $entity_type_id = $this->fieldDefinition->getSetting('target_type'); $entity_type = $this->entityTypeManager->getDefinition($entity_type_id); - // We always show the entity label as an option to be selected. + + // If possible, initialize the options array with the target entity's label, + // because we always want the label to be a selectable option for this + // formatter. if ($entity_type->hasKey('label')) { $label_key = $entity_type->getKey('label'); $field_names = [ @@ -181,9 +191,13 @@ protected function getAvailableFieldOptions() { $target_bundles = empty($this->fieldDefinition->getSetting('handler_settings')['target_bundles']) ? array_keys($this->entityTypeBundleInfo->getBundleInfo($entity_type_id)) : $this->fieldDefinition->getSetting('handler_settings')['target_bundles']; foreach ($target_bundles as $bundle) { + // Fetch all fields present on each target bundle. $bundle_field_names = []; /** @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */ foreach ($this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle) as $field_machine_name => $field_definition) { + // We use ::isDisplayConfigurable() to filter out the base fields that + // should not be exposed to the user. The only exception to this is the + // entity label, which was added earlier and will always be an option. if ($field_definition->isDisplayConfigurable('view')) { $bundle_field_names[$field_machine_name] = $field_definition->getLabel(); } @@ -202,11 +216,16 @@ protected function getAvailableFieldOptions() { * The field storage definition. * * @return string[] - * The field formatter labels keys by plugin ID. + * The field formatter labels, keyed by plugin ID. */ protected function getAvailableFormatterOptions(FieldStorageDefinitionInterface $field_storage_definition) { + $field_type = $field_storage_definition->getType(); + if (isset($this->availableFormatterOptions[$field_type])) { + return $this->availableFormatterOptions[$field_type]; + } + $field_definition = BaseFieldDefinition::createFromFieldStorageDefinition($field_storage_definition); - $formatters = $this->formatterPluginManager->getOptions($field_storage_definition->getType()); + $formatters = $this->formatterPluginManager->getOptions($field_type); $options = []; foreach ($formatters as $formatter_id => $formatter_options) { $formatter_definition = $this->formatterPluginManager->getDefinition($formatter_id); @@ -216,6 +235,8 @@ protected function getAvailableFormatterOptions(FieldStorageDefinitionInterface $options[$formatter_id] = $formatter_definition['label']; } } + + $this->availableFormatterOptions[$field_type] = $options; return $options; } @@ -236,7 +257,7 @@ public static function onFormatterTypeChange(array $form, FormStateInterface $fo /** * Rebuilds the form on select submit. */ - public static function rebuildSubmit(array &$form, FormStateInterface $form_state) { + public static function rebuildOnSubmit(array &$form, FormStateInterface $form_state) { $form_state->setRebuild(TRUE); } @@ -269,7 +290,7 @@ public function settingsForm(array $form, FormStateInterface $form_state) { 'wrapper' => 'field-formatter-ajax', 'method' => 'replace', ], - '#submit' => [[static::class, 'rebuildSubmit']], + '#submit' => [[static::class, 'rebuildOnSubmit']], '#executes_submit_callback' => TRUE, ]; @@ -299,7 +320,7 @@ public function settingsForm(array $form, FormStateInterface $form_state) { 'wrapper' => 'field-formatter-settings-ajax', 'method' => 'replace', ], - '#submit' => [[static::class, 'rebuildSubmit']], + '#submit' => [[static::class, 'rebuildOnSubmit']], '#executes_submit_callback' => TRUE, ]; @@ -319,6 +340,9 @@ public function settingsForm(array $form, FormStateInterface $form_state) { if ($formatter = $this->formatterPluginManager->getInstance($options)) { $settings_form = $formatter->settingsForm([], $form_state); } + else { + drupal_set_message('Could not instantiate the formatter plugin, please choose a different formatter.', 'error'); + } $form['settings'] = $settings_form; $form['settings']['#prefix'] = '
'; $form['settings']['#suffix'] = '
'; @@ -332,23 +356,13 @@ public function settingsForm(array $form, FormStateInterface $form_state) { */ public function settingsSummary() { $summary = parent::settingsSummary(); - $field_storage_definitions = NULL; $field_name = $this->getFieldName(); - if ($this->getSetting('field_name')) { - $summary[] = $this->t('Field "%field_name" displayed.', ['%field_name' => $field_name['label']]); - } - else { - $summary[] = $this->t('The field "%field_name" will be used by default.', ['%field_name' => $field_name['label']]); - } - $formatter_type = $this->getFormatterType($field_name['machine_name']); - if ($this->getSetting('type')) { - $summary[] = $this->t('Formatter "%type" used.', ['%type' => $formatter_type['label']]); - } - else { - $summary[] = $this->t('The "%type" formatter will be used by default.', ['%type' => $formatter_type['label']]); - } + $summary[] = $this->t('Displaying the field "%field_name", using the formatter "%type".', [ + '%field_name' => $field_name['label'], + '%type' => $formatter_type['label'], + ]); return $summary; } @@ -359,7 +373,8 @@ public function settingsSummary() { * @param string $key * The setting name. * @param \Drupal\Core\Form\FormStateInterface $form_state - * The form state. + * (optional) The form state object. If absent, this method is equivalent to + * parent::getSetting(). * * @return mixed|null * The value of the setting, or NULL if absent. diff --git a/core/modules/media/media.module b/core/modules/media/media.module index eeb2ac1..88b28f3 100644 --- a/core/modules/media/media.module +++ b/core/modules/media/media.module @@ -6,12 +6,12 @@ */ use Drupal\Core\Access\AccessResult; -use Drupal\Core\Session\AccountInterface; -use Drupal\field\FieldConfigInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Render\Element; use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; +use Drupal\field\FieldConfigInterface; /** * Implements hook_help(). @@ -114,3 +114,81 @@ function template_preprocess_media(array &$variables) { $variables['content'][$key] = $variables['elements'][$key]; } } + +/** + * Implements hook_ENTITY_TYPE_presave(). + */ +function media_field_config_presave(FieldConfigInterface $field) { + // Don't change anything during a configuration sync. + if ($field->isSyncing()) { + return; + } + + // If this field formatter has not been configured yet, set the defaults to + // something more appropriate than the entity label. In our case, the + // source field. + if ($field->getSetting('handler') !== 'default:media' || empty($field->getSetting('handler_settings')['target_bundles'])) { + return; + } + + // If there are multiple target bundles allowed, we can't assume which field + // to use. + if (count($field->getSetting('handler_settings')['target_bundles']) > 1) { + return; + } + + $field_name = $field->get('field_name'); + $media_bundle = array_keys($field->getSetting('handler_settings')['target_bundles'])[0]; + $display = entity_get_display($field->get('entity_type'), $field->get('bundle'), 'default'); + + // @TODO How to detect if we need to define the formatter? At this point the + // display is already created and the formatter defaults were populated for + // this field type. How to detect then if the defaults were automattically + // set, or if the user really chose those settings? For now we assume that if + // the field is not used in any content, it is safe to change the display + // settings, but it may be too much to assume. + $num_entities = \Drupal::entityQuery($field->getTargetEntityTypeId()) + ->condition('type', $field->getTargetBundle()) + ->condition($field_name, NULL, 'IS NOT NULL') + ->count() + ->accessCheck(FALSE) + ->execute(); + if ($num_entities > 0) { + return; + } + + /** @var \Drupal\media\MediaTypeInterface $media_type */ + $media_type = \Drupal::entityTypeManager()->getStorage('media_type')->load($media_bundle); + $source_field_name = $media_type->getSource()->getConfiguration()['source_field']; + // Take decision on which formatter to use based on known source plugins. + // @TODO Maybe we should move this decision to the source plugins? This would + // allow contrib sources also to provide their preferred default formatters. + $source_field_formatter_type = FALSE; + $source_field_formatter_settings = []; + switch ($media_type->getSource()->getPluginId()) { + case 'image': + $source_field_formatter_type = 'image'; + $source_field_formatter_settings = [ + 'image_style' => '', + 'image_link' => '', + ]; + break; + + case 'file': + $source_field_formatter_type = 'file_default'; + break; + + } + + if ($source_field_formatter_type) { + $display->setComponent($field_name, [ + 'type' => 'entity_reference_field', + 'settings' => [ + 'field_name' => $source_field_name, + 'type' => $source_field_formatter_type, + 'settings' => $source_field_formatter_settings, + 'label' => 'hidden', + ], + ])->save(); + } +} diff --git a/core/tests/Drupal/FunctionalJavascriptTests/EntityReference/EntityReferenceFieldFormatterTest.php b/core/tests/Drupal/FunctionalJavascriptTests/EntityReference/EntityReferenceFieldFormatterTest.php index 3f1c64d..16058db 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/EntityReference/EntityReferenceFieldFormatterTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/EntityReference/EntityReferenceFieldFormatterTest.php @@ -96,8 +96,7 @@ public function testEntityReferenceFieldFormatter() { $page->selectFieldOption('fields[field_related_content][type]', 'entity_reference_field'); $result = $assert_session->waitForElementVisible('css', '.field-plugin-summary-cell .ajax-new-content'); $this->assertNotEmpty($result); - $assert_session->pageTextContains('The field "Title" will be used by default.'); - $assert_session->pageTextContains('The "Plain text" formatter will be used by default.'); + $assert_session->pageTextContains('Displaying the field "Title", using the formatter "Plain text".'); // Save without choosing anything and verify that the child title is shown // by default. @@ -133,8 +132,7 @@ public function testEntityReferenceFieldFormatter() { $page->pressButton('Update'); $result = $assert_session->waitForElementVisible('css', '.field-plugin-summary-cell .ajax-new-content'); $this->assertNotEmpty($result); - $assert_session->pageTextContains('Field "Body" displayed.'); - $assert_session->pageTextContains('Formatter "Default" used.'); + $assert_session->pageTextContains('Displaying the field "Body", using the formatter "Default".'); $page->pressButton('Save'); $this->drupalGet('/node/' . $this->testParentNode->id());