Commit 3c6f8b8d authored by alexpott's avatar alexpott
Browse files

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

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