diff --git a/core/modules/media/config/install/core.entity_view_mode.media.embed.yml b/core/modules/media/config/install/core.entity_view_mode.media.embed.yml
new file mode 100644
index 0000000000..e45e4f3eca
--- /dev/null
+++ b/core/modules/media/config/install/core.entity_view_mode.media.embed.yml
@@ -0,0 +1,12 @@
+langcode: en
+status: true
+dependencies:
+ enforced:
+ module:
+ - media
+ module:
+ - media
+id: media.embed
+label: 'Embed'
+targetEntityType: media
+cache: true
diff --git a/core/modules/media/media.install b/core/modules/media/media.install
index 7d8764c03e..9a806dcf9c 100644
--- a/core/modules/media/media.install
+++ b/core/modules/media/media.install
@@ -9,10 +9,14 @@
use Drupal\Core\File\Exception\FileException;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Url;
+use Drupal\media\Entity\MediaType;
use Drupal\media\MediaTypeInterface;
use Drupal\media\Plugin\media\Source\OEmbedInterface;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
+use Drupal\media\Plugin\media\Source\Image;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Core\Entity\Entity\EntityViewMode;
/**
* Implements hook_install().
@@ -118,6 +122,36 @@ function media_requirements($phase) {
];
}
}
+
+ $view_mode = EntityViewMode::load('media.embed');
+ if (!empty($view_mode)) {
+ $display_repository = \Drupal::service('entity_display.repository');
+ $module_handler = \Drupal::service('module_handler');
+ foreach (MediaType::loadMultiple() as $type) {
+ $source = $type->getSource();
+ if (is_a($source, Image::class, TRUE)) {
+ /** @var \Drupal\Core\Entity\Entity\EntityViewDisplay $display */
+ $display = $display_repository->getViewDisplay('media', $type->id(), 'embed');
+ $source_field = $source->getSourceFieldDefinition($type);
+ $field_name = $source_field->getName();
+ if ($component = $display->getComponent($field_name)) {
+ if ($component['settings']['image_style'] === '') {
+ $action_item = '';
+ if ($module_handler->moduleExists('field_ui')) {
+ $url = Url::fromRoute('entity.entity_view_display.media.view_mode', ['media_type' => $type->id(), 'view_mode_name' => 'embed'])->toString();
+ $action_item = new TranslatableMarkup('If you would like to change this, add an image style to the field %field_name.', ['%field_name' => $source_field->label(), ':display' => $url]);
+ }
+
+ $requirements['media_embed_image_style_' . $type->id()] = [
+ 'title' => t('Media'),
+ 'description' => new TranslatableMarkup('The %view_mode_label display for the media type %type is not currently using an image style on the field %field_name. Not using an image style can lead to much larger file downloads. @action_item', ['%view_mode_label' => $view_mode->label(), '%field_name' => $source_field->label(), '@action_item' => $action_item, '%type' => $type->label()]),
+ 'severity' => REQUIREMENT_WARNING,
+ ];
+ }
+ }
+ }
+ }
+ }
}
return $requirements;
diff --git a/core/modules/media/media.module b/core/modules/media/media.module
index 4553f283ec..18a36c2c36 100644
--- a/core/modules/media/media.module
+++ b/core/modules/media/media.module
@@ -16,7 +16,10 @@
use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;
use Drupal\field\FieldConfigInterface;
+use Drupal\media\MediaTypeInterface;
use Drupal\media\Plugin\media\Source\OEmbedInterface;
+use Drupal\Core\Entity\EntityStorageException;
+use Drupal\Core\Entity\Entity\EntityViewMode;
/**
* Implements hook_help().
@@ -508,3 +511,64 @@ function media_field_widget_form_alter(&$element, FormStateInterface $form_state
$element['#attributes']['data-media-embed-host-entity-langcode'] = $context['items']->getLangcode();
}
}
+
+/**
+ * Ensures that the given media type has an embed view display.
+ *
+ * Embeds need a special view display to make sure that the out-of-the-box
+ * configuration doesn't output very large raw images.
+ *
+ * @param \Drupal\media\MediaTypeInterface $type
+ * The media type to configure.
+ *
+ * @return bool
+ * Returns TRUE if the entity view display was configured correctly, FALSE
+ * otherwise.
+ */
+function _media_configure_embed_view_display(MediaTypeInterface $type) {
+ // If a site builder has deleted the 'Embed' view mode, do not configure the
+ // display.
+ $view_mode = EntityViewMode::load('media.embed');
+ if (!$view_mode) {
+ return FALSE;
+ }
+
+ $display = \Drupal::service('entity_display.repository')
+ ->getViewDisplay('media', $type->id(), 'embed');
+
+ // If the embedded entity view display has already been configured, we don't
+ // need to redo it.
+ if (!$display->isNew()) {
+ return FALSE;
+ }
+ // Remove any defaults.
+ $display->set('content', []);
+ $type->getSource()->prepareViewDisplay($type, $display);
+
+ return $display->save();
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function media_form_media_type_add_form_alter(&$form, FormStateInterface $form_state, $form_id) {
+ $form['actions']['submit']['#submit'][] = '_media_type_add_form_submit';
+}
+
+/**
+ * Submit callback for media_type_add_form.
+ */
+function _media_type_add_form_submit(array &$form, FormStateInterface $form_state) {
+ $type = $form_state->getFormObject()->getEntity();
+ try {
+ if (_media_configure_embed_view_display($type)) {
+ \Drupal::messenger()->addStatus(t('The Embed view display has been created for the %type media type.', [
+ '%type' => $type->label(),
+ ]));
+ }
+ }
+ catch (EntityStorageException $e) {
+ \Drupal::messenger()->addError($e->getMessage());
+ return;
+ }
+}
diff --git a/core/modules/media/media.post_update.php b/core/modules/media/media.post_update.php
index f2a6c375f5..2599a29d37 100644
--- a/core/modules/media/media.post_update.php
+++ b/core/modules/media/media.post_update.php
@@ -5,6 +5,39 @@
* Post update functions for Media.
*/
+use Drupal\Core\Entity\Entity\EntityViewMode;
+use Drupal\media\Entity\MediaType;
+
+/**
+ * Ensure that all existing media types have an embed display.
+ */
+function media_post_update_entity_view_displays() {
+ if (!EntityViewMode::load('media.embed')) {
+ EntityViewMode::create([
+ 'id' => 'media.embed',
+ 'targetEntityType' => 'media',
+ 'label' => t('Embed'),
+ 'dependencies' => [
+ 'enforced' => [
+ 'module' => ['media'],
+ ],
+ ],
+ ])->save();
+ }
+
+ $types = [];
+ foreach (MediaType::loadMultiple() as $type) {
+ if (_media_configure_embed_view_display($type)) {
+ $types[] = $type->label();
+ }
+ }
+ if ($types) {
+ return t('The Embed view display has been created for the following media types: @types.', [
+ '@types' => implode(', ', $types),
+ ]);
+ }
+}
+
/**
* Clear caches due to changes in local tasks and action links.
*/
diff --git a/core/modules/media/src/Plugin/Filter/MediaEmbed.php b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
index 45f2a5ab45..e2cf4c0c61 100644
--- a/core/modules/media/src/Plugin/Filter/MediaEmbed.php
+++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
@@ -29,7 +29,7 @@
* description = @Translation("Embeds media items using a custom tag, <drupal-media>
. If used in conjunction with the 'Align/Caption' filters, make sure this filter is configured to run after them."),
* type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_REVERSIBLE,
* settings = {
- * "default_view_mode" = "full",
+ * "default_view_mode" = "embed",
* },
* weight = 100,
* )
diff --git a/core/modules/media/src/Plugin/media/Source/Image.php b/core/modules/media/src/Plugin/media/Source/Image.php
index 34b565c054..ecc552f888 100644
--- a/core/modules/media/src/Plugin/media/Source/Image.php
+++ b/core/modules/media/src/Plugin/media/Source/Image.php
@@ -3,6 +3,7 @@
namespace Drupal\media\Plugin\media\Source;
use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
@@ -161,4 +162,27 @@ public function createSourceField(MediaTypeInterface $type) {
return $field->set('settings', $settings);
}
+ /**
+ * {@inheritdoc}
+ */
+ public function prepareViewDisplay(MediaTypeInterface $type, EntityViewDisplayInterface $display) {
+ parent::prepareViewDisplay($type, $display);
+
+ // For the `embed` display mode, use the `large` image style and do not
+ // link the image to anything. This is done in order to ensure the image
+ // does not become too unwieldy when embedded into formatted text, and to
+ // allow it to wrapped by an author-created link. If the `large` image
+ // style has been deleted, do not set an image style.
+ if ($display->getMode() === 'embed') {
+ $field_name = $this->getSourceFieldDefinition($type)->getName();
+ $component = $display->getComponent($field_name);
+ $component['settings']['image_link'] = '';
+ $component['settings']['image_style'] = '';
+ if ($this->entityTypeManager->getStorage('image_style')->load('large')) {
+ $component['settings']['image_style'] = 'large';
+ }
+ $display->setComponent($field_name, $component);
+ }
+ }
+
}
diff --git a/core/modules/media/tests/src/Functional/Update/MediaUpdateTest.php b/core/modules/media/tests/src/Functional/Update/MediaUpdateTest.php
index ee97fe5ccd..64f3858979 100644
--- a/core/modules/media/tests/src/Functional/Update/MediaUpdateTest.php
+++ b/core/modules/media/tests/src/Functional/Update/MediaUpdateTest.php
@@ -2,7 +2,9 @@
namespace Drupal\Tests\media\Functional\Update;
+use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
+use Drupal\image\Entity\ImageStyle;
use Drupal\media\Entity\Media;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
use Drupal\user\Entity\Role;
@@ -114,4 +116,19 @@ public function testEnableStandaloneUrl() {
$this->assertSession()->statusCodeEquals(200);
}
+ /**
+ * Tests the media module post update for embed view display.
+ *
+ * @see media_post_update_entity_view_displays()
+ */
+ public function testPostUpdateEntityViewDisplays() {
+ $this->assertNull(ImageStyle::load('embed'));
+ $this->assertNull(EntityViewDisplay::load('media.file.embed'));
+ $this->assertNull(EntityViewDisplay::load('media.image.embed'));
+ $this->runUpdates();
+ $this->assertInstanceOf(EntityViewDisplay::class, EntityViewDisplay::load('media.file.embed'));
+ $this->assertInstanceOf(EntityViewDisplay::class, EntityViewDisplay::load('media.image.embed'));
+ $this->assertSession()->pageTextContains('The Embed view display has been created for the following media types: File, Image.');
+ }
+
}
diff --git a/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedDisplayModeTest.php b/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedDisplayModeTest.php
new file mode 100644
index 0000000000..05488c9af5
--- /dev/null
+++ b/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedDisplayModeTest.php
@@ -0,0 +1,149 @@
+drupalLogin($this->drupalCreateUser([
+ 'administer site configuration',
+ 'access media overview',
+ 'administer media',
+ 'administer media types',
+ 'view media',
+ // We need 'access content' for system.machine_name_transliterate.
+ 'access content',
+ ]));
+ }
+
+ /**
+ * Tests that media can automatically configure entity view displays.
+ */
+ public function testEmbedEntityViewDisplays() {
+ $page = $this->getSession()->getPage();
+ $assert_session = $this->assertSession();
+ // The 'Embed' display is not automatically created when creating a
+ // media type programmatically, only when installing the module or when
+ // creating a media type via the UI.
+ $this->createMediaType('image', [
+ 'id' => 'aramis',
+ 'field_map' => ['name' => Image::METADATA_ATTRIBUTE_NAME],
+ ]);
+ $this->assertNull(EntityViewDisplay::load('media.aramis.embed'));
+
+ // Create a non-image media type through the UI.
+ $this->drupalGet('admin/structure/media/add');
+ $page->fillField('label', 'Athos');
+ $this->assertNotEmpty($assert_session->waitForText('Machine name: athos'));
+ $page->selectFieldOption('source', 'file');
+ // Wait for form to complete with AJAX.
+ $this->assertNotEmpty($assert_session->waitForText('Field mapping'));
+ $page->pressButton('Save');
+ $assert_session->pageTextContains("The Embed view display has been created for the Athos media type.");
+ $this->assertViewDisplayConfigured('athos');
+
+ // Create an image media through the UI.
+ $this->drupalGet('admin/structure/media/add');
+ $page->fillField('label', 'Porthos');
+ $this->assertNotEmpty($assert_session->waitForText('Machine name: porthos'));
+ $page->selectFieldOption('source', 'image');
+ // Wait for form to complete with AJAX.
+ $this->assertNotEmpty($assert_session->waitForText('Field mapping'));
+ $page->pressButton('Save');
+ $assert_session->pageTextContains("The Embed view display has been created for the Porthos media type.");
+ $this->assertViewDisplayConfigured('porthos');
+
+ // Delete a view display.
+ EntityViewDisplay::load("media.porthos.embed")->delete();
+ // Test that the entity view display is not regenerated when saving
+ // existing media types after the entity view display has been deleted.
+ // We only want to create it on the `add` form, not the `edit` form.
+ $this->drupalGet('admin/structure/media/manage/porthos');
+ $page->pressButton('Save');
+ $this->assertNull(EntityViewDisplay::load('media.porthos.embed'));
+
+ // If for some reason a site builder deletes the 'large' image style, it
+ // should be recreated when adding a new media type with an image source.
+ ImageStyle::load('large')->delete();
+ $this->drupalGet('admin/structure/media/add');
+ $page->fillField('label', 'Madame Bonacieux');
+ $this->assertNotEmpty($assert_session->waitForText('Machine name: madame_bonacieux'));
+ $page->selectFieldOption('source', 'image');
+ // Wait for form to complete with AJAX.
+ $this->assertNotEmpty($assert_session->waitForText('Field mapping'));
+ $page->pressButton('Save');
+ $assert_session->pageTextContains("The Embed view display has been created for the Madame Bonacieux media type.");
+ $this->assertViewDisplayConfigured('madame_bonacieux');
+ // Test that hook_requirements adds warning about the lack of an image style.
+ $this->drupalGet('/admin/reports/status');
+ $assert_session->pageTextContains('The Embed display for the media type Madame Bonacieux is not currently using an image style on the field Image.');
+
+ // If for some reason a site builder deletes the embed view mode, an
+ // entity view display should not be configured when creating a media type
+ // through the UI.
+ EntityViewMode::load('media.embed')->delete();
+ $this->drupalGet('admin/structure/media/add');
+ $page->fillField('label', "D'Artagnan");
+ $this->assertNotEmpty($assert_session->waitForText('Machine name: d_artagnan'));
+ $page->selectFieldOption('source', 'image');
+ // Wait for form to complete with AJAX.
+ $this->assertNotEmpty($assert_session->waitForText('Field mapping'));
+ $page->pressButton('Save');
+ $assert_session->pageTextContains("The media type d'Artagnan has been added.");
+ $assert_session->pageTextNotContains("The Embed view display has been created for the d'Artagnan media type.");
+ $this->assertNull(EntityViewDisplay::load('media.d_artagnan.embed'));
+ }
+
+ /**
+ * Asserts the embed view display components for a media type.
+ *
+ * @param string $type_id
+ * The media type ID.
+ */
+ protected function assertViewDisplayConfigured($type_id) {
+ $type = MediaType::load($type_id);
+ $display = EntityViewDisplay::load('media.' . $type_id . '.embed');
+ $this->assertInstanceOf(EntityViewDisplay::class, $display);
+ $source_field_definition = $type->getSource()->getSourceFieldDefinition($type);
+ $component = $display->getComponent($source_field_definition->getName());
+ $this->assertInternalType('array', $component);
+ if ($source_field_definition->getType() === 'image') {
+ if (is_a(ImageStyle::load('large'), ImageStyleInterface::class, TRUE)) {
+ $this->assertSame('large', $component['settings']['image_style']);
+ }
+ else {
+ $this->assertEmpty($component['settings']['image_style']);
+ }
+ $this->assertEmpty($component['settings']['image_link']);
+ }
+ }
+
+}
diff --git a/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php
index abe4442e12..2b2734d158 100644
--- a/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php
+++ b/core/modules/media/tests/src/Kernel/MediaEmbedFilterTest.php
@@ -44,7 +44,7 @@ public function testBasics(array $embed_attributes, $expected_view_mode, array $
* Data provider for testBasics().
*/
public function providerTestBasics() {
- $expected_cacheability_full = (new CacheableMetadata())
+ $expected_cacheability = (new CacheableMetadata())
->setCacheTags([
'_media_test_filter_access:media:1',
'_media_test_filter_access:user:2',
@@ -58,14 +58,14 @@ public function providerTestBasics() {
->setCacheMaxAge(Cache::PERMANENT);
return [
- 'data-entity-uuid only ⇒ default view mode "full" used' => [
+ 'data-entity-uuid only ⇒ default view mode "embed" used' => [
[
'data-entity-type' => 'media',
'data-entity-uuid' => static::EMBEDDED_ENTITY_UUID,
],
- 'full',
+ 'embed',
[],
- $expected_cacheability_full,
+ $expected_cacheability,
],
'data-entity-uuid + data-view-mode=full ⇒ specified view mode used' => [
[
@@ -75,7 +75,17 @@ public function providerTestBasics() {
],
'full',
[],
- $expected_cacheability_full,
+ $expected_cacheability,
+ ],
+ 'data-entity-uuid + data-view-mode=embed ⇒ specified view mode used' => [
+ [
+ 'data-entity-type' => 'media',
+ 'data-entity-uuid' => static::EMBEDDED_ENTITY_UUID,
+ 'data-view-mode' => 'embed',
+ ],
+ 'embed',
+ [],
+ $expected_cacheability,
],
'data-entity-uuid + data-view-mode=foobar ⇒ specified view mode used' => [
[
@@ -103,12 +113,12 @@ public function providerTestBasics() {
'data-entity-type' => 'media',
'data-entity-uuid' => static::EMBEDDED_ENTITY_UUID,
],
- 'full',
+ 'embed',
[
'data-foo' => 'bar',
'foo' => 'bar',
],
- $expected_cacheability_full,
+ $expected_cacheability,
],
];
}
@@ -138,7 +148,7 @@ public function testAccessUnpublished($allowed_to_view_unpublished, $expected_re
$this->assertEmpty($this->getRawContent());
}
else {
- $this->assertCount(1, $this->cssSelect('div[data-media-embed-test-view-mode="full"]'));
+ $this->assertCount(1, $this->cssSelect('div[data-media-embed-test-view-mode="embed"]'));
}
$this->assertSame($expected_cacheability->getCacheTags(), $result->getCacheTags());
@@ -375,7 +385,7 @@ public function testRecursionProtection() {
// Render and verify the presence of the embedded entity 20 times.
for ($i = 0; $i < 20; $i++) {
$this->applyFilter($text);
- $this->assertCount(1, $this->cssSelect('div[data-media-embed-test-view-mode="full"]'));
+ $this->assertCount(1, $this->cssSelect('div[data-media-embed-test-view-mode="embed"]'));
}
// Render a 21st time, this is exceeding the recursion limit. The entity
@@ -399,7 +409,7 @@ public function testFilterIntegration(array $filter_ids, array $additional_attri
$result = $this->processText($content, 'en', $filter_ids);
$this->setRawContent($result->getProcessedText());
$this->assertCount($expected_verification_success ? 1 : 0, $this->cssSelect($verification_selector));
- $this->assertCount(1, $this->cssSelect('div[data-media-embed-test-view-mode="full"]'));
+ $this->assertCount(1, $this->cssSelect('div[data-media-embed-test-view-mode="embed"]'));
$this->assertSame([
'_media_test_filter_access:media:1',
'_media_test_filter_access:user:2',
diff --git a/core/profiles/demo_umami/config/install/core.entity_view_display.media.image.embed.yml b/core/profiles/demo_umami/config/install/core.entity_view_display.media.image.embed.yml
new file mode 100644
index 0000000000..92a96dc0c0
--- /dev/null
+++ b/core/profiles/demo_umami/config/install/core.entity_view_display.media.image.embed.yml
@@ -0,0 +1,30 @@
+langcode: en
+status: true
+dependencies:
+ config:
+ - core.entity_view_mode.media.embed
+ - field.field.media.image.field_media_image
+ - image.style.large
+ - media.type.image
+ module:
+ - image
+id: media.image.embed
+targetEntityType: media
+bundle: image
+mode: embed
+content:
+ field_media_image:
+ label: visually_hidden
+ settings:
+ image_style: large
+ image_link: ''
+ third_party_settings: { }
+ type: image
+ weight: 1
+ region: content
+hidden:
+ created: true
+ langcode: true
+ name: true
+ thumbnail: true
+ uid: true
diff --git a/core/profiles/standard/config/optional/core.entity_view_display.media.image.embed.yml b/core/profiles/standard/config/optional/core.entity_view_display.media.image.embed.yml
new file mode 100644
index 0000000000..92a96dc0c0
--- /dev/null
+++ b/core/profiles/standard/config/optional/core.entity_view_display.media.image.embed.yml
@@ -0,0 +1,30 @@
+langcode: en
+status: true
+dependencies:
+ config:
+ - core.entity_view_mode.media.embed
+ - field.field.media.image.field_media_image
+ - image.style.large
+ - media.type.image
+ module:
+ - image
+id: media.image.embed
+targetEntityType: media
+bundle: image
+mode: embed
+content:
+ field_media_image:
+ label: visually_hidden
+ settings:
+ image_style: large
+ image_link: ''
+ third_party_settings: { }
+ type: image
+ weight: 1
+ region: content
+hidden:
+ created: true
+ langcode: true
+ name: true
+ thumbnail: true
+ uid: true
diff --git a/core/profiles/standard/tests/src/Functional/StandardTest.php b/core/profiles/standard/tests/src/Functional/StandardTest.php
index f62e183fca..6dc6be2863 100644
--- a/core/profiles/standard/tests/src/Functional/StandardTest.php
+++ b/core/profiles/standard/tests/src/Functional/StandardTest.php
@@ -256,6 +256,19 @@ public function testStandard() {
$date_field = $assert_session->fieldExists('Date', $form)->getOuterHtml();
$published_checkbox = $assert_session->fieldExists('Published', $form)->getOuterHtml();
$this->assertTrue(strpos($form_html, $published_checkbox) > strpos($form_html, $date_field));
+ // Assert an "embed" entity view display option exists for each media
+ // type, and that it is preconfigured for the "Image" media type.
+ $this->drupalGet('/admin/structure/media/manage/' . $media_type->id() . '/display');
+ $page->hasUncheckedField('display_modes_custom[full]');
+ if ($media_type === 'image') {
+ $page->hasCheckedField('display_modes_custom[embed]');
+ $this->drupalGet('admin/structure/media/manage/image/display/embed');
+ $this->assertTrue($assert_session->optionExists('fields[field_media_image][type]', 'image')->isSelected());
+ $assert_session->elementTextContains('css', 'tr[data-drupal-selector="edit-fields-field-media-image"]', 'Image style: Large (480×480)');
+ }
+ else {
+ $page->hasUncheckedField('display_modes_custom[embed]');
+ }
}
}