diff --git a/src/Annotation/EntityEmbedDisplay.php b/src/Annotation/EntityEmbedDisplay.php
index 0c21b18..3339088 100644
--- a/src/Annotation/EntityEmbedDisplay.php
+++ b/src/Annotation/EntityEmbedDisplay.php
@@ -55,4 +55,14 @@ class EntityEmbedDisplay extends Plugin {
*/
public $no_ui = FALSE;
+ /**
+ * Alt and title access.
+ *
+ * Whether the plugin supports per-embed alt and title overrides for media
+ * entities with an image source.
+ *
+ * @var bool
+ */
+ public $supports_image_alt_and_title = FALSE;
+
}
diff --git a/src/EntityEmbedDisplay/EntityEmbedDisplayBase.php b/src/EntityEmbedDisplay/EntityEmbedDisplayBase.php
index a696fbc..3cb86cd 100644
--- a/src/EntityEmbedDisplay/EntityEmbedDisplayBase.php
+++ b/src/EntityEmbedDisplay/EntityEmbedDisplayBase.php
@@ -237,7 +237,7 @@ abstract class EntityEmbedDisplayBase extends PluginBase implements ContainerFac
* The currently set context value.
*/
public function getContextValue($name) {
- return $this->context[$name];
+ return !empty($this->context[$name]) ? $this->context[$name] : NULL;
}
/**
@@ -323,6 +323,19 @@ abstract class EntityEmbedDisplayBase extends PluginBase implements ContainerFac
return array_key_exists($name, $attributes) ? $attributes[$name] : $default;
}
+ /**
+ * Checks if an attribute is set.
+ *
+ * @param string $name
+ * The name of the attribute.
+ *
+ * @return bool
+ * Returns TRUE if value is set.
+ */
+ public function hasAttribute($name) {
+ return array_key_exists($name, $this->getAttributeValues());
+ }
+
/**
* Gets the current language code.
*
diff --git a/src/EntityEmbedDisplay/EntityEmbedDisplayManager.php b/src/EntityEmbedDisplay/EntityEmbedDisplayManager.php
index ce560df..51353ee 100644
--- a/src/EntityEmbedDisplay/EntityEmbedDisplayManager.php
+++ b/src/EntityEmbedDisplay/EntityEmbedDisplayManager.php
@@ -7,6 +7,7 @@ use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Component\Plugin\Exception\PluginException;
+use Drupal\entity_embed\Plugin\entity_embed\EntityEmbedDisplay\MediaImageDecorator;
/**
* Provides an Entity Embed display plugin manager.
@@ -111,7 +112,12 @@ class EntityEmbedDisplayManager extends DefaultPluginManager {
* An array of valid plugin labels, keyed by plugin ID.
*/
public function getDefinitionOptionsForContext(array $context) {
- assert(empty(array_diff_key($context, ['entity' => TRUE, 'entity_type' => TRUE, 'embed_button' => TRUE])));
+ $values = [
+ 'entity' => TRUE,
+ 'entity_type' => TRUE,
+ 'embed_button' => TRUE
+ ];
+ assert(empty(array_diff_key($context, $values)));
$definitions = $this->getDefinitionsForContexts($context);
$definitions = $this->filterExposedDefinitions($definitions);
$options = array_map(function ($definition) {
@@ -176,4 +182,21 @@ class EntityEmbedDisplayManager extends DefaultPluginManager {
}, $definitions);
}
+ /**
+ * {@inheritdoc}
+ */
+ public function createInstance($plugin_id, array $configuration = []) {
+ $instance = parent::createInstance($plugin_id, $configuration);
+ $definition = $instance->getPluginDefinition();
+
+ if (empty($definition['supports_image_alt_and_title'])) {
+ return $instance;
+ }
+ else {
+ // Use decorator pattern to add alt and title fields to dialog when
+ // embedding media with image source.
+ return new MediaImageDecorator($instance);
+ }
+ }
+
}
diff --git a/src/EntityEmbedDisplay/FieldFormatterEntityEmbedDisplayBase.php b/src/EntityEmbedDisplay/FieldFormatterEntityEmbedDisplayBase.php
index 038bba2..364c345 100644
--- a/src/EntityEmbedDisplay/FieldFormatterEntityEmbedDisplayBase.php
+++ b/src/EntityEmbedDisplay/FieldFormatterEntityEmbedDisplayBase.php
@@ -169,6 +169,11 @@ abstract class FieldFormatterEntityEmbedDisplayBase extends EntityEmbedDisplayBa
$formatter = $this->getFieldFormatter();
$formatter->prepareView([$fakeEntity->id() => $items]);
$build = $formatter->viewElements($items, $this->getLangcode());
+ // Don't ever cache a representation of an embedded entity, since the host
+ // entity may be overriding specific values (such as an `alt` attribute)
+ // which means that this particular rendered representation is unique to the
+ // host entity, and hence nonsensical to cache separately anyway.
+ unset($build[0]['#cache']['keys']);
// For some reason $build[0]['#printed'] is TRUE, which means it will fail
// to render later. So for now we manually fix that.
// @todo Investigate why this is needed.
diff --git a/src/Form/EntityEmbedDialog.php b/src/Form/EntityEmbedDialog.php
index e541cb0..160aa7a 100644
--- a/src/Form/EntityEmbedDialog.php
+++ b/src/Form/EntityEmbedDialog.php
@@ -488,7 +488,10 @@ class EntityEmbedDialog extends FormBase {
];
$plugin_id = !empty($values['attributes']['data-entity-embed-display']) ? $values['attributes']['data-entity-embed-display'] : $entity_element['data-entity-embed-display'];
if (!empty($plugin_id)) {
- if (is_string($entity_element['data-entity-embed-display-settings'])) {
+ if (empty($entity_element['data-entity-embed-display-settings'])) {
+ $entity_element['data-entity-embed-display-settings'] = [];
+ }
+ elseif (is_string($entity_element['data-entity-embed-display-settings'])) {
$entity_element['data-entity-embed-display-settings'] = Json::decode($entity_element['data-entity-embed-display-settings']);
}
$display = $this->entityEmbedDisplayManager->createInstance($plugin_id, $entity_element['data-entity-embed-display-settings']);
diff --git a/src/Plugin/Derivative/FieldFormatterDeriver.php b/src/Plugin/Derivative/FieldFormatterDeriver.php
index 26a8b06..d420acc 100644
--- a/src/Plugin/Derivative/FieldFormatterDeriver.php
+++ b/src/Plugin/Derivative/FieldFormatterDeriver.php
@@ -65,9 +65,22 @@ class FieldFormatterDeriver extends DeriverBase implements ContainerDeriverInter
if (!isset($base_plugin_definition['field_type'])) {
throw new \LogicException("Undefined field_type definition in plugin {$base_plugin_definition['id']}.");
}
+
+ $no_media_image_decorator = [
+ 'entity_reference_entity_id',
+ 'entity_reference_label',
+ ];
+
foreach ($this->formatterManager->getOptions($base_plugin_definition['field_type']) as $formatter => $label) {
$this->derivatives[$formatter] = $base_plugin_definition;
$this->derivatives[$formatter]['label'] = $label;
+
+ // The base entity embed display plugin annotation has opted into
+ // `supports_image_alt_and_title`. For some derivatives we know that they
+ // do not support this, so opt them back out.
+ if (in_array($formatter, $no_media_image_decorator, TRUE)) {
+ $this->derivatives[$formatter]['supports_image_alt_and_title'] = FALSE;
+ }
}
return $this->derivatives;
}
diff --git a/src/Plugin/Derivative/ViewModeDeriver.php b/src/Plugin/Derivative/ViewModeDeriver.php
index f23d70e..ff21b55 100644
--- a/src/Plugin/Derivative/ViewModeDeriver.php
+++ b/src/Plugin/Derivative/ViewModeDeriver.php
@@ -64,6 +64,9 @@ class ViewModeDeriver extends DeriverBase implements ContainerDeriverInterface {
$this->derivatives[$definition['id']]['view_mode'] = $view_mode;
$this->derivatives[$definition['id']]['entity_types'] = $definition['targetEntityType'];
$this->derivatives[$definition['id']]['no_ui'] = $mode;
+ if ($definition['targetEntityType'] === 'media') {
+ $this->derivatives[$definition['id']]['supports_image_alt_and_title'] = TRUE;
+ }
}
}
return $this->derivatives;
diff --git a/src/Plugin/entity_embed/EntityEmbedDisplay/EntityReferenceFieldFormatter.php b/src/Plugin/entity_embed/EntityEmbedDisplay/EntityReferenceFieldFormatter.php
index ef5793d..8909dda 100644
--- a/src/Plugin/entity_embed/EntityEmbedDisplay/EntityReferenceFieldFormatter.php
+++ b/src/Plugin/entity_embed/EntityEmbedDisplay/EntityReferenceFieldFormatter.php
@@ -21,7 +21,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
* id = "entity_reference",
* label = @Translation("Entity Reference"),
* deriver = "Drupal\entity_embed\Plugin\Derivative\FieldFormatterDeriver",
- * field_type = "entity_reference"
+ * field_type = "entity_reference",
+ * supports_image_alt_and_title = TRUE
* )
*/
class EntityReferenceFieldFormatter extends FieldFormatterEntityEmbedDisplayBase {
diff --git a/src/Plugin/entity_embed/EntityEmbedDisplay/ImageFieldFormatter.php b/src/Plugin/entity_embed/EntityEmbedDisplay/ImageFieldFormatter.php
index 034e714..c175ded 100644
--- a/src/Plugin/entity_embed/EntityEmbedDisplay/ImageFieldFormatter.php
+++ b/src/Plugin/entity_embed/EntityEmbedDisplay/ImageFieldFormatter.php
@@ -178,7 +178,7 @@ class ImageFieldFormatter extends FileFieldFormatter {
// double quotes in place of empty alt text only if that was filled
// intentionally by the user.
if (!empty($entity_element) && $entity_element['data-entity-embed-display'] == 'image:image') {
- $alt = '""';
+ $alt = MediaImageDecorator::EMPTY_STRING;
}
}
@@ -211,7 +211,7 @@ class ImageFieldFormatter extends FileFieldFormatter {
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
// When the alt attribute is set to two double quotes, transform it to the
// empty string: two double quotes signify "empty alt attribute". See above.
- if (trim($form_state->getValue(['attributes', 'alt'])) === '""') {
+ if (trim($form_state->getValue(['attributes', 'alt'])) === MediaImageDecorator::EMPTY_STRING) {
$form_state->setValue(['attributes', 'alt'], '');
}
}
diff --git a/src/Plugin/entity_embed/EntityEmbedDisplay/MediaImageDecorator.php b/src/Plugin/entity_embed/EntityEmbedDisplay/MediaImageDecorator.php
new file mode 100644
index 0000000..9c9e43f
--- /dev/null
+++ b/src/Plugin/entity_embed/EntityEmbedDisplay/MediaImageDecorator.php
@@ -0,0 +1,269 @@
+decorated = $decorated;
+ }
+
+ /**
+ * Passes through all unknown calls to the decorated object.
+ */
+ public function __call($method, $args) {
+ return call_user_func_array([$this->decorated, $method], $args);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function access(AccountInterface $account = NULL) {
+ return $this->decorated->access($account);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
+ return $this->decorated->validateConfigurationForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function defaultConfiguration() {
+ return $this->decorated->defaultConfiguration();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function calculateDependencies() {
+ return $this->decorated->calculateDependencies();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getConfiguration() {
+ return $this->decorated->getConfiguration();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPluginDefinition() {
+ return $this->decorated->getPluginDefinition();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPluginId() {
+ return $this->decorated->getPluginId();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setConfiguration(array $configuration) {
+ return $this->decorated->setConfiguration($configuration);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+ $form = $this->decorated->buildConfigurationForm($form, $form_state);
+
+ /** @var \Drupal\Core\Entity\EntityInterface $entity */
+ $entity = $this->decorated->getEntityFromContext();
+
+ if ($image_field = $this->getMediaImageSourceField($entity)) {
+
+ $settings = $entity->{$image_field}->getItemDefinition()->getSettings();
+ $attributes = $this->getAttributeValues();
+
+ $alt = isset($attributes['alt']) ? $attributes['alt'] : $entity->{$image_field}->alt;
+ $title = isset($attributes['title']) ? $attributes['title'] : $entity->{$image_field}->title;
+
+ // Setting empty alt to double quotes. See ImageFieldFormatter.
+ if ($settings['alt_field_required'] && $alt === '') {
+ $alt = static::EMPTY_STRING;
+ }
+
+ if (!empty($settings['alt_field'])) {
+ // Add support for editing the alternate and title text attributes.
+ $form['alt'] = [
+ '#type' => 'textfield',
+ '#title' => $this->t('Alternate text'),
+ '#default_value' => $alt,
+ '#description' => $this->t('This text will be used by screen readers, search engines, or when the image cannot be loaded.'),
+ '#required' => $settings['alt_field_required'],
+ '#required_error' => $this->t('Alternative text is required.
(Only in rare cases should this be left empty. To create empty alternative text, enter ""
— two double quotes without any content).'),
+ '#maxlength' => 512,
+ ];
+ }
+
+ if (!empty($settings['title_field'])) {
+ $form['title'] = [
+ '#type' => 'textfield',
+ '#title' => $this->t('Title'),
+ '#default_value' => $title,
+ '#description' => t('The title is used as a tool tip when the user hovers the mouse over the image.'),
+ '#maxlength' => 1024,
+ '#required' => $settings['title_field_required'],
+ ];
+ }
+ }
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+ /** @var \Drupal\Core\Entity\EntityInterface $entity */
+ $entity = $this->decorated->getEntityFromContext();
+ if ($image_field = $this->getMediaImageSourceField($entity)) {
+ $settings = $entity->{$image_field}->getItemDefinition()->getSettings();
+ $values = $form_state->getValue(['attributes', 'data-entity-embed-display-settings']);
+
+ if (!empty($settings['alt_field'])) {
+ // When the alt attribute is set to two double quotes, transform it to
+ // the empty string: two double quotes signify "empty alt attribute".
+ // See ImagefieldFormatter.
+ if (trim($values['alt']) === static::EMPTY_STRING) {
+ $values['alt'] = static::EMPTY_STRING;
+ }
+ // If the alt text is unchanged from the values set on the
+ // field, there's no need for the alt property to be set.
+ elseif ($values['alt'] === $entity->{$image_field}->alt) {
+ $values['alt'] = '';
+ }
+
+ $form_state->setValue(['attributes', 'alt'], $values['alt']);
+ $form_state->unsetValue([
+ 'attributes',
+ 'data-entity-embed-display-settings',
+ 'alt',
+ ]);
+ }
+
+ if (!empty($settings['title_field'])) {
+ if (empty($values['title'])) {
+ $values['title'] = '';
+ }
+ // If the title text is unchanged from the values set on the
+ // field, there's no need for the title property to be set.
+ elseif ($values['title'] === $entity->{$image_field}->title) {
+ $values['title'] = '';
+ }
+
+ $form_state->setValue(['attributes', 'title'], $values['title']);
+ $form_state->unsetValue([
+ 'attributes',
+ 'data-entity-embed-display-settings',
+ 'title',
+ ]);
+ }
+ }
+ $this->decorated->submitConfigurationForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function build() {
+ $build = $this->decorated->build();
+
+ /** @var \Drupal\Core\Entity\EntityInterface $entity */
+ $entity = $this->decorated->getEntityFromContext();
+
+ if ($image_field = $this->getMediaImageSourceField($entity)) {
+ $settings = $entity->{$image_field}->getItemDefinition()->getSettings();
+
+ if (!empty($settings['alt_field']) && $this->hasAttribute('alt')) {
+ $entity->{$image_field}->alt = $this->getAttributeValue('alt');
+ $entity->thumbnail->alt = $this->getAttributeValue('alt');
+ }
+
+ if (!empty($settings['title_field']) && $this->hasAttribute('title')) {
+ $entity->{$image_field}->title = $this->getAttributeValue('title');
+ $entity->thumbnail->title = $this->getAttributeValue('title');
+ }
+ }
+
+ return $build;
+ }
+
+ /**
+ * Get image field from source config.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ * Embedded entity.
+ *
+ * @return string|null
+ * String of image field name.
+ */
+ protected function getMediaImageSourceField(EntityInterface $entity) {
+ if (!$entity instanceof MediaInterface) {
+ return NULL;
+ }
+
+ try {
+ $field_definition = $entity->getSource()
+ ->getSourceFieldDefinition($entity->bundle->entity);
+ $field_name = $field_definition->getName();
+ $field = $entity->get($field_name);
+ $item = $field->first();
+ if (!empty($item) && $item instanceof ImageItem) {
+ // Check that either alt field or title field is enabled.
+ if ($field_definition->getSetting('alt_field') || $field_definition->getSetting('title_field')) {
+ return $field_name;
+ }
+ }
+ }
+ catch (\Exception $e) {
+ return NULL;
+ }
+
+ return NULL;
+ }
+
+}
diff --git a/tests/modules/entity_embed_translation_test/config/install/core.entity_form_display.node.article.default.yml b/tests/modules/entity_embed_test/config/install/core.entity_form_display.node.article.default.yml
similarity index 100%
rename from tests/modules/entity_embed_translation_test/config/install/core.entity_form_display.node.article.default.yml
rename to tests/modules/entity_embed_test/config/install/core.entity_form_display.node.article.default.yml
diff --git a/tests/modules/entity_embed_test/config/install/core.entity_view_display.media.image.embed.yml b/tests/modules/entity_embed_test/config/install/core.entity_view_display.media.image.embed.yml
new file mode 100644
index 0000000..60ae55d
--- /dev/null
+++ b/tests/modules/entity_embed_test/config/install/core.entity_view_display.media.image.embed.yml
@@ -0,0 +1,29 @@
+langcode: en
+status: true
+dependencies:
+ config:
+ - field.field.media.image.field_media_image
+ - image.style.medium
+ - media.type.image
+ module:
+ - image
+ - user
+id: media.image.default
+targetEntityType: media
+bundle: image
+mode: embed
+content:
+ field_media_image:
+ type: image
+ weight: 2
+ region: content
+ label: hidden
+ settings:
+ image_style: medium
+ image_link: ''
+ third_party_settings: { }
+hidden:
+ name: true
+ thumbnail: true
+ created: true
+ uid: true
diff --git a/tests/modules/entity_embed_test/config/install/core.entity_view_display.media.image.thumb.yml b/tests/modules/entity_embed_test/config/install/core.entity_view_display.media.image.thumb.yml
new file mode 100644
index 0000000..4c4338c
--- /dev/null
+++ b/tests/modules/entity_embed_test/config/install/core.entity_view_display.media.image.thumb.yml
@@ -0,0 +1,28 @@
+langcode: en
+status: true
+dependencies:
+ config:
+ - image.style.medium
+ - media.type.image
+ module:
+ - image
+ - user
+id: media.image.default
+targetEntityType: media
+bundle: image
+mode: thumb
+content:
+ thumbnail:
+ type: image
+ weight: 2
+ region: content
+ label: hidden
+ settings:
+ image_style: medium
+ image_link: ''
+ third_party_settings: { }
+hidden:
+ name: true
+ field_media_image: true
+ created: true
+ uid: true
diff --git a/tests/modules/entity_embed_translation_test/config/install/core.entity_view_display.node.article.default.yml b/tests/modules/entity_embed_test/config/install/core.entity_view_display.node.article.default.yml
similarity index 100%
rename from tests/modules/entity_embed_translation_test/config/install/core.entity_view_display.node.article.default.yml
rename to tests/modules/entity_embed_test/config/install/core.entity_view_display.node.article.default.yml
diff --git a/tests/modules/entity_embed_test/config/install/core.entity_view_mode.media.embed.yml b/tests/modules/entity_embed_test/config/install/core.entity_view_mode.media.embed.yml
new file mode 100644
index 0000000..6a87a05
--- /dev/null
+++ b/tests/modules/entity_embed_test/config/install/core.entity_view_mode.media.embed.yml
@@ -0,0 +1,9 @@
+langcode: en
+status: false
+dependencies:
+ module:
+ - media
+id: media.embed
+label: 'Embed'
+targetEntityType: media
+cache: true
diff --git a/tests/modules/entity_embed_test/config/install/core.entity_view_mode.media.thumb.yml b/tests/modules/entity_embed_test/config/install/core.entity_view_mode.media.thumb.yml
new file mode 100644
index 0000000..54f487c
--- /dev/null
+++ b/tests/modules/entity_embed_test/config/install/core.entity_view_mode.media.thumb.yml
@@ -0,0 +1,9 @@
+langcode: en
+status: false
+dependencies:
+ module:
+ - media
+id: media.thumb
+label: 'Thumb (View Mode)'
+targetEntityType: media
+cache: true
diff --git a/tests/modules/entity_embed_translation_test/config/install/editor.editor.full_html.yml b/tests/modules/entity_embed_test/config/install/editor.editor.full_html.yml
similarity index 100%
rename from tests/modules/entity_embed_translation_test/config/install/editor.editor.full_html.yml
rename to tests/modules/entity_embed_test/config/install/editor.editor.full_html.yml
diff --git a/tests/modules/entity_embed_translation_test/config/install/embed.button.test_media_entity_embed.yml b/tests/modules/entity_embed_test/config/install/embed.button.test_media_entity_embed.yml
similarity index 100%
rename from tests/modules/entity_embed_translation_test/config/install/embed.button.test_media_entity_embed.yml
rename to tests/modules/entity_embed_test/config/install/embed.button.test_media_entity_embed.yml
diff --git a/tests/modules/entity_embed_translation_test/config/install/embed.button.test_node.yml b/tests/modules/entity_embed_test/config/install/embed.button.test_node.yml
similarity index 100%
rename from tests/modules/entity_embed_translation_test/config/install/embed.button.test_node.yml
rename to tests/modules/entity_embed_test/config/install/embed.button.test_node.yml
diff --git a/tests/modules/entity_embed_translation_test/config/install/field.field.media.image.field_media_image.yml b/tests/modules/entity_embed_test/config/install/field.field.media.image.field_media_image.yml
similarity index 100%
rename from tests/modules/entity_embed_translation_test/config/install/field.field.media.image.field_media_image.yml
rename to tests/modules/entity_embed_test/config/install/field.field.media.image.field_media_image.yml
diff --git a/tests/modules/entity_embed_translation_test/config/install/field.field.node.article.body.yml b/tests/modules/entity_embed_test/config/install/field.field.node.article.body.yml
similarity index 100%
rename from tests/modules/entity_embed_translation_test/config/install/field.field.node.article.body.yml
rename to tests/modules/entity_embed_test/config/install/field.field.node.article.body.yml
diff --git a/tests/modules/entity_embed_translation_test/config/install/field.storage.media.field_media_image.yml b/tests/modules/entity_embed_test/config/install/field.storage.media.field_media_image.yml
similarity index 100%
rename from tests/modules/entity_embed_translation_test/config/install/field.storage.media.field_media_image.yml
rename to tests/modules/entity_embed_test/config/install/field.storage.media.field_media_image.yml
diff --git a/tests/modules/entity_embed_translation_test/config/install/filter.format.full_html.yml b/tests/modules/entity_embed_test/config/install/filter.format.full_html.yml
similarity index 100%
rename from tests/modules/entity_embed_translation_test/config/install/filter.format.full_html.yml
rename to tests/modules/entity_embed_test/config/install/filter.format.full_html.yml
diff --git a/tests/modules/entity_embed_translation_test/config/install/media.type.image.yml b/tests/modules/entity_embed_test/config/install/media.type.image.yml
similarity index 100%
rename from tests/modules/entity_embed_translation_test/config/install/media.type.image.yml
rename to tests/modules/entity_embed_test/config/install/media.type.image.yml
diff --git a/tests/modules/entity_embed_translation_test/config/install/node.type.article.yml b/tests/modules/entity_embed_test/config/install/node.type.article.yml
similarity index 100%
rename from tests/modules/entity_embed_translation_test/config/install/node.type.article.yml
rename to tests/modules/entity_embed_test/config/install/node.type.article.yml
diff --git a/tests/modules/entity_embed_test/entity_embed_test.info.yml b/tests/modules/entity_embed_test/entity_embed_test.info.yml
index 45e7865..9b44c30 100644
--- a/tests/modules/entity_embed_test/entity_embed_test.info.yml
+++ b/tests/modules/entity_embed_test/entity_embed_test.info.yml
@@ -3,3 +3,13 @@ type: module
description: 'Support module for the Entity Embed module tests.'
core: 8.x
package: Testing
+dependencies:
+ - drupal:file
+ - drupal:image
+ - drupal:node
+ - drupal:text
+ - drupal:media
+ - drupal:ckeditor
+ - drupal:editor
+ - embed:embed
+ - entity_embed:entity_embed
diff --git a/tests/src/FunctionalJavascript/EntityEmbedTestBase.php b/tests/src/FunctionalJavascript/EntityEmbedTestBase.php
index 2c23dbf..113e7a6 100644
--- a/tests/src/FunctionalJavascript/EntityEmbedTestBase.php
+++ b/tests/src/FunctionalJavascript/EntityEmbedTestBase.php
@@ -2,6 +2,7 @@
namespace Drupal\Tests\entity_embed\FunctionalJavascript;
+use Drupal\Component\Utility\Html;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
/**
@@ -57,4 +58,23 @@ JS;
$this->getSession()->wait($timeout, $condition);
}
+ /**
+ * Creates an embed code with given attributes.
+ *
+ * @param array $attributes
+ * The attributes to add.
+ *
+ * @return string
+ * A string containing a element with the given attributes.
+ */
+ protected function createEmbedCode(array $attributes) {
+ $dom = Html::load('This placeholder should not be rendered.');
+ $xpath = new \DOMXPath($dom);
+ $drupal_entity = $xpath->query('//drupal-entity')[0];
+ foreach ($attributes as $attribute => $value) {
+ $drupal_entity->setAttribute($attribute, $value);
+ }
+ return Html::serialize($dom);
+ }
+
}
diff --git a/tests/src/FunctionalJavascript/MediaImageTest.php b/tests/src/FunctionalJavascript/MediaImageTest.php
new file mode 100644
index 0000000..f6df168
--- /dev/null
+++ b/tests/src/FunctionalJavascript/MediaImageTest.php
@@ -0,0 +1,516 @@
+adminUser = $this->drupalCreateUser([
+ 'use text format full_html',
+ 'administer nodes',
+ 'edit any article content',
+ ]);
+ }
+
+ /**
+ * Tests alt and title overriding for embedded images.
+ */
+ public function testAltAndTitle() {
+ \Drupal::service('file_system')->copy($this->root . '/core/misc/druplicon.png', 'public://example.jpg');
+ /** @var \Drupal\file\FileInterface $file */
+ $file = File::create([
+ 'uri' => 'public://example.jpg',
+ 'uid' => $this->adminUser->id(),
+ ]);
+ $file->save();
+
+ $this->createNode([
+ 'type' => 'article',
+ 'title' => 'Red-lipped batfish',
+ ]);
+
+ $media = Media::create([
+ 'bundle' => 'image',
+ 'name' => 'Screaming hairy armadillo',
+ 'field_media_image' => [
+ [
+ 'target_id' => $file->id(),
+ 'alt' => 'default alt',
+ 'title' => 'default title',
+ ],
+ ],
+ ]);
+ $media->save();
+
+ $host = $this->createNode([
+ 'type' => 'article',
+ 'title' => 'Animals with strange names',
+ 'body' => [
+ 'value' => '',
+ 'format' => 'full_html',
+ ],
+ ]);
+
+ $this->drupalLogin($this->adminUser);
+ $this->drupalGet('node/' . $host->id() . '/edit');
+ $this->waitForEditor();
+
+ $this->assignNameToCkeditorIframe();
+
+ $this->assertSession()
+ ->waitForElementVisible('css', 'a.cke_button__test_node')
+ ->click();
+ $this->assertSession()->waitForId('drupal-modal');
+
+ // Test that node embed doesn't display alt and title fields.
+ $this->assertSession()
+ ->fieldExists('entity_id')
+ ->setValue('Red-lipped batfish (1)');
+ $this->assertSession()->elementExists('css', 'button.js-button-next')->click();
+ $form = $this->assertSession()->waitForElementVisible('css', 'form.entity-embed-dialog-step--embed');
+
+ // Assert that the review step displays the selected entity with the label.
+ $text = $form->getText();
+ $this->assertContains('Red-lipped batfish', $text);
+
+ $select = $this->assertSession()
+ ->selectExists('attributes[data-entity-embed-display]');
+
+ $select->setValue('view_mode:node.full');
+ $this->assertSession()->assertWaitOnAjaxRequest();
+
+ // The view_mode:node.full display shouldn't have alt and title fields.
+ $this->assertSession()
+ ->fieldNotExists('attributes[data-entity-embed-display-settings][alt]');
+ $this->assertSession()
+ ->fieldNotExists('attributes[data-entity-embed-display-settings][title]');
+
+ $select = $this->assertSession()
+ ->selectExists('attributes[data-entity-embed-display]');
+
+ $select->setValue('view_mode:node.teaser');
+ $this->assertSession()->assertWaitOnAjaxRequest();
+
+ // The view_mode:node.teaser display shouldn't have alt and title fields.
+ $this->assertSession()
+ ->fieldNotExists('attributes[data-entity-embed-display-settings][alt]');
+ $this->assertSession()
+ ->fieldNotExists('attributes[data-entity-embed-display-settings][title]');
+
+ // Close the dialog.
+ $this->assertSession()->elementExists('css', '.ui-dialog-titlebar-close')->press();
+
+ // Now test with media.
+ $this->assertSession()
+ ->waitForElementVisible('css', 'a.cke_button__test_media_entity_embed')
+ ->click();
+ $this->assertSession()->waitForId('drupal-modal');
+
+ $this->assertSession()
+ ->fieldExists('entity_id')
+ ->setValue('Screaming hairy armadillo (1)');
+ $this->assertSession()->elementExists('css', 'button.js-button-next')->click();
+ $form = $this->assertSession()->waitForElementVisible('css', 'form.entity-embed-dialog-step--embed');
+
+ // Assert that the review step displays the selected entity with the label.
+ $text = $form->getText();
+ $this->assertContains('Screaming hairy armadillo', $text);
+
+ $select = $this->assertSession()
+ ->selectExists('attributes[data-entity-embed-display]');
+
+ $select->setValue('entity_reference:entity_reference_entity_id');
+ $this->assertSession()->assertWaitOnAjaxRequest();
+
+ // The entity_reference:entity_reference_entity_id display shouldn't have
+ // alt and title fields.
+ $this->assertSession()
+ ->fieldNotExists('attributes[data-entity-embed-display-settings][alt]');
+ $this->assertSession()
+ ->fieldNotExists('attributes[data-entity-embed-display-settings][title]');
+
+ $select->setValue('entity_reference:entity_reference_label');
+ $this->assertSession()->assertWaitOnAjaxRequest();
+
+ // The entity_reference:entity_reference_label display shouldn't have alt
+ // and title fields.
+ $this->assertSession()
+ ->fieldNotExists('attributes[data-entity-embed-display-settings][alt]');
+ $this->assertSession()
+ ->fieldNotExists('attributes[data-entity-embed-display-settings][title]');
+
+ // Test the entity embed display that ships with core media.
+ $select->setValue('entity_reference:media_thumbnail');
+ $this->assertSession()->assertWaitOnAjaxRequest();
+
+ $this->assertSession()
+ ->selectExists('attributes[data-entity-embed-display]')
+ ->setValue('view_mode:media.embed');
+ $this->assertSession()->assertWaitOnAjaxRequest();
+ $alt = $this->assertSession()
+ ->fieldExists('attributes[data-entity-embed-display-settings][alt]');
+ $this->assertEquals($media->field_media_image->alt, $alt->getValue());
+ $title = $this->assertSession()
+ ->fieldExists('attributes[data-entity-embed-display-settings][title]');
+ $this->assertEquals($media->field_media_image->title, $title->getValue());
+
+ $this->submitDialog();
+
+ $img = $this->assertSession()->elementExists('css', 'img');
+ $this->assertEquals("default alt", $img->getAttribute('alt'));
+ $this->assertEquals("default title", $img->getAttribute('title'));
+
+ $this->reopenDialog();
+
+ $this->assertSession()
+ ->fieldExists('attributes[data-entity-embed-display-settings][alt]')
+ ->setValue('Satanic leaf-tailed gecko alt');
+ $this->assertSession()
+ ->fieldExists('attributes[data-entity-embed-display-settings][title]')
+ ->setValue('Satanic leaf-tailed gecko title');
+
+ $this->submitDialog();
+
+ $img = $this->assertSession()->elementExists('css', 'img');
+ $this->assertEquals("Satanic leaf-tailed gecko alt", $img->getAttribute('alt'));
+ $this->assertEquals("Satanic leaf-tailed gecko title", $img->getAttribute('title'));
+
+ $this->reopenDialog();
+
+ // Test a view mode that displays thumbnail field.
+ $select->setValue('view_mode:media.thumb');
+ $this->assertSession()->assertWaitOnAjaxRequest();
+
+ $this->assertSession()
+ ->selectExists('attributes[data-entity-embed-display]')
+ ->setValue('view_mode:media.embed');
+ $this->assertSession()->assertWaitOnAjaxRequest();
+ $alt = $this->assertSession()
+ ->fieldExists('attributes[data-entity-embed-display-settings][alt]');
+ $this->assertEquals('Satanic leaf-tailed gecko alt', $alt->getValue());
+ $title = $this->assertSession()
+ ->fieldExists('attributes[data-entity-embed-display-settings][title]');
+ $this->assertEquals('Satanic leaf-tailed gecko title', $title->getValue());
+
+ $this->submitDialog();
+
+ $img = $this->assertSession()->elementExists('css', 'img');
+ $this->assertEquals('Satanic leaf-tailed gecko alt', $img->getAttribute('alt'));
+ $this->assertEquals('Satanic leaf-tailed gecko title', $img->getAttribute('title'));
+
+ $this->reopenDialog();
+
+ $this->assertSession()
+ ->fieldExists('attributes[data-entity-embed-display-settings][alt]')
+ ->setValue('Goblin shark alt');
+ $this->assertSession()
+ ->fieldExists('attributes[data-entity-embed-display-settings][title]')
+ ->setValue('Goblin shark title');
+
+ $this->submitDialog();
+
+ $img = $this->assertSession()->elementExists('css', 'img');
+ $this->assertEquals("Goblin shark alt", $img->getAttribute('alt'));
+ $this->assertEquals("Goblin shark title", $img->getAttribute('title'));
+
+ $this->reopenDialog();
+
+ // Test a view mode that displays the media's image field.
+ $select->setValue('view_mode:media.embed');
+ $this->assertSession()->assertWaitOnAjaxRequest();
+
+ // Test that the view_mode:media.embed display has alt and title fields,
+ // and that the default values match the values on the media's
+ // source image field.
+ $this->assertSession()
+ ->selectExists('attributes[data-entity-embed-display]')
+ ->setValue('view_mode:media.embed');
+ $this->assertSession()->assertWaitOnAjaxRequest();
+ $alt = $this->assertSession()
+ ->fieldExists('attributes[data-entity-embed-display-settings][alt]');
+ $this->assertEquals("Goblin shark alt", $alt->getValue());
+ $title = $this->assertSession()
+ ->fieldExists('attributes[data-entity-embed-display-settings][title]');
+ $this->assertEquals("Goblin shark title", $title->getValue());
+
+ $this->submitDialog();
+
+ $img = $this->assertSession()->elementExists('css', 'img');
+ $this->assertEquals("Goblin shark alt", $img->getAttribute('alt'));
+ $this->assertEquals("Goblin shark title", $img->getAttribute('title'));
+
+ $this->reopenDialog();
+
+ $this->assertSession()
+ ->fieldExists('attributes[data-entity-embed-display-settings][alt]')
+ ->setValue('Satanic leaf-tailed gecko alt');
+ $this->assertSession()
+ ->fieldExists('attributes[data-entity-embed-display-settings][title]')
+ ->setValue('Satanic leaf-tailed gecko title');
+
+ $this->submitDialog();
+
+ $img = $this->assertSession()->elementExists('css', 'img');
+ $this->assertEquals('Satanic leaf-tailed gecko alt', $img->getAttribute('alt'));
+ $this->assertEquals('Satanic leaf-tailed gecko title', $img->getAttribute('title'));
+
+ $this->config('field.field.media.image.field_media_image')
+ ->set('settings.alt_field', FALSE)
+ ->set('settings.title_field', FALSE)
+ ->save();
+
+ $field = FieldConfig::load('media.image.field_media_image');
+ $settings = $field->getSettings();
+ $settings['alt_field'] = FALSE;
+ $settings['title_field'] = FALSE;
+ $field->set('settings', $settings);
+ $field->save();
+
+ drupal_flush_all_caches();
+
+ $this->reopenDialog();
+
+ $this->assertSession()
+ ->fieldNotExists('attributes[data-entity-embed-display-settings][alt]');
+ $this->assertSession()
+ ->fieldNotExists('attributes[data-entity-embed-display-settings][title]');
+
+ $this->submitDialog();
+
+ $img = $this->assertSession()->elementExists('css', 'img');
+ $this->assertEquals('default alt', $img->getAttribute('alt'));
+ $this->assertEquals('default title', $img->getAttribute('title'));
+
+ $field = FieldConfig::load('media.image.field_media_image');
+ $settings = $field->getSettings();
+ $settings['alt_field'] = TRUE;
+ $field->set('settings', $settings);
+ $field->save();
+
+ drupal_flush_all_caches();
+
+ $this->reopenDialog();
+
+ // Test that when only the alt field is enabled, only alt field should
+ // display.
+ $this->assertSession()
+ ->fieldExists('attributes[data-entity-embed-display-settings][alt]')->setValue('Satanic leaf-tailed gecko alt');
+ $this->assertSession()
+ ->fieldNotExists('attributes[data-entity-embed-display-settings][title]');
+
+ $this->submitDialog();
+
+ $img = $this->assertSession()->elementExists('css', 'img');
+ $this->assertEquals('Satanic leaf-tailed gecko alt', $img->getAttribute('alt'));
+ $this->assertEquals('default title', $img->getAttribute('title'));
+
+ $field = FieldConfig::load('media.image.field_media_image');
+ $settings = $field->getSettings();
+ $settings['alt_field'] = FALSE;
+ $settings['title_field'] = TRUE;
+ $field->set('settings', $settings);
+ $field->save();
+
+ drupal_flush_all_caches();
+
+ $this->reopenDialog();
+
+ // With only title field enabled, only title field should display.
+ $this->assertSession()
+ ->fieldExists('attributes[data-entity-embed-display-settings][title]')->setValue('Satanic leaf-tailed gecko title');
+ $this->assertSession()
+ ->fieldNotExists('attributes[data-entity-embed-display-settings][alt]');
+
+ $this->submitDialog();
+
+ $img = $this->assertSession()->elementExists('css', 'img');
+ $this->assertEquals('Satanic leaf-tailed gecko title', $img->getAttribute('title'));
+ $this->assertEquals('default alt', $img->getAttribute('alt'));
+
+ $field = FieldConfig::load('media.image.field_media_image');
+ $settings = $field->getSettings();
+ $settings['alt_field'] = TRUE;
+ $settings['title_field'] = TRUE;
+ $settings['alt_field_required'] = FALSE;
+ $settings['title_field_required'] = TRUE;
+ $field->set('settings', $settings);
+ $field->save();
+
+ drupal_flush_all_caches();
+
+ $this->reopenDialog();
+
+ $alt = $this->assertSession()
+ ->fieldExists('attributes[data-entity-embed-display-settings][alt]');
+ $this->assertFalse($alt->hasAttribute('required'));
+ $title = $this->assertSession()
+ ->fieldExists('attributes[data-entity-embed-display-settings][title]');
+ $this->assertTrue($title->hasAttribute('required'));
+
+ $this->submitDialog();
+
+ $field = FieldConfig::load('media.image.field_media_image');
+ $settings = $field->getSettings();
+ $settings['alt_field_required'] = TRUE;
+ $settings['title_field_required'] = FALSE;
+ $field->set('settings', $settings);
+ $field->save();
+
+ drupal_flush_all_caches();
+
+ $this->reopenDialog();
+
+ $alt = $this->assertSession()
+ ->fieldExists('attributes[data-entity-embed-display-settings][alt]');
+ $this->assertTrue($alt->hasAttribute('required'));
+ $title = $this->assertSession()
+ ->fieldExists('attributes[data-entity-embed-display-settings][title]');
+ $this->assertFalse($title->hasAttribute('required'));
+
+ // Test that setting value to double quote will allow setting the alt
+ // and title to empty.
+ $alt->setValue(MediaImageDecorator::EMPTY_STRING);
+ $title->setValue(MediaImageDecorator::EMPTY_STRING);
+
+ $this->submitDialog();
+
+ $img = $this->assertSession()->elementExists('css', 'img');
+ $this->assertEmpty($img->getAttribute('alt'));
+ $this->assertEmpty($img->getAttribute('title'));
+
+ // Test the same embed with different alt and title text.
+ $input = $this->createEmbedCode([
+ 'alt' => 'alt 1',
+ 'title' => 'title 1',
+ 'data-embed-button' => 'test_media_entity_embed',
+ 'data-entity-embed-display' => 'view_mode:media.embed',
+ 'data-entity-embed-display-settings' => '',
+ 'data-entity-type' => 'media',
+ 'data-entity-uuid' => $media->uuid(),
+ 'data-langcode' => 'en',
+ ]);
+ $input .= $this->createEmbedCode([
+ 'alt' => 'alt 2',
+ 'title' => 'title 2',
+ 'data-embed-button' => 'test_media_entity_embed',
+ 'data-entity-embed-display' => 'view_mode:media.embed',
+ 'data-entity-embed-display-settings' => '',
+ 'data-entity-type' => 'media',
+ 'data-entity-uuid' => $media->uuid(),
+ 'data-langcode' => 'en',
+ ]);
+ $input .= $this->createEmbedCode([
+ 'alt' => 'alt 3',
+ 'title' => 'title 3',
+ 'data-embed-button' => 'test_media_entity_embed',
+ 'data-entity-embed-display' => 'view_mode:media.embed',
+ 'data-entity-embed-display-settings' => '',
+ 'data-entity-type' => 'media',
+ 'data-entity-uuid' => $media->uuid(),
+ 'data-langcode' => 'en',
+ ]);
+
+ $this->getSession()->switchToIFrame();
+
+ $this->assertSession()
+ ->waitForElementVisible('css', 'a.cke_button__source')
+ ->click();
+
+ $source = $this->assertSession()
+ ->waitForElementVisible('xpath', "//textarea[contains(@class, 'cke_source')]");
+ $source->setValue($input);
+
+ // Exit "source" mode.
+ $this->assertSession()
+ ->waitForElementVisible('css', 'a.cke_button__source')
+ ->click();
+
+ $this->assertSession()->assertWaitOnAjaxRequest();
+ $this->assignNameToCkeditorIframe();
+ $this->getSession()->switchToIFrame('ckeditor');
+
+ $img = $this->assertSession()->waitForElement('xpath', "//img[contains(@alt, 'alt 1')]");
+ $this->assertEquals('alt 1', $img->getAttribute('alt'));
+ $this->assertEquals('title 1', $img->getAttribute('title'));
+
+ $img = $this->assertSession()->elementExists('xpath', "//img[contains(@alt, 'alt 2')]");
+ $this->assertEquals('alt 2', $img->getAttribute('alt'));
+ $this->assertEquals('title 2', $img->getAttribute('title'));
+
+ $img = $this->assertSession()->elementExists('xpath', "//img[contains(@alt, 'alt 3')]");
+ $this->assertEquals('alt 3', $img->getAttribute('alt'));
+ $this->assertEquals('title 3', $img->getAttribute('title'));
+
+ // Save the host entity.
+ $this->getSession()->switchToIFrame();
+ $this->assertSession()->buttonExists('Save')->press();
+
+ $img = $this->assertSession()->waitForElement('xpath', "//img[contains(@alt, 'alt 1')]");
+ $this->assertEquals('alt 1', $img->getAttribute('alt'));
+ $this->assertEquals('title 1', $img->getAttribute('title'));
+
+ $img = $this->assertSession()->elementExists('xpath', "//img[contains(@alt, 'alt 2')]");
+ $this->assertEquals('alt 2', $img->getAttribute('alt'));
+ $this->assertEquals('title 2', $img->getAttribute('title'));
+
+ $img = $this->assertSession()->elementExists('xpath', "//img[contains(@alt, 'alt 3')]");
+ $this->assertEquals('alt 3', $img->getAttribute('alt'));
+ $this->assertEquals('title 3', $img->getAttribute('title'));
+ }
+
+ /**
+ * Helper function to reopen EntityEmbedDialog for first embed.
+ */
+ protected function reopenDialog() {
+ $this->getSession()->switchToIFrame();
+ $select_and_edit_embed = <<getSession()->executeScript($select_and_edit_embed);
+ $this->assertSession()->assertWaitOnAjaxRequest();
+ $this->assertSession()->waitForElementVisible('css', 'form.entity-embed-dialog-step--embed');
+ }
+
+ /**
+ * Helper function to submit dialog and focus on ckeditor frame.
+ */
+ protected function submitDialog() {
+ $this->assertSession()->elementExists('css', 'button.button--primary')->press();
+ $this->assertSession()->assertWaitOnAjaxRequest();
+
+ // Verify that the embedded entity preview in CKEditor displays the image
+ // with the default alt and title.
+ $this->getSession()->switchToIFrame('ckeditor');
+ }
+
+}