diff --git a/config/schema/dynamic_entity_reference.schema.yml b/config/schema/dynamic_entity_reference.schema.yml index fe29f09..1e6c4b3 100644 --- a/config/schema/dynamic_entity_reference.schema.yml +++ b/config/schema/dynamic_entity_reference.schema.yml @@ -46,15 +46,18 @@ field.value.dynamic_entity_reference: label: 'Type of item to reference' field.formatter.settings.dynamic_entity_reference_entity_view: - type: mapping + type: sequence label: 'Dynamic entity reference rendered entity display format settings' - mapping: - view_mode: - type: string - label: 'View mode' - link: - type: boolean - label: 'Show links' + sequence: + type: mapping + label: 'Entity Type ID' + mapping: + view_mode: + type: string + label: 'View modes' + link: + type: boolean + label: 'Show links' field.formatter.settings.dynamic_entity_reference_entity_id: type: mapping diff --git a/src/Plugin/Field/FieldFormatter/DynamicEntityReferenceEntityFormatter.php b/src/Plugin/Field/FieldFormatter/DynamicEntityReferenceEntityFormatter.php index 6ef1d5a..f0b11e6 100644 --- a/src/Plugin/Field/FieldFormatter/DynamicEntityReferenceEntityFormatter.php +++ b/src/Plugin/Field/FieldFormatter/DynamicEntityReferenceEntityFormatter.php @@ -3,7 +3,10 @@ namespace Drupal\dynamic_entity_reference\Plugin\Field\FieldFormatter; use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceEntityFormatter; +use Drupal\Core\Form\FormStateInterface; +use Drupal\dynamic_entity_reference\Plugin\Field\FieldType\DynamicEntityReferenceItem; /** * Plugin implementation of the 'rendered entity' formatter. @@ -28,4 +31,112 @@ class DynamicEntityReferenceEntityFormatter extends EntityReferenceEntityFormatt return TRUE; } + /** + * {@inheritdoc} + */ + public static function defaultSettings() { + $labels = \Drupal::service('entity_type.repository')->getEntityTypeLabels(TRUE); + $options = array_keys($labels[(string) t('Content', [], ['context' => 'Entity type group'])]); + return array_fill_keys($options, ['view_mode' => 'default', 'link' => FALSE]); + } + + /** + * {@inheritdoc} + */ + public function settingsSummary() { + $labels = \Drupal::service('entity_type.repository')->getEntityTypeLabels(TRUE); + $options = $labels[(string) t('Content', [], ['context' => 'Entity type group'])]; + $entity_type_ids = DynamicEntityReferenceItem::getTargetTypes($this->getFieldSettings()); + $available = []; + foreach ($this->getSettings() as $key => $value) { + if (in_array($key, array_values($entity_type_ids))) { + $available[$key] = $value; + } + } + if (!empty($available)) { + return array_map(function ($entity_type_id, $settings) use ($options) { + return t('Rendered %entity as view mode: @mode', [ + '%entity' => $options[$entity_type_id], + '@mode' => $settings['view_mode'], + ]); + }, array_keys($available), $available); + } + else { + return []; + } + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $labels = \Drupal::service('entity_type.repository')->getEntityTypeLabels(TRUE); + $options = $labels[(string) t('Content', [], ['context' => 'Entity type group'])]; + $entity_type_ids = DynamicEntityReferenceItem::getTargetTypes($this->getFieldSettings()); + $elements['view_mode'] = []; + + foreach ($entity_type_ids as $entity_type_id) { + $elements[$entity_type_id] = [ + '#type' => 'container', + ]; + $elements[$entity_type_id]['view_mode'] = [ + '#type' => 'select', + '#options' => $this->entityDisplayRepository->getViewModeOptions($entity_type_id), + '#title' => t('View mode for %entity', ['%entity' => $options[$entity_type_id]]), + '#default_value' => $this->getSetting($entity_type_id)['view_mode'], + '#required' => TRUE, + ]; + } + + return $elements; + } + + /** + * {@inheritdoc} + */ + public function viewElements(FieldItemListInterface $items, $langcode) { + $elements = []; + + foreach ($this->getEntitiesToView($items, $langcode) as $delta => $entity) { + // Due to render caching and delayed calls, the viewElements() method + // will be called later in the rendering process through a '#pre_render' + // callback, so we need to generate a counter that takes into account + // all the relevant information about this field and the referenced + // entity that is being rendered. + $recursive_render_id = $items->getFieldDefinition()->getTargetEntityTypeId() + . $items->getFieldDefinition()->getTargetBundle() + . $items->getName() + . $entity->id(); + + if (isset(static::$recursiveRenderDepth[$recursive_render_id])) { + static::$recursiveRenderDepth[$recursive_render_id]++; + } + else { + static::$recursiveRenderDepth[$recursive_render_id] = 1; + } + + // Protect ourselves from recursive rendering. + if (static::$recursiveRenderDepth[$recursive_render_id] > static::RECURSIVE_RENDER_LIMIT) { + $this->loggerFactory->get('entity')->error('Recursive rendering detected when rendering entity %entity_type: %entity_id, using the %field_name field on the %bundle_name bundle. Aborting rendering.', [ + '%entity_type' => $entity->getEntityTypeId(), + '%entity_id' => $entity->id(), + '%field_name' => $items->getName(), + '%bundle_name' => $items->getFieldDefinition()->getTargetBundle(), + ]); + return $elements; + } + $entity_type_id = $entity->getEntityTypeId(); + $view_builder = $this->entityTypeManager->getViewBuilder($entity_type_id); + $elements[$delta] = $view_builder->view($entity, $this->getSetting($entity_type_id)['view_mode'], $entity->language()->getId()); + // Add a resource attribute to set the mapping property's value to the + // entity's url. Since we don't know what the markup of the entity will + // be, we shouldn't rely on it for structured data such as RDFa. + if (!empty($items[$delta]->_attributes) && !$entity->isNew() && $entity->hasLinkTemplate('canonical')) { + $items[$delta]->_attributes += array('resource' => $entity->toUrl()->toString()); + } + } + + return $elements; + } + } diff --git a/tests/src/FunctionalJavascript/DynamicEntityReferenceTest.php b/tests/src/FunctionalJavascript/DynamicEntityReferenceTest.php index df92b06..7c5849f 100644 --- a/tests/src/FunctionalJavascript/DynamicEntityReferenceTest.php +++ b/tests/src/FunctionalJavascript/DynamicEntityReferenceTest.php @@ -56,6 +56,7 @@ class DynamicEntityReferenceTest extends JavascriptTestBase { 'field_ui', 'dynamic_entity_reference', 'entity_test', + 'node', ]; /** @@ -68,6 +69,8 @@ class DynamicEntityReferenceTest extends JavascriptTestBase { 'view test entity', 'administer entity_test fields', 'administer entity_test content', + 'administer node fields', + 'administer node display', 'access user profiles', ]; @@ -187,6 +190,76 @@ class DynamicEntityReferenceTest extends JavascriptTestBase { } /** + * Tests view modes in formatter of dynamic entity reference field. + */ + public function testFieldFormatterViewModes() { + $assert_session = $this->assertSession(); + $this->drupalLogin($this->adminUser); + $this->drupalCreateContentType(['type' => 'test_content']); + $this->drupalGet('/admin/structure/types/manage/test_content/fields/add-field'); + $edit = [ + 'label' => 'Foobar', + 'field_name' => 'foobar', + 'new_storage_type' => 'dynamic_entity_reference', + ]; + $this->submitForm($edit, t('Save and continue'), 'field-ui-field-storage-add-form'); + $page = $this->getSession()->getPage(); + $entity_type_ids_select = $assert_session->selectExists('settings[entity_type_ids][]', $page); + $entity_type_ids_select->selectOption('user'); + $assert_session->selectExists('cardinality', $page) + ->selectOption(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED); + $page->uncheckField('settings[exclude_entity_types]'); + $this->submitForm([], t('Save field settings'), 'field-storage-config-edit-form'); + $this->drupalGet('admin/structure/types/manage/test_content/display'); + $page = $this->getSession()->getPage(); + $formats = $assert_session->selectExists('fields[field_foobar][type]', $page); + $formats->selectOption('dynamic_entity_reference_entity_view'); + $assert_session->assertWaitOnAjaxRequest(); + $page->pressButton('Edit'); + $assert_session->assertWaitOnAjaxRequest(); + $page = $this->getSession()->getPage(); + $assert_session->selectExists('fields[field_foobar][settings_edit_form][settings][user][view_mode]', $page); + $assert_session->optionExists('fields[field_foobar][settings_edit_form][settings][user][view_mode]', 'compact', $page); + $assert_session->optionExists('fields[field_foobar][settings_edit_form][settings][user][view_mode]', 'full', $page); + // Edit field, turn on exclude entity types and check display again. + $this->drupalGet('admin/structure/types/manage/test_content/fields/node.test_content.field_foobar/storage'); + $page->checkField('settings[exclude_entity_types]'); + $this->submitForm([], t('Save field settings'), 'field-storage-config-edit-form'); + $this->drupalGet('admin/structure/types/manage/test_content/display'); + $page = $this->getSession()->getPage(); + $formats = $assert_session->selectExists('fields[field_foobar][type]', $page); + $formats->selectOption('dynamic_entity_reference_entity_view'); + $assert_session->assertWaitOnAjaxRequest(); + // Assert node view mode is set on default. + $assert_session->responseContains("Rendered Content as view mode: default"); + $page->pressButton('Edit'); + $assert_session->assertWaitOnAjaxRequest(); + $page = $this->getSession()->getPage(); + // Assert we have multi select form items for view mode settings. + $assert_session->selectExists('fields[field_foobar][settings_edit_form][settings][entity_test_with_bundle][view_mode]', $page); + $assert_session->responseContains("View mode for Test entity with bundle"); + $assert_session->optionExists('fields[field_foobar][settings_edit_form][settings][entity_test_with_bundle][view_mode]', 'default', $page); + $assert_session->optionNotExists('fields[field_foobar][settings_edit_form][settings][entity_test_with_bundle][view_mode]', 'rss', $page); + $node_view_modes = $assert_session->selectExists('fields[field_foobar][settings_edit_form][settings][node][view_mode]', $page); + $assert_session->responseContains("View mode for Content"); + $assert_session->optionExists('fields[field_foobar][settings_edit_form][settings][node][view_mode]', 'default', $page); + $assert_session->optionExists('fields[field_foobar][settings_edit_form][settings][node][view_mode]', 'full', $page); + $assert_session->optionExists('fields[field_foobar][settings_edit_form][settings][node][view_mode]', 'rss', $page); + $assert_session->optionExists('fields[field_foobar][settings_edit_form][settings][node][view_mode]', 'teaser', $page); + // Select different select options and assert summary is changed properly. + $node_view_modes->selectOption('teaser'); + $page->pressButton('Update'); + $assert_session->assertWaitOnAjaxRequest(); + $assert_session->responseContains("Rendered Content as view mode: teaser"); + $page->pressButton('Edit'); + $assert_session->assertWaitOnAjaxRequest(); + $node_view_modes->selectOption('rss'); + $page->pressButton('Update'); + $assert_session->assertWaitOnAjaxRequest(); + $assert_session->responseContains("Rendered Content as view mode: rss"); + } + + /** * Creates auto complete path for the given target type. * * @param string $target_type diff --git a/tests/src/Kernel/DynamicEntityReferenceFormatterTest.php b/tests/src/Kernel/DynamicEntityReferenceFormatterTest.php index b5bd51f..87fca8f 100644 --- a/tests/src/Kernel/DynamicEntityReferenceFormatterTest.php +++ b/tests/src/Kernel/DynamicEntityReferenceFormatterTest.php @@ -182,6 +182,7 @@ class DynamicEntityReferenceFormatterTest extends EntityKernelTestBase { entity_get_display($this->entityType, $this->bundle, 'default') ->setComponent($field_name, [ 'type' => $formatter, + 'settings' => $formatter == 'dynamic_entity_reference_entity_view' ? ['view_mode' => [$referencing_entity->getEntityTypeId() => 'default']] : [], ]) ->save(); @@ -212,7 +213,16 @@ class DynamicEntityReferenceFormatterTest extends EntityKernelTestBase { /** @var \Drupal\Core\Render\RendererInterface $renderer */ $renderer = $this->container->get('renderer'); $formatter = 'dynamic_entity_reference_entity_view'; - $build = $this->buildRenderArray([$this->referencedEntity, $this->unsavedReferencedEntity], $formatter); + $build = $this->buildRenderArray([$this->referencedEntity, $this->unsavedReferencedEntity], $formatter, [ + $this->referencedEntity->getEntityTypeId() => [ + 'view_mode' => 'default', + 'link' => FALSE, + ], + $this->unsavedReferencedEntity->getEntityTypeId() => [ + 'view_mode' => 'default', + 'link' => FALSE, + ], + ]); // Test the first field item. $expected_rendered_name_field_1 = ' diff --git a/tests/src/Kernel/DynamicEntityReferenceSchemaTest.php b/tests/src/Kernel/DynamicEntityReferenceSchemaTest.php index 696bd68..07e6647 100644 --- a/tests/src/Kernel/DynamicEntityReferenceSchemaTest.php +++ b/tests/src/Kernel/DynamicEntityReferenceSchemaTest.php @@ -142,8 +142,10 @@ class DynamicEntityReferenceSchemaTest extends EntityKernelTestBase { 'type' => 'dynamic_entity_reference_entity_view', 'label' => 'above', 'settings' => [ - 'view_mode' => 'default', - 'link' => FALSE, + $referenced_entity->getEntityTypeId() => [ + 'view_mode' => 'default', + 'link' => FALSE, + ], ], 'third_party_settings' => [], ])->save();