Commit 63ce40cc authored by Dave Reid's avatar Dave Reid Committed by slashrsm

Issue #2706051 by slashrsm, Dave Reid, Wim Leers, Denchev: Require UUIDs for...

Issue #2706051 by slashrsm, Dave Reid, Wim Leers, Denchev: Require UUIDs for embedding with the dialog (remove data-entity-id from embeds)
parent 08e50989
# Entity Embed Module
[![Travis build status](https://img.shields.io/travis/drupal-media/entity_embed/8.x-1.x.svg)](https://travis-ci.org/drupal-media/entity_embed) [![Scrutinizer code quality](https://img.shields.io/scrutinizer/g/drupal-media/entity_embed/8.x-1.x.svg)](https://scrutinizer-ci.com/g/drupal-media/entity_embed)
[![Travis build status](https://img.shields.io/travis/drupal-media/entity_embed/8.x-1.x.svg)](https://travis-ci.org/drupal-media/entity_embed)
[![Scrutinizer code quality](https://img.shields.io/scrutinizer/g/drupal-media/entity_embed/8.x-1.x.svg)](https://scrutinizer-ci.com/g/drupal-media/entity_embed)
[Entity Embed](https://www.drupal.org/project/entity_embed) module allows any entity to be embedded using a text editor.
[Entity Embed](https://www.drupal.org/project/entity_embed) module
allows any entity to be embedded using a text editor.
## Requirements
......@@ -11,27 +13,40 @@
## Installation
Entity Embed can be installed via the [standard Drupal installation process](http://drupal.org/node/895232).
Entity Embed can be installed via the
[standard Drupal installation process](http://drupal.org/node/895232).
## Configuration
* Install and enable [Embed](https://www.drupal.org/project/embed) module.
* Install and enable [Entity Embed](https://www.drupal.org/project/entity_embed) module.
* Go to the 'Text formats and editors' configuration page: `/admin/config/content/formats`, and for each text format/editor combo where you want to embed entities, do the following:
* Install and enable [Entity Embed](https://www.drupal.org/project/entity_embed)
module.
* Go to the 'Text formats and editors' configuration page: `/admin/config/content/formats`,
and for each text format/editor combo where you want to embed entities,
do the following:
* Enable the 'Display embedded entities' filter.
* Drag and drop the 'E' button into the Active toolbar.
* If the text format uses the 'Limit allowed HTML tags and correct faulty HTML' filter, ensure the necessary tags and attributes are whitelisted: add ```<drupal-entity data-entity-type data-entity-uuid data-entity-id data-view-mode data-entity-embed-display data-entity-embed-settings data-align data-caption data-embed-button>``` to the 'Allowed HTML tags' setting. (Will happen automatically after https://www.drupal.org/node/2554687.)
* If the text format uses the 'Limit allowed HTML tags and correct
faulty HTML' filter, ensure the necessary tags and attributes were
automatically whitelisted:
```<drupal-entity data-entity-type data-entity-uuid data-view-mode data-entity-embed-display data-entity-embed-settings data-align data-caption data-embed-button>```
appears in the 'Allowed HTML tags' setting.
*Warning: If you were using the module in very early pre-alpha
stages you might need to add `data-entity-id` to the list of allowed
attributes.*
## Usage
* For example, create a new *Article* content.
* Click on the 'E' button in the text editor.
* Enter part of the title of the entity you're looking for and select one of the search results.
* Enter part of the title of the entity you're looking for and select
one of the search results.
* For **Display as**, choose one of the following options:
* Rendered Entity
* Entity ID
* Label
* If chosen **Rendered Entity**, choose one of the following options for **View mode**:
* If chosen **Rendered Entity**, choose one of the following options for
**View mode**:
* Default
* Full content
* RSS
......@@ -41,27 +56,37 @@ Entity Embed can be installed via the [standard Drupal installation process](htt
## Embedding entities without WYSIWYG
Users should be embedding entities using the CKEditor WYSIWYG button as described above. This section is more technical about the HTML markup that is used to embed the actual entity.
Users should be embedding entities using the CKEditor WYSIWYG button as
described above. This section is more technical about the HTML markup
that is used to embed the actual entity.
### Embed by UUID (recommended):
### Example:
```html
<drupal-entity data-entity-type="node" data-entity-uuid="07bf3a2e-1941-4a44-9b02-2d1d7a41ec0e" data-entity-embed-display="entity_reference:entity_reference_entity_view" data-entity-embed-settings='{"view_mode":"teaser"}' />
```
### Embed by ID (not recommended):
```html
<drupal-entity data-entity-type="node" data-entity-id="1" data-entity-embed-display="entity_reference:entity_reference_entity_view" data-entity-embed-settings='{"view_mode":"teaser"}' />
```
### Entity Embed Display Plugins
Embedding entities uses an Entity Embed Display plugin, provided in the `data-entity-embed-display` attribute. By default we provide four different Entity Embed Display plugins out of the box:
- entity_reference:_formatter_id_: Renders the entity using a specific Entity Reference field formatter.
- entity_reference:_entity_reference_label_: Renders the entity using the "Label" formatter.
- file:_formatter_id_: Renders the entity using a specific File field formatter. This will only work if the entity is a file entity type.
- image:_formatter_id_: Renders the entity using a specific Image field formatter. This will only work if the entity is a file entity type, and the file is an image.
Configuration for the Entity Embed Display plugin can be provided by using a `data-entity-embed-settings` attribute, which contains a JSON-encoded array value. Note that care must be used to use single quotes around the attribute value since JSON-encoded arrays typically contain double quotes.
The above examples render the entity using the _entity_reference_entity_view_ formatter from the Entity Reference module, using the _teaser_ view mode.
## Entity Embed Display Plugins
Embedding entities uses an Entity Embed Display plugin, provided in the
`data-entity-embed-display` attribute. By default we provide four
different Entity Embed Display plugins out of the box:
- entity_reference:_formatter_id_: Renders the entity using a specific
Entity Reference field formatter.
- entity_reference:_entity_reference_label_: Renders the entity using
the "Label" formatter.
- file:_formatter_id_: Renders the entity using a specific File field
formatter. This will only work if the entity is a file entity type.
- image:_formatter_id_: Renders the entity using a specific Image field
formatter. This will only work if the entity is a file entity type,
and the file is an image.
Configuration for the Entity Embed Display plugin can be provided by
using a `data-entity-embed-settings` attribute, which contains a
JSON-encoded array value. Note that care must be used to use single
quotes around the attribute value since JSON-encoded arrays typically
contain double quotes.
The above examples render the entity using the
_entity_reference_entity_view_ formatter from the Entity Reference
module, using the _teaser_ view mode.
......@@ -27,8 +27,8 @@
// Generic command for adding/editing entities of all types.
editor.addCommand('editdrupalentity', {
allowedContent: 'drupal-entity[data-embed-button,data-entity-type,data-entity-id,data-entity-uuid,data-entity-embed-display,data-entity-embed-settings,data-align,data-caption]',
requiredContent: 'drupal-entity[data-embed-button,data-entity-type,data-entity-id,data-entity-uuid,data-entity-embed-display,data-entity-embed-settings,data-align,data-caption]',
allowedContent: 'drupal-entity[data-embed-button,data-entity-type,data-entity-uuid,data-entity-embed-display,data-entity-embed-settings,data-align,data-caption]',
requiredContent: 'drupal-entity[data-embed-button,data-entity-type,data-entity-uuid,data-entity-embed-display,data-entity-embed-settings,data-align,data-caption]',
modes: { wysiwyg : 1 },
canUndo: true,
exec: function (editor, data) {
......@@ -82,8 +82,8 @@
// Register the entity embed widget.
editor.widgets.add('drupalentity', {
// Minimum HTML which is required by this widget to work.
allowedContent: 'drupal-entity[data-entity-type,data-entity-id,data-entity-uuid,data-entity-embed-display,data-entity-embed-settings,data-align,data-caption]',
requiredContent: 'drupal-entity[data-entity-type,data-entity-id,data-entity-uuid,data-entity-embed-display,data-entity-embed-settings,data-align,data-caption]',
allowedContent: 'drupal-entity[data-entity-type,data-entity-uuid,data-entity-embed-display,data-entity-embed-settings,data-align,data-caption]',
requiredContent: 'drupal-entity[data-entity-type,data-entity-uuid,data-entity-embed-display,data-entity-embed-settings,data-align,data-caption]',
// Simply recognize the element as our own. The inner markup if fetched
// and inserted the init() callback, since it requires the actual DOM
......@@ -135,7 +135,7 @@
editor.ui.addButton(button.id, {
label: button.label,
data: button,
allowedContent: 'drupal-entity[!data-entity-type,!data-entity-id,!data-entity-uuid,!data-entity-embed-display,!data-entity-embed-settings,!data-align,!data-caption,!data-embed-button]',
allowedContent: 'drupal-entity[!data-entity-type,!data-entity-uuid,!data-entity-embed-display,!data-entity-embed-settings,!data-align,!data-caption,!data-embed-button]',
click: function(editor) {
editor.execCommand('editdrupalentity', this.data);
},
......
......@@ -171,8 +171,8 @@ trait EntityHelperTrait {
// Merge in default attributes.
$context += array(
'data-entity-id' => $entity->id(),
'data-entity-type' => $entity->getEntityTypeId(),
'data-entity-uuid' => $entity->uuid(),
'data-entity-embed-display' => 'entity_reference:entity_reference_entity_view',
'data-entity-embed-settings' => array(),
);
......
......@@ -133,12 +133,11 @@ class EntityEmbedDialog extends FormBase {
$entity_element += array(
'data-entity-type' => $embed_button->getTypeSetting('entity_type'),
'data-entity-uuid' => '',
'data-entity-id' => '',
'data-entity-embed-display' => 'entity_reference:entity_reference_entity_view',
'data-entity-embed-settings' => array(),
);
$form_state->set('entity_element', $entity_element);
$form_state->set('entity', $this->loadEntity($entity_element['data-entity-type'], $entity_element['data-entity-uuid'] ?: $entity_element['data-entity-id']));
$form_state->set('entity', $this->loadEntity($entity_element['data-entity-type'], $entity_element['data-entity-uuid']));
if (!$form_state->get('step')) {
// If an entity has been selected, then always skip to the embed options.
......@@ -184,6 +183,7 @@ class EntityEmbedDialog extends FormBase {
* The form structure.
*/
public function buildSelectStep(array &$form, FormStateInterface $form_state) {
// Entity element is calculated on every AJAX request/submit. See ::buildForm().
$entity_element = $form_state->get('entity_element');
/** @var \Drupal\embed\EmbedButtonInterface $embed_button */
$embed_button = $form_state->get('embed_button');
......@@ -210,9 +210,9 @@ class EntityEmbedDialog extends FormBase {
if ($this->entityBrowser) {
$this->eventDispatcher->addListener(Events::REGISTER_JS_CALLBACKS, [$this, 'registerJSCallback']);
$form['attributes']['entity_browser']['#theme_wrappers'] = ['container'];
$form['attributes']['entity_browser']['browser'] = $this->entityBrowser->getDisplay()->displayEntityBrowser($form_state);
$form['attributes']['entity_browser']['entity-id'] = [
$form['entity_browser']['#theme_wrappers'] = ['container'];
$form['entity_browser']['browser'] = $this->entityBrowser->getDisplay()->displayEntityBrowser($form_state);
$form['entity_browser']['entity_id'] = [
'#type' => 'hidden',
'#default_value' => $entity ? $entity->id() : '',
'#attributes' => ['class' => ['eb-target']]
......@@ -223,13 +223,9 @@ class EntityEmbedDialog extends FormBase {
'cardinality' => 1
]
];
$form['attributes']['data-entity-id'] = array(
'#type' => 'value',
'#title' => $entity_element['data-entity-id'],
);
}
else {
$form['attributes']['data-entity-id'] = array(
$form['entity_id'] = array(
'#type' => 'entity_autocomplete',
'#target_type' => $entity_element['data-entity-type'],
'#title' => $label,
......@@ -238,7 +234,7 @@ class EntityEmbedDialog extends FormBase {
'#description' => $this->t('Type label and pick the right one from suggestions. Note that the unique ID will be saved.'),
);
if ($bundles = $embed_button->getTypeSetting('bundles')) {
$form['attributes']['data-entity-id']['#selection_settings']['target_bundles'] = $bundles;
$form['entity_id']['#selection_settings']['target_bundles'] = $bundles;
}
}
......@@ -338,6 +334,7 @@ class EntityEmbedDialog extends FormBase {
* The form structure.
*/
public function buildEmbedStep(array $form, FormStateInterface $form_state) {
// Entity element is calculated on every AJAX request/submit. See ::buildForm().
$entity_element = $form_state->get('entity_element');
/** @var \Drupal\embed\EmbedButtonInterface $embed_button */
$embed_button = $form_state->get('embed_button');
......@@ -368,10 +365,6 @@ class EntityEmbedDialog extends FormBase {
'#type' => 'hidden',
'#value' => $entity_element['data-entity-type'],
);
$form['attributes']['data-entity-id'] = array(
'#type' => 'hidden',
'#value' => $entity_element['data-entity-id'],
);
$form['attributes']['data-entity-uuid'] = array(
'#type' => 'hidden',
'#value' => $entity_element['data-entity-uuid'],
......@@ -507,42 +500,42 @@ class EntityEmbedDialog extends FormBase {
* The current state of the form.
*/
public function validateSelectStep(array $form, FormStateInterface $form_state) {
if ($eb_id = $form_state->getValue(['attributes', 'entity_browser', 'entity-id'])) {
$form_state->setValue(['attributes', 'data-entity-id'], $eb_id);
if ($form_state->hasValue(['entity_browser', 'entity_id'])) {
$id = trim($form_state->getValue(['entity_browser', 'entity_id']));
$element = $form['entity_browser']['entity_id'];
}
else {
$id = trim($form_state->getValue(['entity_id']));
$element = $form['entity_id'];
}
$values = $form_state->getValues();
if ($entity_type = $values['attributes']['data-entity-type']) {
$id = trim($values['attributes']['data-entity-id']);
if ($entity = $this->loadEntity($entity_type, $id)) {
if (!$entity->access('view')) {
$form_state->setError($form['attributes']['data-entity-id'], $this->t('Unable to access @type entity @id.', array('@type' => $entity_type, '@id' => $id)));
$entity_type = $form_state->getValue(['attributes', 'data-entity-type']);
if ($entity = $this->loadEntity($entity_type, $id)) {
if (!$entity->access('view')) {
$form_state->setError($element, $this->t('Unable to access @type entity @id.', array('@type' => $entity_type, '@id' => $id)));
}
else {
if ($uuid = $entity->uuid()) {
$form_state->setValueForElement($form['attributes']['data-entity-uuid'], $uuid);
}
else {
$form_state->setValueForElement($form['attributes']['data-entity-id'], $entity->id());
if ($uuid = $entity->uuid()) {
$form_state->setValueForElement($form['attributes']['data-entity-uuid'], $uuid);
}
else {
$form_state->setValueForElement($form['attributes']['data-entity-uuid'], '');
}
// Ensure that at least one Entity Embed Display plugin is present
// before proceeding to the next step. Rasie an error otherwise.
$embed_button = $form_state->get('embed_button');
$display_plugin_options = $this->getDisplayPluginOptions($embed_button, $entity);
// If no plugin is available after taking the intersection,
// raise error. Also log an exception.
if (empty($display_plugin_options)) {
$form_state->setError($form['attributes']['data-entity-id'], $this->t('No display options available for the selected entity. Please select another entity.'));
$this->logger('entity_embed')->warning('No display options available for "@type:" entity "@id" while embedding using button "@button". Please ensure that at least one Entity Embed Display plugin is allowed for this embed button which is available for this entity.', array('@type' => $entity_type, '@id' => $entity->id(), '@button' => $embed_button->id()));
}
$form_state->setError($element, $this->t('Cannot embed @type entity @id because it does not have a UUID.', array('@type' => $entity_type, '@id' => $id)));
}
// Ensure that at least one Entity Embed Display plugin is present
// before proceeding to the next step. Raise an error otherwise.
$embed_button = $form_state->get('embed_button');
$display_plugin_options = $this->getDisplayPluginOptions($embed_button, $entity);
// If no plugin is available after taking the intersection, raise error.
// Also log an exception.
if (empty($display_plugin_options)) {
$form_state->setError($element, $this->t('No display options available for the selected entity. Please select another entity.'));
$this->logger('entity_embed')->warning('No display options available for "@type:" entity "@id" while embedding using button "@button". Please ensure that at least one Entity Embed Display plugin is allowed for this embed button which is available for this entity.', array('@type' => $entity_type, '@id' => $entity->id(), '@button' => $embed_button->id()));
}
}
else {
$form_state->setError($form['attributes']['data-entity-id'], $this->t('Unable to load @type entity @id.', array('@type' => $entity_type, '@id' => $id)));
}
}
else {
$form_state->setError($element, $this->t('Unable to load @type entity @id.', array('@type' => $entity_type, '@id' => $id)));
}
}
......
......@@ -232,6 +232,10 @@ class Entity extends EmbedTypeBase implements ContainerFactoryPluginInterface {
if (!$this->entityTypeManager->getDefinition($entity_type_id)->hasViewBuilderClass()) {
unset($options[$group][$entity_type_id]);
}
// Filter out entity types that do not support UUIDs.
if (!$this->entityTypeManager->getDefinition($entity_type_id)->hasKey('uuid')) {
unset($options[$group][$entity_type_id]);
}
// Filter out entity types that will not have any Entity Embed Display
// plugins.
if (!$this->displayPluginManager->getDefinitionOptionsForEntityType($entity_type_id)) {
......
......@@ -26,7 +26,7 @@ use Drupal\embed\DomHelperTrait;
* @Filter(
* id = "entity_embed",
* title = @Translation("Display embedded entities"),
* description = @Translation("Embeds entities using data attributes: data-entity-type, data-entity-uuid or data-entity-id, and data-view-mode."),
* description = @Translation("Embeds entities using data attributes: data-entity-type, data-entity-uuid, and data-view-mode."),
* type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_REVERSIBLE
* )
*/
......@@ -135,11 +135,8 @@ class EntityEmbedFilter extends FilterBase implements ContainerFactoryPluginInte
public function tips($long = FALSE) {
if ($long) {
return $this->t('
<p>You can embed entities. Additional properties can be added to the embed tag like data-caption and data-align if supported. Examples:</p>
<ul>
<li>Embed by ID: <code>&lt;drupal-entity data-entity-type="node" data-entity-id="1" data-view-mode="teaser" /&gt;</code></li>
<li>Embed by UUID: <code>&lt;drupal-entity data-entity-type="node" data-entity-uuid="07bf3a2e-1941-4a44-9b02-2d1d7a41ec0e" data-view-mode="teaser" /&gt;</code></li>
</ul>');
<p>You can embed entities. Additional properties can be added to the embed tag like data-caption and data-align if supported. Example:</p>
<code>&lt;drupal-entity data-entity-type="node" data-entity-uuid="07bf3a2e-1941-4a44-9b02-2d1d7a41ec0e" data-view-mode="teaser" /&gt;</code>');
}
else {
return $this->t('You can embed entities.');
......
......@@ -52,7 +52,7 @@ class EntityEmbedDialogTest extends EntityEmbedTestBase {
$this->assertResponse(200, 'Embed dialog is accessible with correct filter format and embed button.');
// Ensure form structure of the 'select' step and submit form.
$this->assertFieldByName('attributes[data-entity-id]', '', 'Entity ID/UUID field is present.');
$this->assertFieldByName('entity_id', '', 'Entity ID/UUID field is present.');
// $edit = ['attributes[data-entity-id]' => $this->node->id()];
// $this->drupalPostAjaxForm(NULL, $edit, 'op');
......@@ -85,13 +85,13 @@ class EntityEmbedDialogTest extends EntityEmbedTestBase {
$this->assertResponse(200, 'Embed dialog is accessible with correct filter format and embed button.');
// Ensure form structure of the 'select' step and submit form.
$this->assertFieldByName('attributes[data-entity-id]', '', 'Entity ID/UUID field is present.');
$this->assertFieldByName('entity_id', '', 'Entity ID/UUID field is present.');
// Check that 'Next' is a primary button.
$this->assertFieldByXPath('//input[contains(@class, "button--primary")]', 'Next', 'Next is a primary button');
$title = $this->node->getTitle() . ' (' . $this->node->id() . ')';
$edit = ['attributes[data-entity-id]' => $title];
$edit = ['entity_id' => $title];
$this->drupalPostAjaxForm(NULL, $edit, 'op');
/*$this->drupalPostForm(NULL, $edit, 'Next');
// Ensure form structure of the 'embed' step and submit form.
......
......@@ -33,7 +33,7 @@ class EntityEmbedEntityBrowserTest extends EntityEmbedDialogTest {
$this->assertResponse(200, 'Embed dialog is accessible with custom filter format and default embed button.');
// Verify that an autocomplete field is available by default.
$this->assertFieldByName('attributes[data-entity-id]', '', 'Entity ID/UUID field is present.');
$this->assertFieldByName('entity_id', '', 'Entity ID/UUID field is present.');
$this->assertNoText('Select entities to embed', 'Entity browser button is not present.');
// Set up entity browser.
......@@ -64,7 +64,7 @@ class EntityEmbedEntityBrowserTest extends EntityEmbedDialogTest {
// Verify that the autocomplete field is replaced by an entity browser
// button.
$this->assertNoFieldByName('attributes[data-entity-id]', '', 'Entity ID/UUID field is present.');
$this->assertNoFieldByName('entity_id', '', 'Entity ID/UUID field is present.');
$this->assertText('Select entities to embed', 'Entity browser button is present.');
}
......
......@@ -70,7 +70,7 @@ class EntityEmbedTwigExtension extends \Twig_Extension {
$entity = $this->loadEntity($entity_type, $entity_id);
$context = array(
'data-entity-type' => $entity_type,
'data-entity-id' => $entity_id,
'data-entity-uuid' => $entity->uuid(),
'data-entity-embed-display' => $display_plugin,
'data-entity-embed-settings' => $display_settings,
);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment