Issue #2350327 by PieterDC, Dave Reid: editor.module should use the same data- attributes as entity_embed.module uses
parent
99d219aa0e
commit
3c6f8b8da9
|
@ -3,8 +3,8 @@
|
|||
* Drupal Image plugin.
|
||||
*
|
||||
* This alters the existing CKEditor image2 widget plugin to:
|
||||
* - require a data-editor-file-uuid attribute (which Drupal uses to track where
|
||||
* images are being used)
|
||||
* - require a data-entity-type and a data-entity-uuid attribute (which Drupal
|
||||
* uses to track where images are being used)
|
||||
* - use a Drupal-native dialog (that is in fact just an alterable Drupal form
|
||||
* like any other) instead of CKEditor's own dialogs.
|
||||
* @see \Drupal\editor\Form\EditorImageDialog
|
||||
|
@ -18,7 +18,7 @@
|
|||
|
||||
beforeInit: function (editor) {
|
||||
// Override the image2 widget definition to require and handle the
|
||||
// additional data-editor-file-uuid attribute.
|
||||
// additional data-entity-type and data-entity-uuid attributes.
|
||||
editor.on('widgetDefinition', function (event) {
|
||||
var widgetDefinition = event.data;
|
||||
if (widgetDefinition.name !== 'image') {
|
||||
|
@ -26,8 +26,8 @@
|
|||
}
|
||||
|
||||
// Override requiredContent & allowedContent.
|
||||
widgetDefinition.requiredContent = 'img[alt,src,width,height,data-editor-file-uuid]';
|
||||
widgetDefinition.allowedContent.img.attributes += ',!data-editor-file-uuid';
|
||||
widgetDefinition.requiredContent = 'img[alt,src,width,height,data-entity-type,data-entity-uuid]';
|
||||
widgetDefinition.allowedContent.img.attributes += ',!data-entity-type,!data-entity-uuid';
|
||||
// We don't allow <figure>, <figcaption>, <div> or <p> in our downcast.
|
||||
delete widgetDefinition.allowedContent.figure;
|
||||
delete widgetDefinition.allowedContent.figcaption;
|
||||
|
@ -40,14 +40,14 @@
|
|||
|
||||
// Override downcast(): since we only accept <img> in our upcast method,
|
||||
// the element is already correct. We only need to update the element's
|
||||
// data-editor-file-uuid attribute.
|
||||
// data-entity-uuid attribute.
|
||||
widgetDefinition.downcast = function (element) {
|
||||
element.attributes['data-editor-file-uuid'] = this.data['data-editor-file-uuid'];
|
||||
element.attributes['data-entity-uuid'] = this.data['data-entity-uuid'];
|
||||
};
|
||||
|
||||
// We want to upcast <img> elements to a DOM structure required by the
|
||||
// image2 widget; we only accept an <img> tag, and that <img> tag MAY
|
||||
// have a data-editor-file-uuid attribute.
|
||||
// have a data-entity-type and a data-entity-uuid attribute.
|
||||
widgetDefinition.upcast = function (element, data) {
|
||||
if (element.name !== 'img') {
|
||||
return;
|
||||
|
@ -57,8 +57,10 @@
|
|||
return;
|
||||
}
|
||||
|
||||
// Parse the data-editor-file-uuid attribute.
|
||||
data['data-editor-file-uuid'] = element.attributes['data-editor-file-uuid'];
|
||||
// Parse the data-entity-type attribute.
|
||||
data['data-entity-type'] = element.attributes['data-entity-type'];
|
||||
// Parse the data-entity-uuid attribute.
|
||||
data['data-entity-uuid'] = element.attributes['data-entity-uuid'];
|
||||
|
||||
return element;
|
||||
};
|
||||
|
@ -71,7 +73,8 @@
|
|||
'alt': 'alt',
|
||||
'width': 'width',
|
||||
'height': 'height',
|
||||
'data-editor-file-uuid': 'data-editor-file-uuid'
|
||||
'data-entity-type': 'data-entity-type',
|
||||
'data-entity-uuid': 'data-entity-uuid'
|
||||
};
|
||||
|
||||
// Protected; transforms widget's data object to the format used by the
|
||||
|
@ -175,8 +178,8 @@
|
|||
// Register the "editdrupalimage" command, which essentially just replaces
|
||||
// the "image" command's CKEditor dialog with a Drupal-native dialog.
|
||||
editor.addCommand('editdrupalimage', {
|
||||
allowedContent: 'img[alt,!src,width,height,!data-editor-file-uuid]',
|
||||
requiredContent: 'img[alt,src,width,height,data-editor-file-uuid]',
|
||||
allowedContent: 'img[alt,!src,width,height,!data-entity-type,!data-entity-uuid]',
|
||||
requiredContent: 'img[alt,src,width,height,data-entity-type,data-entity-uuid]',
|
||||
modes: { wysiwyg: 1 },
|
||||
canUndo: true,
|
||||
exec: function (editor, data) {
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
}, true);
|
||||
|
||||
// Override requiredContent & allowedContent.
|
||||
widgetDefinition.requiredContent = 'img[alt,src,width,height,data-editor-file-uuid,data-align,data-caption]';
|
||||
widgetDefinition.requiredContent = 'img[alt,src,width,height,data-entity-type,data-entity-uuid,data-align,data-caption]';
|
||||
widgetDefinition.allowedContent.img.attributes += ',data-align,data-caption';
|
||||
|
||||
// Override allowedContent setting for the 'caption' nested editable.
|
||||
|
@ -58,8 +58,8 @@
|
|||
widgetDefinition.editables.caption.allowedContent = 'a[!href]; em strong cite code br';
|
||||
|
||||
// Override downcast(): ensure we *only* output <img>, but also ensure
|
||||
// we include the data-editor-file-uuid, data-align and data-caption
|
||||
// attributes.
|
||||
// we include the data-entity-type, data-entity-uuid, data-align and
|
||||
// data-caption attributes.
|
||||
widgetDefinition.downcast = function (element) {
|
||||
// Find an image element in the one being downcasted (can be itself).
|
||||
var img = findElementByName(element, 'img');
|
||||
|
@ -79,7 +79,8 @@
|
|||
attrs['data-align'] = this.data.align;
|
||||
}
|
||||
}
|
||||
attrs['data-editor-file-uuid'] = this.data['data-editor-file-uuid'];
|
||||
attrs['data-entity-type'] = this.data['data-entity-type'];
|
||||
attrs['data-entity-uuid'] = this.data['data-entity-uuid'];
|
||||
|
||||
return img;
|
||||
};
|
||||
|
@ -91,7 +92,7 @@
|
|||
// - <figure> tag (captioned image).
|
||||
// We take the same attributes into account as downcast() does.
|
||||
widgetDefinition.upcast = function (element, data) {
|
||||
if (element.name !== 'img' || !element.attributes['data-editor-file-uuid']) {
|
||||
if (element.name !== 'img' || !element.attributes['data-entity-type'] || !element.attributes['data-entity-uuid']) {
|
||||
return;
|
||||
}
|
||||
// Don't initialize on pasted fake objects.
|
||||
|
@ -112,8 +113,10 @@
|
|||
data.align = attrs['data-align'];
|
||||
delete attrs['data-align'];
|
||||
}
|
||||
data['data-editor-file-uuid' ] = attrs['data-editor-file-uuid'];
|
||||
delete attrs['data-editor-file-uuid'];
|
||||
data['data-entity-type' ] = attrs['data-entity-type'];
|
||||
delete attrs['data-entity-type'];
|
||||
data['data-entity-uuid' ] = attrs['data-entity-uuid'];
|
||||
delete attrs['data-entity-uuid'];
|
||||
|
||||
if (captionFilterEnabled) {
|
||||
// Unwrap from <p> wrapper created by HTML parser for a captioned
|
||||
|
|
|
@ -441,7 +441,7 @@ function _editor_delete_file_usage(array $uuids, EntityInterface $entity, $count
|
|||
}
|
||||
|
||||
/**
|
||||
* Finds all files referenced (data-editor-file-uuid) by formatted text fields.
|
||||
* Finds all files referenced (data-entity-uuid) by formatted text fields.
|
||||
*
|
||||
* @param EntityInterface $entity
|
||||
* An entity whose fields to analyze.
|
||||
|
@ -482,7 +482,7 @@ function _editor_get_formatted_text_fields(FieldableEntityInterface $entity) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Parse an HTML snippet for any data-editor-file-uuid attributes.
|
||||
* Parse an HTML snippet for any linked file with data-entity-uuid attributes.
|
||||
*
|
||||
* @param string $text
|
||||
* The partial (X)HTML snippet to load. Invalid markup will be corrected on
|
||||
|
@ -495,8 +495,8 @@ function _editor_parse_file_uuids($text) {
|
|||
$dom = Html::load($text);
|
||||
$xpath = new \DOMXPath($dom);
|
||||
$uuids = array();
|
||||
foreach ($xpath->query('//*[@data-editor-file-uuid]') as $node) {
|
||||
$uuids[] = $node->getAttribute('data-editor-file-uuid');
|
||||
foreach ($xpath->query('//*[@data-entity-type="file" and @data-entity-uuid]') as $node) {
|
||||
$uuids[] = $node->getAttribute('data-entity-uuid');
|
||||
}
|
||||
return $uuids;
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ class EditorImageDialog extends FormBase {
|
|||
}
|
||||
$max_filesize = min(Bytes::toInt($image_upload['max_size']), file_upload_max_size());
|
||||
|
||||
$existing_file = isset($image_element['data-editor-file-uuid']) ? entity_load_by_uuid('file', $image_element['data-editor-file-uuid']) : NULL;
|
||||
$existing_file = isset($image_element['data-entity-uuid']) ? entity_load_by_uuid('file', $image_element['data-entity-uuid']) : NULL;
|
||||
$fid = $existing_file ? $existing_file->id() : NULL;
|
||||
|
||||
$form['fid'] = array(
|
||||
|
@ -204,8 +204,8 @@ class EditorImageDialog extends FormBase {
|
|||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$response = new AjaxResponse();
|
||||
|
||||
// Convert any uploaded files from the FID values to data-editor-file-uuid
|
||||
// attributes.
|
||||
// Convert any uploaded files from the FID values to data-entity-uuid
|
||||
// attributes and set data-entity-type to 'file'.
|
||||
$fid = $form_state->getValue(array('fid', 0));
|
||||
if (!empty($fid)) {
|
||||
$file = file_load($fid);
|
||||
|
@ -214,7 +214,8 @@ class EditorImageDialog extends FormBase {
|
|||
// on multisite set-ups and prevent mixed content errors.
|
||||
$file_url = file_url_transform_relative($file_url);
|
||||
$form_state->setValue(array('attributes', 'src'), $file_url);
|
||||
$form_state->setValue(array('attributes', 'data-editor-file-uuid'), $file->uuid());
|
||||
$form_state->setValue(array('attributes', 'data-entity-uuid'), $file->uuid());
|
||||
$form_state->setValue(array('attributes', 'data-entity-type'), 'file');
|
||||
}
|
||||
|
||||
// When the alt attribute is set to two double quotes, transform it to the
|
||||
|
|
|
@ -70,12 +70,12 @@ class EditorFileReference extends FilterBase implements ContainerFactoryPluginIn
|
|||
public function process($text, $langcode) {
|
||||
$result = new FilterProcessResult($text);
|
||||
|
||||
if (stristr($text, 'data-editor-file-uuid') !== FALSE) {
|
||||
if (stristr($text, 'data-entity-type="file"') !== FALSE) {
|
||||
$dom = Html::load($text);
|
||||
$xpath = new \DOMXPath($dom);
|
||||
$processed_uuids = array();
|
||||
foreach ($xpath->query('//*[@data-editor-file-uuid]') as $node) {
|
||||
$uuid = $node->getAttribute('data-editor-file-uuid');
|
||||
foreach ($xpath->query('//*[@data-entity-type="file" and @data-entity-uuid]') as $node) {
|
||||
$uuid = $node->getAttribute('data-entity-uuid');
|
||||
// Only process the first occurrence of each file UUID.
|
||||
if (!isset($processed_uuids[$uuid])) {
|
||||
$processed_uuids[$uuid] = TRUE;
|
||||
|
|
|
@ -68,45 +68,50 @@ class EditorFileReferenceFilterTest extends KernelTestBase {
|
|||
$uuid_2 = $image_2->uuid();
|
||||
$cache_tag_2 = ['file:' . $id_2];
|
||||
|
||||
$this->pass('No data-editor-file-uuid attribute.');
|
||||
$this->pass('No data-entity-type and no data-entity-uuid attribute.');
|
||||
$input = '<img src="llama.jpg" />';
|
||||
$output = $test($input);
|
||||
$this->assertIdentical($input, $output->getProcessedText());
|
||||
|
||||
$this->pass('One data-editor-file-uuid attribute.');
|
||||
$input = '<img src="llama.jpg" data-editor-file-uuid="' . $uuid . '" />';
|
||||
$this->pass('A non-file data-entity-type attribute value.');
|
||||
$input = '<img src="llama.jpg" data-entity-type="invalid-entity-type-value" data-entity-uuid="' . $uuid . '" />';
|
||||
$output = $test($input);
|
||||
$this->assertIdentical($input, $output->getProcessedText());
|
||||
|
||||
$this->pass('One data-entity-uuid attribute.');
|
||||
$input = '<img src="llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
|
||||
$output = $test($input);
|
||||
$this->assertIdentical($input, $output->getProcessedText());
|
||||
$this->assertEqual($cache_tag, $output->getCacheTags());
|
||||
|
||||
$this->pass('One data-editor-file-uuid attribute with odd capitalization.');
|
||||
$input = '<img src="llama.jpg" DATA-editor-file-UUID = "' . $uuid . '" />';
|
||||
$this->pass('One data-entity-uuid attribute with odd capitalization.');
|
||||
$input = '<img src="llama.jpg" data-entity-type="file" DATA-entity-UUID = "' . $uuid . '" />';
|
||||
$output = $test($input);
|
||||
$this->assertIdentical($input, $output->getProcessedText());
|
||||
$this->assertEqual($cache_tag, $output->getCacheTags());
|
||||
|
||||
$this->pass('One data-editor-file-uuid attribute on a non-image tag.');
|
||||
$input = '<video src="llama.jpg" data-editor-file-uuid="' . $uuid . '" />';
|
||||
$this->pass('One data-entity-uuid attribute on a non-image tag.');
|
||||
$input = '<video src="llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
|
||||
$output = $test($input);
|
||||
$this->assertIdentical($input, $output->getProcessedText());
|
||||
$this->assertEqual($cache_tag, $output->getCacheTags());
|
||||
|
||||
$this->pass('One data-editor-file-uuid attribute with an invalid value.');
|
||||
$input = '<img src="llama.jpg" data-editor-file-uuid="invalid-' . $uuid . '" />';
|
||||
$this->pass('One data-entity-uuid attribute with an invalid value.');
|
||||
$input = '<img src="llama.jpg" data-entity-type="file" data-entity-uuid="invalid-' . $uuid . '" />';
|
||||
$output = $test($input);
|
||||
$this->assertIdentical($input, $output->getProcessedText());
|
||||
$this->assertEqual(array(), $output->getCacheTags());
|
||||
|
||||
$this->pass('Two different data-editor-file-uuid attributes.');
|
||||
$input = '<img src="llama.jpg" data-editor-file-uuid="' . $uuid . '" />';
|
||||
$input .= '<img src="alpaca.jpg" data-editor-file-uuid="' . $uuid_2 . '" />';
|
||||
$this->pass('Two different data-entity-uuid attributes.');
|
||||
$input = '<img src="llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
|
||||
$input .= '<img src="alpaca.jpg" data-entity-type="file" data-entity-uuid="' . $uuid_2 . '" />';
|
||||
$output = $test($input);
|
||||
$this->assertIdentical($input, $output->getProcessedText());
|
||||
$this->assertEqual(Cache::mergeTags($cache_tag, $cache_tag_2), $output->getCacheTags());
|
||||
|
||||
$this->pass('Two identical data-editor-file-uuid attributes.');
|
||||
$input = '<img src="llama.jpg" data-editor-file-uuid="' . $uuid . '" />';
|
||||
$input .= '<img src="llama.jpg" data-editor-file-uuid="' . $uuid . '" />';
|
||||
$this->pass('Two identical data-entity-uuid attributes.');
|
||||
$input = '<img src="llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
|
||||
$input .= '<img src="llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
|
||||
$output = $test($input);
|
||||
$this->assertIdentical($input, $output->getProcessedText());
|
||||
$this->assertEqual($cache_tag, $output->getCacheTags());
|
||||
|
|
|
@ -62,11 +62,13 @@ class EditorFileUsageTest extends EntityUnitTestBase {
|
|||
$file_usage = $this->container->get('file.usage');
|
||||
$this->assertIdentical(array(), $file_usage->listUsage($image), 'The image has zero usages.');
|
||||
|
||||
$body_value = '<p>Hello, world!</p><img src="awesome-llama.jpg" data-editor-file-uuid="' . $image->uuid() . '" />';
|
||||
// Test handling of an invalid data- attribute.
|
||||
$body_value .= '<img src="awesome-llama.jpg" data-editor-file-uuid="invalid-editor-file-uuid-value" />';
|
||||
$body_value = '<p>Hello, world!</p><img src="awesome-llama.jpg" data-entity-type="file" data-entity-uuid="' . $image->uuid() . '" />';
|
||||
// Test handling of an invalid data-entity-uuid attribute.
|
||||
$body_value .= '<img src="awesome-llama.jpg" data-entity-type="file" data-entity-uuid="invalid-entity-uuid-value" />';
|
||||
// Test handling of an invalid data-entity-type attribute.
|
||||
$body_value .= '<img src="awesome-llama.jpg" data-entity-type="invalid-entity-type-value" data-entity-uuid="' . $image->uuid() . '" />';
|
||||
// Test handling of a non-existing UUID.
|
||||
$body_value .= '<img src="awesome-llama.jpg" data-editor-file-uuid="30aac704-ba2c-40fc-b609-9ed121aa90f4" />';
|
||||
$body_value .= '<img src="awesome-llama.jpg" data-entity-type="file" data-entity-uuid="30aac704-ba2c-40fc-b609-9ed121aa90f4" />';
|
||||
// Test editor_entity_insert(): increment.
|
||||
$this->createUser();
|
||||
$node = entity_create('node', array(
|
||||
|
@ -90,16 +92,31 @@ class EditorFileUsageTest extends EntityUnitTestBase {
|
|||
$this->assertIdentical(array('editor' => array('node' => array(1 => '3'))), $file_usage->listUsage($image), 'The image has 3 usages.');
|
||||
|
||||
// Test hook_entity_update(): decrement, by modifying the last revision:
|
||||
// remove the data- attribute from the body field.
|
||||
// remove the data-entity-type attribute from the body field.
|
||||
$body = $node->get('body')->first()->get('value');
|
||||
$original_value = $body->getValue();
|
||||
$new_value = str_replace('data-editor-file-uuid', 'data-editor-file-uuid-modified', $original_value);
|
||||
$new_value = str_replace('data-entity-type', 'data-entity-type-modified', $original_value);
|
||||
$body->setValue($new_value);
|
||||
$node->save();
|
||||
$this->assertIdentical(array('editor' => array('node' => array(1 => '2'))), $file_usage->listUsage($image), 'The image has 2 usages.');
|
||||
|
||||
// Test editor_entity_update(): increment again by creating a new revision:
|
||||
// read the data- attributes to the body field.
|
||||
$node->setNewRevision(TRUE);
|
||||
$node->get('body')->first()->get('value')->setValue($original_value);
|
||||
$node->save();
|
||||
$this->assertIdentical(array('editor' => array('node' => array(1 => '3'))), $file_usage->listUsage($image), 'The image has 3 usages.');
|
||||
|
||||
// Test hook_entity_update(): decrement, by modifying the last revision:
|
||||
// remove the data-entity-uuid attribute from the body field.
|
||||
$body = $node->get('body')->first()->get('value');
|
||||
$new_value = str_replace('data-entity-uuid', 'data-entity-uuid-modified', $original_value);
|
||||
$body->setValue($new_value);
|
||||
$node->save();
|
||||
$this->assertIdentical(array('editor' => array('node' => array(1 => '2'))), $file_usage->listUsage($image), 'The image has 2 usages.');
|
||||
|
||||
// Test hook_entity_update(): increment, by modifying the last revision:
|
||||
// readd the data- attribute to the body field.
|
||||
// read the data- attributes to the body field.
|
||||
$node->get('body')->first()->get('value')->setValue($original_value);
|
||||
$node->save();
|
||||
$this->assertIdentical(array('editor' => array('node' => array(1 => '3'))), $file_usage->listUsage($image), 'The image has 3 usages.');
|
||||
|
|
Loading…
Reference in New Issue