You can embed entities. Additional properties can be added to the embed tag like data-caption and data-align if supported. Example:
184 |<drupal-entity data-entity-type="node" data-entity-uuid="07bf3a2e-1941-4a44-9b02-2d1d7a41ec0e" data-view-mode="teaser" />');
185 | }
186 | else {
187 | return $this->t('You can embed entities.');
188 | }
189 | }
190 |
191 | }
192 |
--------------------------------------------------------------------------------
/src/Plugin/entity_embed/EntityEmbedDisplay/ImageFieldFormatter.php:
--------------------------------------------------------------------------------
1 | imageFactory = $image_factory;
55 | }
56 |
57 | /**
58 | * {@inheritdoc}
59 | */
60 | public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
61 | return new static(
62 | $configuration,
63 | $plugin_id,
64 | $plugin_definition,
65 | $container->get('entity_type.manager'),
66 | $container->get('plugin.manager.field.formatter'),
67 | $container->get('typed_data_manager'),
68 | $container->get('image.factory'),
69 | $container->get('language_manager')
70 | );
71 | }
72 |
73 | /**
74 | * {@inheritdoc}
75 | */
76 | public function getFieldValue() {
77 | $value = parent::getFieldValue();
78 | // File field support descriptions, but images do not.
79 | unset($value['description']);
80 | $value += array_intersect_key($this->getAttributeValues(), array('alt' => '', 'title' => ''));
81 | return $value;
82 | }
83 |
84 | /**
85 | * {@inheritdoc}
86 | */
87 | public function access(AccountInterface $account = NULL) {
88 | return parent::access($account)->andIf($this->isValidImage());
89 | }
90 |
91 | /**
92 | * Checks if the image is valid.
93 | *
94 | * @return \Drupal\Core\Access\AccessResult
95 | * Returns the access result.
96 | */
97 | protected function isValidImage() {
98 | // If entity type is not file we have to return early to prevent fatal in
99 | // the condition above. Access should already be forbidden at this point,
100 | // which means this won't have any effect.
101 | // @see EntityEmbedDisplayBase::access()
102 | if ($this->getEntityTypeFromContext() != 'file') {
103 | return AccessResult::forbidden();
104 | }
105 | $access = AccessResult::allowed();
106 |
107 | // @todo needs cacheability metadata for getEntityFromContext.
108 | // @see \Drupal\entity_embed\EntityEmbedDisplay\EntityEmbedDisplayBase::getEntityFromContext()
109 | /** @var \Drupal\file\FileInterface $entity */
110 | if ($entity = $this->getEntityFromContext()) {
111 | // Loading large files is slow, make sure it is an image mime type before
112 | // doing that.
113 | list($type,) = explode('/', $entity->getMimeType(), 2);
114 | $access = AccessResult::allowedIf($type == 'image' && $this->imageFactory->get($entity->getFileUri())->isValid())
115 | // See the above @todo, this is the best we can do for now.
116 | ->addCacheableDependency($entity);
117 | }
118 |
119 | return $access;
120 | }
121 |
122 | /**
123 | * {@inheritdoc}
124 | */
125 | public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
126 | $form = parent::buildConfigurationForm($form, $form_state);
127 |
128 | // File field support descriptions, but images do not.
129 | unset($form['description']);
130 |
131 | // Ensure that the 'Link image to: Content' setting is not available.
132 | if ($this->getDerivativeId() == 'image') {
133 | unset($form['image_link']['#options']['content']);
134 | }
135 |
136 | $entity_element = $form_state->get('entity_element');
137 | // The alt attribute is *required*, but we allow users to opt-in to empty
138 | // alt attributes for the very rare edge cases where that is valid by
139 | // specifying two double quotes as the alternative text in the dialog.
140 | // However, that *is* stored as an empty alt attribute, so if we're editing
141 | // an existing image (which means the src attribute is set) and its alt
142 | // attribute is empty, then we show that as two double quotes in the dialog.
143 | // @see https://www.drupal.org/node/2307647
144 | // Alt attribute behavior is taken from the Core image dialog to ensure a
145 | // consistent UX across various forms.
146 | // @see Drupal\editor\Form\EditorImageDialog::buildForm()
147 | $alt = $this->getAttributeValue('alt', '');
148 | if ($alt === '') {
149 | // Do not change empty alt text to two double quotes if the previously
150 | // used Entity Embed Display plugin was not 'image:image'. That means that
151 | // some other plugin was used so if this image formatter is selected at a
152 | // later stage, then this should be treated as a new edit. We show two
153 | // double quotes in place of empty alt text only if that was filled
154 | // intentionally by the user.
155 | if (!empty($entity_element) && $entity_element['data-entity-embed-display'] == 'image:image') {
156 | $alt = '""';
157 | }
158 | }
159 |
160 | // Add support for editing the alternate and title text attributes.
161 | $form['alt'] = array(
162 | '#type' => 'textfield',
163 | '#title' => $this->t('Alternate text'),
164 | '#default_value' => $alt,
165 | '#description' => $this->t('This text will be used by screen readers, search engines, or when the image cannot be loaded.'),
166 | '#parents' => array('attributes', 'alt'),
167 | '#required' => TRUE,
168 | '#required_error' => $this->t('Alternative text is required."" — two double quotes without any content).'),
169 | '#maxlength' => 512,
170 | );
171 | $form['title'] = array(
172 | '#type' => 'textfield',
173 | '#title' => $this->t('Title'),
174 | '#default_value' => $this->getAttributeValue('title', ''),
175 | '#description' => t('The title is used as a tool tip when the user hovers the mouse over the image.'),
176 | '#parents' => array('attributes', 'title'),
177 | '#maxlength' => 1024,
178 | );
179 |
180 | return $form;
181 | }
182 |
183 | /**
184 | * {@inheritdoc}
185 | */
186 | public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
187 | // When the alt attribute is set to two double quotes, transform it to the
188 | // empty string: two double quotes signify "empty alt attribute". See above.
189 | if (trim($form_state->getValue(array('attributes', 'alt'))) === '""') {
190 | $form_state->setValue(array('attributes', 'alt'), '');
191 | }
192 | }
193 |
194 | }
195 |
--------------------------------------------------------------------------------
/src/EntityEmbedDisplay/FieldFormatterEntityEmbedDisplayBase.php:
--------------------------------------------------------------------------------
1 | formatterPluginManager = $formatter_plugin_manager;
65 | $this->setConfiguration($configuration);
66 | $this->typedDataManager = $typed_data_manager;
67 | parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $language_manager);
68 | }
69 |
70 | /**
71 | * {@inheritdoc}
72 | */
73 | public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
74 | return new static(
75 | $configuration,
76 | $plugin_id,
77 | $plugin_definition,
78 | $container->get('entity_type.manager'),
79 | $container->get('plugin.manager.field.formatter'),
80 | $container->get('typed_data_manager'),
81 | $container->get('language_manager')
82 | );
83 | }
84 |
85 | /**
86 | * Get the FieldDefinition object required to render this field's formatter.
87 | *
88 | * @return \Drupal\Core\Field\BaseFieldDefinition
89 | * The field definition.
90 | *
91 | * @see \Drupal\entity_embed\FieldFormatterEntityEmbedDisplayBase::build()
92 | */
93 | public function getFieldDefinition() {
94 | if (!isset($this->fieldDefinition)) {
95 | $field_type = $this->getPluginDefinition()['field_type'];
96 | $this->fieldDefinition = BaseFieldDefinition::create($field_type);
97 | // Ensure the field name is unique for each Entity Embed Display plugin
98 | // instance.
99 | static $index = 0;
100 | $this->fieldDefinition->setName('_entity_embed_' . $index++);
101 | }
102 | return $this->fieldDefinition;
103 | }
104 |
105 | /**
106 | * Get the field value required to pass into the field formatter.
107 | *
108 | * @return mixed
109 | * The field value.
110 | */
111 | abstract public function getFieldValue();
112 |
113 | /**
114 | * {@inheritdoc}
115 | */
116 | public function access(AccountInterface $account = NULL) {
117 | return parent::access($account)->andIf($this->isApplicableFieldFormatter());
118 | }
119 |
120 | /**
121 | * Checks if the field formatter is applicable.
122 | *
123 | * @return \Drupal\Core\Access\AccessResult
124 | * Returns the access result.
125 | */
126 | protected function isApplicableFieldFormatter() {
127 | $definition = $this->formatterPluginManager->getDefinition($this->getFieldFormatterId());
128 | return AccessResult::allowedIf($definition['class']::isApplicable($this->getFieldDefinition()));
129 | }
130 |
131 | /**
132 | * Returns the field formatter id.
133 | *
134 | * @return string|null
135 | * Returns field formatter id or null.
136 | */
137 | public function getFieldFormatterId() {
138 | return $this->getDerivativeId();
139 | }
140 |
141 | /**
142 | * {@inheritdoc}
143 | */
144 | public function build() {
145 | // Create a temporary node object to which our fake field value can be
146 | // added.
147 | $node = Node::create(array('type' => '_entity_embed'));
148 |
149 | $definition = $this->getFieldDefinition();
150 |
151 | /* @var \Drupal\Core\Field\FieldItemListInterface $items $items */
152 | // Create a field item list object, 1 is the value, array('target_id' => 1)
153 | // would work too, or multiple values. 1 is passed down from the list to the
154 | // field item, which knows that an integer is the ID.
155 | $items = $this->typedDataManager->create(
156 | $definition,
157 | $this->getFieldValue($definition),
158 | $definition->getName(),
159 | $node->getTypedData()
160 | );
161 |
162 | // Prepare, expects an array of items, keyed by parent entity ID.
163 | $formatter = $this->getFieldFormatter();
164 | $formatter->prepareView(array($node->id() => $items));
165 | $build = $formatter->viewElements($items, $this->getLangcode());
166 | // For some reason $build[0]['#printed'] is TRUE, which means it will fail
167 | // to render later. So for now we manually fix that.
168 | // @todo Investigate why this is needed.
169 | show($build[0]);
170 | return $build[0];
171 | }
172 |
173 | /**
174 | * {@inheritdoc}
175 | */
176 | public function defaultConfiguration() {
177 | return $this->formatterPluginManager->getDefaultSettings($this->getFieldFormatterId());
178 | }
179 |
180 | /**
181 | * {@inheritdoc}
182 | */
183 | public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
184 | return $this->getFieldFormatter()->settingsForm($form, $form_state);
185 | }
186 |
187 | /**
188 | * Constructs a field formatter.
189 | *
190 | * @return \Drupal\Core\Field\FormatterInterface
191 | * The formatter object.
192 | */
193 | public function getFieldFormatter() {
194 | if (!isset($this->fieldFormatter)) {
195 | $display = array(
196 | 'type' => $this->getFieldFormatterId(),
197 | 'settings' => $this->getConfiguration(),
198 | 'label' => 'hidden',
199 | );
200 |
201 | // Create the formatter plugin. Will use the default formatter for that
202 | // field type if none is passed.
203 | $this->fieldFormatter = $this->formatterPluginManager->getInstance(
204 | array(
205 | 'field_definition' => $this->getFieldDefinition(),
206 | 'view_mode' => '_entity_embed',
207 | 'configuration' => $display,
208 | )
209 | );
210 | }
211 |
212 | return $this->fieldFormatter;
213 | }
214 |
215 | /**
216 | * Creates a new faux-field definition.
217 | *
218 | * @param string $type
219 | * The type of the field.
220 | *
221 | * @return \Drupal\Core\Field\BaseFieldDefinition
222 | * A new field definition.
223 | */
224 | protected function createFieldDefinition($type) {
225 | $definition = BaseFieldDefinition::create($type);
226 | static $index = 0;
227 | $definition->setName('_entity_embed_' . $index++);
228 | return $definition;
229 | }
230 |
231 | /**
232 | * {@inheritdoc}
233 | */
234 | public function calculateDependencies() {
235 | $this->addDependencies(parent::calculateDependencies());
236 |
237 | $definition = $this->formatterPluginManager->getDefinition($this->getFieldFormatterId());
238 | $this->addDependency('module', $definition['provider']);
239 | // @todo Investigate why this does not work currently.
240 | // $this->calculatePluginDependencies($this->getFieldFormatter());
241 | return $this->dependencies;
242 | }
243 |
244 | }
245 |
--------------------------------------------------------------------------------
/js/plugins/drupalentity/plugin.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file
3 | * Drupal Entity embed plugin.
4 | */
5 |
6 | (function ($, Drupal, CKEDITOR) {
7 |
8 | "use strict";
9 |
10 | CKEDITOR.plugins.add('drupalentity', {
11 | // This plugin requires the Widgets System defined in the 'widget' plugin.
12 | requires: 'widget',
13 |
14 | // The plugin initialization logic goes inside this method.
15 | beforeInit: function (editor) {
16 | // Configure CKEditor DTD for custom drupal-entity element.
17 | // @see https://www.drupal.org/node/2448449#comment-9717735
18 | var dtd = CKEDITOR.dtd, tagName;
19 | dtd['drupal-entity'] = {'#': 1};
20 | // Register drupal-entity element as allowed child, in each tag that can
21 | // contain a div element.
22 | for (tagName in dtd) {
23 | if (dtd[tagName].div) {
24 | dtd[tagName]['drupal-entity'] = 1;
25 | }
26 | }
27 |
28 | // Generic command for adding/editing entities of all types.
29 | editor.addCommand('editdrupalentity', {
30 | allowedContent: 'drupal-entity[data-embed-button,data-entity-type,data-entity-uuid,data-entity-embed-display,data-entity-embed-display-settings,data-align,data-caption]',
31 | requiredContent: 'drupal-entity[data-embed-button,data-entity-type,data-entity-uuid,data-entity-embed-display,data-entity-embed-display-settings,data-align,data-caption]',
32 | modes: { wysiwyg : 1 },
33 | canUndo: true,
34 | exec: function (editor, data) {
35 | data = data || {};
36 |
37 | var existingElement = getSelectedEmbeddedEntity(editor);
38 |
39 | var existingValues = {};
40 | if (existingElement && existingElement.$ && existingElement.$.firstChild) {
41 | var embedDOMElement = existingElement.$.firstChild;
42 | // Populate array with the entity's current attributes.
43 | var attribute = null, attributeName;
44 | for (var key = 0; key < embedDOMElement.attributes.length; key++) {
45 | attribute = embedDOMElement.attributes.item(key);
46 | attributeName = attribute.nodeName.toLowerCase();
47 | if (attributeName.substring(0, 15) === 'data-cke-saved-') {
48 | continue;
49 | }
50 | existingValues[attributeName] = existingElement.data('cke-saved-' + attributeName) || attribute.nodeValue;
51 | }
52 | }
53 |
54 | var embed_button_id = data.id ? data.id : existingValues['data-embed-button'];
55 |
56 | var dialogSettings = {
57 | dialogClass: 'entity-select-dialog',
58 | resizable: false
59 | };
60 |
61 | var saveCallback = function (values) {
62 | var entityElement = editor.document.createElement('drupal-entity');
63 | var attributes = values.attributes;
64 | for (var key in attributes) {
65 | entityElement.setAttribute(key, attributes[key]);
66 | }
67 |
68 | editor.insertHtml(entityElement.getOuterHtml());
69 | if (existingElement) {
70 | // Detach the behaviors that were attached when the entity content
71 | // was inserted.
72 | Drupal.runEmbedBehaviors('detach', existingElement.$);
73 | existingElement.remove();
74 | }
75 | };
76 |
77 | // Open the entity embed dialog for corresponding EmbedButton.
78 | Drupal.ckeditor.openDialog(editor, Drupal.url('entity-embed/dialog/' + editor.config.drupal.format + '/' + embed_button_id), existingValues, saveCallback, dialogSettings);
79 | }
80 | });
81 |
82 | // Register the entity embed widget.
83 | editor.widgets.add('drupalentity', {
84 | // Minimum HTML which is required by this widget to work.
85 | allowedContent: 'drupal-entity[data-entity-type,data-entity-uuid,data-entity-embed-display,data-entity-embed-display-settings,data-align,data-caption]',
86 | requiredContent: 'drupal-entity[data-entity-type,data-entity-uuid,data-entity-embed-display,data-entity-embed-display-settings,data-align,data-caption]',
87 |
88 | // Simply recognize the element as our own. The inner markup if fetched
89 | // and inserted the init() callback, since it requires the actual DOM
90 | // element.
91 | upcast: function (element) {
92 | var attributes = element.attributes;
93 | if (attributes['data-entity-type'] === undefined || (attributes['data-entity-id'] === undefined && attributes['data-entity-uuid'] === undefined) || (attributes['data-view-mode'] === undefined && attributes['data-entity-embed-display'] === undefined)) {
94 | return;
95 | }
96 | // Generate an ID for the element, so that we can use the Ajax
97 | // framework.
98 | element.attributes.id = generateEmbedId();
99 | return element;
100 | },
101 |
102 | // Fetch the rendered entity.
103 | init: function () {
104 | /** @type {CKEDITOR.dom.element} */
105 | var element = this.element;
106 | // Use the Ajax framework to fetch the HTML, so that we can retrieve
107 | // out-of-band assets (JS, CSS...).
108 | var entityEmbedPreview = Drupal.ajax({
109 | base: element.getId(),
110 | element: element.$,
111 | url: Drupal.url('embed/preview/' + editor.config.drupal.format + '?' + $.param({
112 | value: element.getOuterHtml()
113 | })),
114 | progress: {type: 'none'},
115 | // Use a custom event to trigger the call.
116 | event: 'entity_embed_dummy_event'
117 | });
118 | entityEmbedPreview.execute();
119 | },
120 |
121 | // Downcast the element.
122 | downcast: function (element) {
123 | // Only keep the wrapping element.
124 | element.setHtml('');
125 | // Remove the auto-generated ID.
126 | delete element.attributes.id;
127 | return element;
128 | }
129 | });
130 |
131 | // Register the toolbar buttons.
132 | if (editor.ui.addButton) {
133 | for (var key in editor.config.DrupalEntity_buttons) {
134 | var button = editor.config.DrupalEntity_buttons[key];
135 | editor.ui.addButton(button.id, {
136 | label: button.label,
137 | data: button,
138 | allowedContent: 'drupal-entity[!data-entity-type,!data-entity-uuid,!data-entity-embed-display,!data-entity-embed-display-settings,!data-align,!data-caption,!data-embed-button]',
139 | click: function(editor) {
140 | editor.execCommand('editdrupalentity', this.data);
141 | },
142 | icon: button.image
143 | });
144 | }
145 | }
146 |
147 | // Register context menu option for editing widget.
148 | if (editor.contextMenu) {
149 | editor.addMenuGroup('drupalentity');
150 | editor.addMenuItem('drupalentity', {
151 | label: Drupal.t('Edit Entity'),
152 | icon: this.path + 'entity.png',
153 | command: 'editdrupalentity',
154 | group: 'drupalentity'
155 | });
156 |
157 | editor.contextMenu.addListener(function(element) {
158 | if (isEditableEntityWidget(editor, element)) {
159 | return { drupalentity: CKEDITOR.TRISTATE_OFF };
160 | }
161 | });
162 | }
163 |
164 | // Execute widget editing action on double click.
165 | editor.on('doubleclick', function (evt) {
166 | var element = getSelectedEmbeddedEntity(editor) || evt.data.element;
167 |
168 | if (isEditableEntityWidget(editor, element)) {
169 | editor.execCommand('editdrupalentity');
170 | }
171 | });
172 | }
173 | });
174 |
175 | /**
176 | * Get the surrounding drupalentity widget element.
177 | *
178 | * @param {CKEDITOR.editor} editor
179 | */
180 | function getSelectedEmbeddedEntity(editor) {
181 | var selection = editor.getSelection();
182 | var selectedElement = selection.getSelectedElement();
183 | if (isEditableEntityWidget(editor, selectedElement)) {
184 | return selectedElement;
185 | }
186 |
187 | return null;
188 | }
189 |
190 | /**
191 | * Checks if the given element is an editable drupalentity widget.
192 | *
193 | * @param {CKEDITOR.editor} editor
194 | * @param {CKEDITOR.htmlParser.element} element
195 | */
196 | function isEditableEntityWidget (editor, element) {
197 | var widget = editor.widgets.getByElement(element, true);
198 | if (!widget || widget.name !== 'drupalentity') {
199 | return false;
200 | }
201 |
202 | var button = $(element.$.firstChild).attr('data-embed-button');
203 | if (!button) {
204 | // If there was no data-embed-button attribute, not editable.
205 | return false;
206 | }
207 |
208 | // The button itself must be valid.
209 | return editor.config.DrupalEntity_buttons.hasOwnProperty(button);
210 | }
211 |
212 | /**
213 | * Generates unique HTML IDs for the widgets.
214 | *
215 | * @returns {string}
216 | */
217 | function generateEmbedId() {
218 | if (typeof generateEmbedId.counter == 'undefined') {
219 | generateEmbedId.counter = 0;
220 | }
221 | return 'entity-embed-' + generateEmbedId.counter++;
222 | }
223 |
224 | })(jQuery, Drupal, CKEDITOR);
225 |
--------------------------------------------------------------------------------
/src/EntityEmbedDisplay/EntityEmbedDisplayBase.php:
--------------------------------------------------------------------------------
1 | setConfiguration($configuration);
67 | $this->entityTypeManager = $entity_type_manager;
68 | $this->languageManager = $language_manager;
69 | }
70 |
71 | /**
72 | * {@inheritdoc}
73 | */
74 | public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
75 | return new static(
76 | $configuration,
77 | $plugin_id,
78 | $plugin_definition,
79 | $container->get('entity_type.manager'),
80 | $container->get('language_manager')
81 | );
82 | }
83 |
84 | /**
85 | * {@inheritdoc}
86 | */
87 | public function access(AccountInterface $account = NULL) {
88 | // @todo Add a hook_entity_embed_display_access()?
89 | // Check that the plugin's registered entity types matches the current
90 | // entity type.
91 | return AccessResult::allowedIf($this->isValidEntityType())
92 | // @see \Drupal\Core\Entity\EntityTypeManager
93 | ->addCacheTags(['entity_types']);
94 | }
95 |
96 | /**
97 | * Validates that this Entity Embed Display plugin applies to the current
98 | * entity type.
99 | *
100 | * This checks the plugin annotation's 'entity_types' value, which should be
101 | * an array of entity types that this plugin can process, or FALSE if the
102 | * plugin applies to all entity types.
103 | *
104 | * @return bool
105 | * TRUE if the plugin can display the current entity type, or FALSE
106 | * otherwise.
107 | */
108 | protected function isValidEntityType() {
109 | // First, determine whether or not the entity type id is valid. Return FALSE
110 | // if the specified id is not valid.
111 | $entity_type = $this->getEntityTypeFromContext();
112 | if (!$this->entityTypeManager->getDefinition($entity_type)) {
113 | return FALSE;
114 | }
115 |
116 | $definition = $this->getPluginDefinition();
117 | if ($definition['entity_types'] === FALSE) {
118 | return TRUE;
119 | }
120 | else {
121 | return in_array($entity_type, $definition['entity_types']);
122 | }
123 | }
124 |
125 | /**
126 | * {@inheritdoc}
127 | */
128 | abstract public function build();
129 |
130 | /**
131 | * {@inheritdoc}
132 | */
133 | public function calculateDependencies() {
134 | return array();
135 | }
136 |
137 | /**
138 | * {@inheritdoc}
139 | */
140 | public function defaultConfiguration() {
141 | return array();
142 | }
143 |
144 | /**
145 | * {@inheritdoc}
146 | */
147 | public function getConfiguration() {
148 | return $this->configuration;
149 | }
150 |
151 | /**
152 | * {@inheritdoc}
153 | */
154 | public function setConfiguration(array $configuration) {
155 | $this->configuration = NestedArray::mergeDeep(
156 | $this->defaultConfiguration(),
157 | $configuration
158 | );
159 | return $this;
160 | }
161 |
162 | /**
163 | * {@inheritdoc}
164 | */
165 | public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
166 | return $form;
167 | }
168 |
169 | /**
170 | * {@inheritdoc}
171 | */
172 | public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
173 | // Do nothing.
174 | }
175 |
176 | /**
177 | * {@inheritdoc}
178 | */
179 | public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
180 | if (!$form_state->getErrors()) {
181 | $this->configuration = array_intersect_key($form_state->getValues(), $this->defaultConfiguration());
182 | }
183 | }
184 |
185 | /**
186 | * Gets a configuration value.
187 | *
188 | * @param string $name
189 | * The name of the plugin configuration value.
190 | * @param mixed $default
191 | * The default value to return if the configuration value does not exist.
192 | *
193 | * @return mixed
194 | * The currently set configuration value, or the value of $default if the
195 | * configuration value is not set.
196 | */
197 | public function getConfigurationValue($name, $default = NULL) {
198 | $configuration = $this->getConfiguration();
199 | return array_key_exists($name, $configuration) ? $configuration[$name] : $default;
200 | }
201 |
202 | /**
203 | * Sets the value for a defined context.
204 | *
205 | * @param string $name
206 | * The name of the context in the plugin definition.
207 | * @param mixed $value
208 | * The value to set the context to. The value has to validate against the
209 | * provided context definition.
210 | */
211 | public function setContextValue($name, $value) {
212 | $this->context[$name] = $value;
213 | }
214 |
215 | /**
216 | * Gets the values for all defined contexts.
217 | *
218 | * @return array
219 | * An array of set context values, keyed by context name.
220 | */
221 | public function getContextValues() {
222 | return $this->context;
223 | }
224 |
225 | /**
226 | * Gets the value for a defined context.
227 | *
228 | * @param string $name
229 | * The name of the context in the plugin configuration.
230 | *
231 | * @return mixed
232 | * The currently set context value.
233 | */
234 | public function getContextValue($name) {
235 | return $this->context[$name];
236 | }
237 |
238 | /**
239 | * Returns whether or not value is set for a defined context.
240 | *
241 | * @param string $name
242 | * The name of the context in the plugin configuration.
243 | *
244 | * @return bool
245 | * True if context value exists, false otherwise.
246 | */
247 | public function hasContextValue($name) {
248 | return array_key_exists($name, $this->context);
249 | }
250 |
251 | /**
252 | * Gets the entity type from the current context.
253 | *
254 | * @return string
255 | * The entity type id.
256 | */
257 | public function getEntityTypeFromContext() {
258 | if ($this->hasContextValue('entity')) {
259 | return $this->getContextValue('entity')->getEntityTypeId();
260 | }
261 | else {
262 | return $this->getContextValue('entity_type');
263 | }
264 | }
265 |
266 | /**
267 | * Gets the entity from the current context.
268 | *
269 | * @todo Where doe sthis come from? The value must come from somewhere, yet
270 | * this does not implement any context-related interfaces. This is an *input*,
271 | * so we need cache contexts and possibly cache tags to reflect where this
272 | * came from. We need that for *everything* that this class does that relies
273 | * on this, plus any of its subclasses. Right now, this is effectively a
274 | * global that breaks cacheability metadata.
275 | *
276 | * @return \Drupal\Core\Entity\EntityInterface
277 | */
278 | public function getEntityFromContext() {
279 | if ($this->hasContextValue('entity')) {
280 | return $this->getContextValue('entity');
281 | }
282 | }
283 |
284 | /**
285 | * Sets the values for all attributes.
286 | *
287 | * @param array $attributes
288 | * An array of attributes, keyed by attribute name.
289 | */
290 | public function setAttributes(array $attributes) {
291 | $this->attributes = $attributes;
292 | }
293 |
294 | /**
295 | * Gets the values for all attributes.
296 | *
297 | * @return array
298 | * An array of set attribute values, keyed by attribute name.
299 | */
300 | public function getAttributeValues() {
301 | return $this->attributes;
302 | }
303 |
304 | /**
305 | * Gets the value for an attribute.
306 | *
307 | * @param string $name
308 | * The name of the attribute.
309 | * @param mixed $default
310 | * The default value to return if the attribute value does not exist.
311 | *
312 | * @return mixed
313 | * The currently set attribute value.
314 | */
315 | public function getAttributeValue($name, $default = NULL) {
316 | $attributes = $this->getAttributeValues();
317 | return array_key_exists($name, $attributes) ? $attributes[$name] : $default;
318 | }
319 |
320 | /**
321 | * Gets the current language code.
322 | *
323 | * @return string
324 | */
325 | public function getLangcode() {
326 | $langcode = $this->getAttributeValue('data-langcode');
327 | if (empty($langcode)) {
328 | $langcode = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId();
329 | }
330 | return $langcode;
331 | }
332 |
333 | }
334 |
--------------------------------------------------------------------------------
/src/Plugin/EmbedType/Entity.php:
--------------------------------------------------------------------------------
1 | entityTypeManager = $entity_type_manager;
76 | $this->entityTypeRepository = $entity_type_repository;
77 | $this->entityTypeBundleInfo = $bundle_info;
78 | $this->displayPluginManager = $display_plugin_manager;
79 | }
80 |
81 | /**
82 | * {@inheritdoc}
83 | */
84 | public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
85 | return new static(
86 | $configuration,
87 | $plugin_id,
88 | $plugin_definition,
89 | $container->get('entity_type.manager'),
90 | $container->get('entity_type.repository'),
91 | $container->get('entity_type.bundle.info'),
92 | $container->get('plugin.manager.entity_embed.display')
93 | );
94 | }
95 |
96 | /**
97 | * {@inheritdoc}
98 | */
99 | public function defaultConfiguration() {
100 | return [
101 | 'entity_type' => 'node',
102 | 'bundles' => [],
103 | 'display_plugins' => [],
104 | 'entity_browser' => '',
105 | 'entity_browser_settings' => [],
106 | ];
107 | }
108 |
109 | /**
110 | * {@inheritdoc}
111 | */
112 | public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
113 | $embed_button = $form_state->getTemporaryValue('embed_button');
114 | $entity_type_id = $this->getConfigurationValue('entity_type');
115 |
116 | $form['entity_type'] = array(
117 | '#type' => 'select',
118 | '#title' => $this->t('Entity type'),
119 | '#options' => $this->getEntityTypeOptions(),
120 | '#default_value' => $entity_type_id,
121 | '#description' => $this->t("Entity type for which this button is to enabled."),
122 | '#required' => TRUE,
123 | '#ajax' => array(
124 | 'callback' => array($form_state->getFormObject(), 'updateTypeSettings'),
125 | 'effect' => 'fade',
126 | ),
127 | '#disabled' => !$embed_button->isNew(),
128 | );
129 |
130 | if ($entity_type_id) {
131 | $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
132 | $form['bundles'] = array(
133 | '#type' => 'checkboxes',
134 | '#title' => $entity_type->getBundleLabel() ?: $this->t('Bundles'),
135 | '#options' => $this->getEntityBundleOptions($entity_type),
136 | '#default_value' => $this->getConfigurationValue('bundles'),
137 | '#description' => $this->t('If none are selected, all are allowed.'),
138 | );
139 | $form['bundles']['#access'] = !empty($form['bundles']['#options']);
140 |
141 | // Allow option to limit Entity Embed Display plugins.
142 | $form['display_plugins'] = array(
143 | '#type' => 'checkboxes',
144 | '#title' => $this->t('Allowed Entity Embed Display plugins'),
145 | '#options' => $this->displayPluginManager->getDefinitionOptionsForEntityType($entity_type_id),
146 | '#default_value' => $this->getConfigurationValue('display_plugins'),
147 | '#description' => $this->t('If none are selected, all are allowed. Note that these are the plugins which are allowed for this entity type, all of these might not be available for the selected entity.'),
148 | );
149 | $form['display_plugins']['#access'] = !empty($form['display_plugins']['#options']);
150 |
151 | /** @var \Drupal\entity_browser\EntityBrowserInterface[] $browsers */
152 | if ($this->entityTypeManager->hasDefinition('entity_browser') && ($browsers = $this->entityTypeManager->getStorage('entity_browser')->loadMultiple())) {
153 | $ids = array_keys($browsers);
154 | $labels = array_map(
155 | function ($item) {
156 | /** @var \Drupal\entity_browser\EntityBrowserInterface $item */
157 | return $item->label();
158 | },
159 | $browsers
160 | );
161 | $options = ['_none' => $this->t('None (autocomplete)')] + array_combine($ids, $labels);
162 | $form['entity_browser'] = [
163 | '#type' => 'select',
164 | '#title' => $this->t('Entity browser'),
165 | '#description' => $this->t('Entity browser to be used to select entities to be embedded. Note that not all display plugins from Entity Browser are compatible with Entity Embed. For example, the "iFrame" display is compatible, while the "Modal" display is not.'),
166 | '#options' => $options,
167 | '#default_value' => $this->getConfigurationValue('entity_browser'),
168 | ];
169 | $form['entity_browser_settings'] = [
170 | '#type' => 'details',
171 | '#title' => $this->t('Entity browser settings'),
172 | '#open' => TRUE,
173 | '#states' => [
174 | 'invisible' => [
175 | ':input[name="type_settings[entity_browser]"]' => ['value' => '_none'],
176 | ],
177 | ],
178 | ];
179 | $form['entity_browser_settings']['display_review'] = [
180 | '#type' => 'checkbox',
181 | '#title' => 'Display the entity after selection',
182 | '#default_value' => $this->getConfigurationValue('entity_browser_settings')['display_review'],
183 | ];
184 | }
185 | else {
186 | $form['entity_browser'] = [
187 | '#type' => 'value',
188 | '#value' => '',
189 | ];
190 | }
191 | }
192 |
193 | return $form;
194 | }
195 |
196 | /**
197 | * {@inheritdoc}
198 | */
199 | public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
200 | // Filter down the bundles and allowed Entity Embed Display plugins.
201 | $bundles = $form_state->getValue('bundles');
202 | $form_state->setValue('bundles', array_keys(array_filter($bundles)));
203 | $display_plugins = $form_state->getValue('display_plugins');
204 | $form_state->setValue('display_plugins', array_keys(array_filter($display_plugins)));
205 | $entity_browser = $form_state->getValue('entity_browser') == '_none' ? '' : $form_state->getValue('entity_browser');
206 | $form_state->setValue('entity_browser', $entity_browser);
207 | $form_state->setValue('entity_browser_settings', $form_state->getValue('entity_browser_settings'));
208 |
209 | parent::submitConfigurationForm($form, $form_state);
210 | }
211 |
212 | /**
213 | * Builds a list of entity type options.
214 | *
215 | * Configuration entity types without a view builder are filtered out while
216 | * all other entity types are kept.
217 | *
218 | * @return array
219 | * An array of entity type labels, keyed by entity type name.
220 | */
221 | protected function getEntityTypeOptions() {
222 | $options = $this->entityTypeRepository->getEntityTypeLabels(TRUE);
223 |
224 | foreach ($options as $group => $group_types) {
225 | foreach (array_keys($group_types) as $entity_type_id) {
226 | // Filter out entity types that do not have a view builder class.
227 | if (!$this->entityTypeManager->getDefinition($entity_type_id)->hasViewBuilderClass()) {
228 | unset($options[$group][$entity_type_id]);
229 | }
230 | // Filter out entity types that do not support UUIDs.
231 | if (!$this->entityTypeManager->getDefinition($entity_type_id)->hasKey('uuid')) {
232 | unset($options[$group][$entity_type_id]);
233 | }
234 | // Filter out entity types that will not have any Entity Embed Display
235 | // plugins.
236 | if (!$this->displayPluginManager->getDefinitionOptionsForEntityType($entity_type_id)) {
237 | unset($options[$group][$entity_type_id]);
238 | }
239 | }
240 | }
241 |
242 | return $options;
243 | }
244 |
245 | /**
246 | * Builds a list of entity type bundle options.
247 | *
248 | * Configuration entity types without a view builder are filtered out while
249 | * all other entity types are kept.
250 | *
251 | * @return array
252 | * An array of bundle labels, keyed by bundle name.
253 | */
254 | protected function getEntityBundleOptions(EntityTypeInterface $entity_type) {
255 | $bundle_options = array();
256 | // If the entity has bundles, allow option to restrict to bundle(s).
257 | if ($entity_type->hasKey('bundle')) {
258 | foreach ($this->entityTypeBundleInfo->getBundleInfo($entity_type->id()) as $bundle_id => $bundle_info) {
259 | $bundle_options[$bundle_id] = $bundle_info['label'];
260 | }
261 | natsort($bundle_options);
262 | }
263 | return $bundle_options;
264 | }
265 |
266 | /**
267 | * {@inheritdoc}
268 | */
269 | public function getDefaultIconUrl() {
270 | return file_create_url(drupal_get_path('module', 'entity_embed') . '/js/plugins/drupalentity/entity.png');
271 | }
272 |
273 | /**
274 | * {@inheritdoc}
275 | */
276 | public function calculateDependencies() {
277 | $this->addDependencies(parent::calculateDependencies());
278 |
279 | $entity_type_id = $this->getConfigurationValue('entity_type');
280 | $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
281 | $this->addDependency('module', $entity_type->getProvider());
282 |
283 | // Calculate bundle dependencies.
284 | foreach ($this->getConfigurationValue('bundles') as $bundle) {
285 | $bundle_dependency = $entity_type->getBundleConfigDependency($bundle);
286 | $this->addDependency($bundle_dependency['type'], $bundle_dependency['name']);
287 | }
288 |
289 | // Calculate display Entity Embed Display dependencies.
290 | foreach ($this->getConfigurationValue('display_plugins') as $display_plugin) {
291 | $instance = $this->displayPluginManager->createInstance($display_plugin);
292 | $this->calculatePluginDependencies($instance);
293 | }
294 |
295 | return $this->dependencies;
296 | }
297 |
298 | }
299 |
--------------------------------------------------------------------------------
/src/Tests/EntityEmbedFilterTest.php:
--------------------------------------------------------------------------------
1 | node->id() . '" data-view-mode="teaser">This placeholder should not be rendered.