Issue #2350327 by PieterDC, Dave Reid: editor.module should use the same data- attributes as entity_embed.module uses

8.0.x
Alex Pott 2014-11-25 10:38:54 +00:00
parent 99d219aa0e
commit 3c6f8b8da9
7 changed files with 82 additions and 53 deletions

View File

@ -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) {

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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;

View File

@ -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());

View File

@ -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.');