diff --git a/core/modules/file/file.module b/core/modules/file/file.module index c63fff4..1c7bf20 100644 --- a/core/modules/file/file.module +++ b/core/modules/file/file.module @@ -49,6 +49,26 @@ function file_help($route_name, RouteMatchInterface $route_match) { } /** + * Implements hook_field_formatter_info_alter(). + */ +function file_field_formatter_info_alter(&$info) { + // Allow the file formatters to be used on entity reference and image fields. + $info['file_default']['field_types'][] = 'entity_reference'; + $info['file_default']['field_types'][] = 'image'; + $info['file_table']['field_types'][] = 'entity_reference'; + $info['file_table']['field_types'][] = 'image'; + $info['file_url_plain']['field_types'][] = 'entity_reference'; + $info['file_url_plain']['field_types'][] = 'image'; + $info['file_rss_enclosure']['field_types'][] = 'entity_reference'; + $info['file_rss_enclosure']['field_types'][] = 'image'; + + // Allow entity reference formatters to be used on file fields. + $info['entity_reference_entity_id']['field_types'][] = 'file'; + $info['entity_reference_entity_view']['field_types'][] = 'file'; + $info['entity_reference_label']['field_types'][] = 'file'; +} + +/** * Loads file entities from the database. * * @param array|null $fids diff --git a/core/modules/file/src/Plugin/Field/FieldFormatter/FileFormatterBase.php b/core/modules/file/src/Plugin/Field/FieldFormatter/FileFormatterBase.php index ff17323..99f4126 100644 --- a/core/modules/file/src/Plugin/Field/FieldFormatter/FileFormatterBase.php +++ b/core/modules/file/src/Plugin/Field/FieldFormatter/FileFormatterBase.php @@ -4,8 +4,10 @@ use Drupal\Core\Access\AccessResult; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceFormatterBase; use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem; +use Drupal\file\Plugin\Field\FieldType\FileItem; /** * Base class for file formatters. @@ -16,7 +18,12 @@ * {@inheritdoc} */ protected function needsEntityLoad(EntityReferenceItem $item) { - return parent::needsEntityLoad($item) && $item->isDisplayed(); + if ($item instanceof FileItem) { + return parent::needsEntityLoad($item) && $item->isDisplayed(); + } + else { + return parent::needsEntityLoad($item); + } } /** @@ -34,4 +41,13 @@ protected function checkAccess(EntityInterface $entity) { } } + /** + * {@inheritdoc} + */ + public static function isApplicable(FieldDefinitionInterface $field_definition) { + // Since file formatters may be re-used on entity reference fields, ensure + // that a file entity type is being referenced by the field. + return $field_definition->getSetting('target_type') === 'file'; + } + } diff --git a/core/modules/file/tests/src/Kernel/Formatter/FileEntityFormatterTest.php b/core/modules/file/tests/src/Kernel/Formatter/FileEntityFormatterTest.php index 743da80..db8e359 100644 --- a/core/modules/file/tests/src/Kernel/Formatter/FileEntityFormatterTest.php +++ b/core/modules/file/tests/src/Kernel/Formatter/FileEntityFormatterTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\file\Kernel\Formatter; use Drupal\Core\Entity\Entity\EntityViewDisplay; +use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Url; use Drupal\file\Entity\File; use Drupal\KernelTests\KernelTestBase; @@ -165,4 +166,47 @@ public function testFormatterFileSize() { } } + /** + * Tests that file formatters can be used on entity reference fields. + */ + public function testFileFormattersOnEntityReferenceFields() { + /** @var \Drupal\Core\Field\FormatterPluginManager $formatter_plugin_manager */ + $formatter_plugin_manager = \Drupal::service('plugin.manager.field.formatter'); + + $applicable_field_definition = BaseFieldDefinition::create('entity_reference') + ->setSetting('target_type', 'file'); + $inapplicable_field_definition = BaseFieldDefinition::create('entity_reference') + ->setSetting('target_type', 'user'); + + $file_formatters = ['file_default', 'file_table', 'file_url_plain', 'file_rss_enclosure']; + foreach ($file_formatters as $file_formatter) { + $formatter_options = array( + 'field_definition' => $applicable_field_definition, + 'view_mode' => 'default', + 'configuration' => array( + 'type' => $file_formatter, + ), + ); + + // Check that file formatters can be used for an entity reference field + // that targets file entities. + $instance = $formatter_plugin_manager->getInstance($formatter_options); + $this->assertEquals($file_formatter, $instance->getPluginId()); + + // Check that file formatters can not be used for an entity reference + // field that doesn't targets file entities, and that the default + // formatter is used. + $formatter_options['field_definition'] = $inapplicable_field_definition; + $instance = $formatter_plugin_manager->getInstance($formatter_options); + $this->assertNotEquals($file_formatter, $instance->getPluginId()); + $this->assertEquals('entity_reference_label', $instance->getPluginId()); + } + + // Check that file reference fields can use the generic entity reference + // formatters. + $entity_reference_formatters = ['entity_reference_entity_id', 'entity_reference_entity_view', 'entity_reference_label']; + $available_formatters = array_keys($formatter_plugin_manager->getOptions('file')); + $this->assertTrue(!array_diff($entity_reference_formatters, $available_formatters), 'Entity reference formatters can be used by the file reference field.'); + } + } diff --git a/core/modules/image/image.module b/core/modules/image/image.module index 7f8314a..a563353 100644 --- a/core/modules/image/image.module +++ b/core/modules/image/image.module @@ -98,6 +98,20 @@ function image_help($route_name, RouteMatchInterface $route_match) { } /** + * Implements hook_field_formatter_info_alter(). + */ +function image_field_formatter_info_alter(&$info) { + // Allow the image formatter to be used on entity reference and file fields. + $info['image']['field_types'][] = 'entity_reference'; + $info['image']['field_types'][] = 'file'; + + // Allow entity reference formatters to be used on image fields. + $info['entity_reference_entity_id']['field_types'][] = 'image'; + $info['entity_reference_entity_view']['field_types'][] = 'image'; + $info['entity_reference_label']['field_types'][] = 'image'; +} + +/** * Implements hook_theme(). */ function image_theme() { diff --git a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php index 4c3a27d..ccc01f2 100644 --- a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php @@ -21,7 +21,9 @@ * id = "image", * label = @Translation("Image"), * field_types = { - * "image" + * "entity_reference", + * "file", + * "image", * } * ) */ diff --git a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatterBase.php b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatterBase.php index 19b89b1..0ae30b5 100644 --- a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatterBase.php +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatterBase.php @@ -42,7 +42,34 @@ protected function getEntitiesToView(EntityReferenceFieldItemListInterface $item } } - return parent::getEntitiesToView($items, $langcode); + $entities = parent::getEntitiesToView($items, $langcode); + + // If this formatter is being used on a non-image field, ensure only images + // are rendered, and have width and height properties set. + if ($this->fieldDefinition->getType() !== 'image') { + foreach ($entities as $delta => $entity) { + $image = \Drupal::service('image.factory')->get($entity->getFileUri()); + if ($image->isValid()) { + $item = $entity->_referringItem; + $properties = $item->getProperties(); + // @todo Setting width and height manually could be avoided if these + // were available on the file entity itself. + // @see https://www.drupal.org/node/1448124 + if (!isset($properties['width']) || $item->get('width') === NULL) { + $item->set('width', $image->getWidth()); + } + if (!isset($properties['height']) || $item->get('height') === NULL) { + $item->set('height', $image->getHeight()); + } + } + else { + // Files not supported as images should not be displayed. + unset($entities[$delta]); + } + } + } + + return $entities; } } diff --git a/core/modules/image/tests/src/Kernel/ImageFormatterTest.php b/core/modules/image/tests/src/Kernel/ImageFormatterTest.php index d1b132e..147861a 100644 --- a/core/modules/image/tests/src/Kernel/ImageFormatterTest.php +++ b/core/modules/image/tests/src/Kernel/ImageFormatterTest.php @@ -3,10 +3,13 @@ namespace Drupal\Tests\image\Kernel; use Drupal\Component\Utility\Unicode; +use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\entity_test\Entity\EntityTest; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; +use Drupal\file\Plugin\Field\FieldType\FileItem; +use Drupal\image\Plugin\Field\FieldType\ImageItem; use Drupal\Tests\field\Kernel\FieldKernelTestBase; /** @@ -99,4 +102,89 @@ function testImageFormatterCacheTags() { $this->assertEquals($entity->{$this->fieldName}[1]->entity->getCacheTags(), $build[$this->fieldName][1]['#cache']['tags'], 'Second image cache tags is as expected'); } + /** + * Tests that image formatters can be used on entity reference fields. + */ + public function testImageFormatterOnEntityReferenceFields() { + /** @var \Drupal\Core\Field\FormatterPluginManager $formatter_plugin_manager */ + $formatter_plugin_manager = \Drupal::service('plugin.manager.field.formatter'); + + $applicable_field_definition = BaseFieldDefinition::create('entity_reference') + ->setSetting('target_type', 'file'); + $inapplicable_field_definition = BaseFieldDefinition::create('entity_reference') + ->setSetting('target_type', 'user'); + + $formatter_options = array( + 'field_definition' => $applicable_field_definition, + 'view_mode' => 'default', + 'configuration' => array( + 'type' => 'image', + ), + ); + + // Check that image formatters can be used for an entity reference field + // that targets image entities. + $instance = $formatter_plugin_manager->getInstance($formatter_options); + $this->assertEquals('image', $instance->getPluginId()); + + // Check that image formatters can not be used for an entity reference + // field that doesn't targets image entities, and that the default + // formatter is used. + $formatter_options['field_definition'] = $inapplicable_field_definition; + $instance = $formatter_plugin_manager->getInstance($formatter_options); + $this->assertNotEquals('image', $instance->getPluginId()); + $this->assertEquals('entity_reference_label', $instance->getPluginId()); + + // Check that image reference fields can use the generic entity reference + // formatters. + $entity_reference_formatters = ['entity_reference_entity_id', 'entity_reference_entity_view', 'entity_reference_label']; + $available_formatters = array_keys($formatter_plugin_manager->getOptions('image')); + $this->assertTrue(!array_diff($entity_reference_formatters, $available_formatters), 'Entity reference formatters can be used by the image reference field.'); + + // Check that the image formatter only outputs relevant field items when + // used on a non-image reference field (e.g. file field). + $this->fieldName = Unicode::strtolower($this->randomMachineName()); + FieldStorageConfig::create(array( + 'entity_type' => $this->entityType, + 'field_name' => $this->fieldName, + 'type' => 'file', + 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, + ))->save(); + $field_definition = FieldConfig::create([ + 'entity_type' => $this->entityType, + 'field_name' => $this->fieldName, + 'bundle' => $this->bundle, + 'settings' => [ + 'file_extensions' => 'txt jpg', + ], + ]); + $field_definition->save(); + + $this->display = entity_get_display($this->entityType, $this->bundle, 'default') + ->setComponent($this->fieldName, [ + 'type' => 'image', + 'label' => 'hidden', + ]); + $this->display->save(); + + // Create a test entity with the image field set. + $entity = EntityTest::create([ + 'name' => $this->randomMachineName(), + ]); + + // Add a image value. + $entity->{$this->fieldName}->appendItem(ImageItem::generateSampleValue($field_definition)); + + // Add a non-image value. + $entity->{$this->fieldName}->appendItem(FileItem::generateSampleValue($field_definition)); + + $entity->save(); + + // Generate the render array to verify that the second value of the field + // is not rendered. + $build = $this->display->build($entity); + $this->assertTrue(isset($build[$this->fieldName][0])); + $this->assertFalse(isset($build[$this->fieldName][1])); + } + } diff --git a/core/modules/responsive_image/responsive_image.module b/core/modules/responsive_image/responsive_image.module index 89ac37f..442eb99 100644 --- a/core/modules/responsive_image/responsive_image.module +++ b/core/modules/responsive_image/responsive_image.module @@ -56,6 +56,16 @@ function responsive_image_help($route_name, RouteMatchInterface $route_match) { } /** + * Implements hook_field_formatter_info_alter(). + */ +function responsive_image_field_formatter_info_alter(&$info) { + // Allow the responsive_image formatter to be used on entity reference and + // file fields. + $info['responsive_image']['field_types'][] = 'entity_reference'; + $info['responsive_image']['field_types'][] = 'file'; +} + +/** * Implements hook_theme(). */ function responsive_image_theme() {