diff --git a/core/modules/editor/src/Form/EditorImageDialog.php b/core/modules/editor/src/Form/EditorImageDialog.php
index c353e2d..d8407d7 100644
--- a/core/modules/editor/src/Form/EditorImageDialog.php
+++ b/core/modules/editor/src/Form/EditorImageDialog.php
@@ -96,26 +96,26 @@ public function buildForm(array $form, FormStateInterface $form_state, FilterFor
$existing_file = isset($image_element['data-entity-uuid']) ? \Drupal::entityManager()->loadEntityByUuid('file', $image_element['data-entity-uuid']) : NULL;
$fid = $existing_file ? $existing_file->id() : NULL;
- $form['fid'] = array(
+ $form['fid'] = [
'#title' => $this->t('Image'),
'#type' => 'managed_file',
'#upload_location' => $image_upload['scheme'] . '://' . $image_upload['directory'],
- '#default_value' => $fid ? array($fid) : NULL,
- '#upload_validators' => array(
- 'file_validate_extensions' => array('gif png jpg jpeg'),
- 'file_validate_size' => array($max_filesize),
- 'file_validate_image_resolution' => array($max_dimensions),
- ),
+ '#default_value' => $fid ? [$fid] : NULL,
+ '#upload_validators' => [
+ 'file_validate_extensions' => ['gif png jpg jpeg'],
+ 'file_validate_size' => [$max_filesize],
+ 'file_validate_image_resolution' => [$max_dimensions],
+ ],
'#required' => TRUE,
- );
+ ];
- $form['attributes']['src'] = array(
+ $form['attributes']['src'] = [
'#title' => $this->t('URL'),
'#type' => 'textfield',
'#default_value' => isset($image_element['src']) ? $image_element['src'] : '',
'#maxlength' => 2048,
'#required' => TRUE,
- );
+ ];
// If the editor has image uploads enabled, show a managed_file form item,
// otherwise show a (file URL) text form item.
@@ -139,7 +139,7 @@ public function buildForm(array $form, FormStateInterface $form_state, FilterFor
if ($alt === '' && !empty($image_element['src'])) {
$alt = '""';
}
- $form['attributes']['alt'] = array(
+ $form['attributes']['alt'] = [
'#title' => $this->t('Alternative text'),
'#placeholder' => $this->t('Short description for the visually impaired'),
'#type' => 'textfield',
@@ -147,51 +147,51 @@ public function buildForm(array $form, FormStateInterface $form_state, FilterFor
'#required_error' => $this->t('Alternative text is required.
(Only in rare cases should this be left empty. To create empty alternative text, enter ""
— two double quotes without any content).'),
'#default_value' => $alt,
'#maxlength' => 2048,
- );
+ ];
// When Drupal core's filter_align is being used, the text editor may
// offer the ability to change the alignment.
if (isset($image_element['data-align']) && $filter_format->filters('filter_align')->status) {
- $form['align'] = array(
+ $form['align'] = [
'#title' => $this->t('Align'),
'#type' => 'radios',
- '#options' => array(
+ '#options' => [
'none' => $this->t('None'),
'left' => $this->t('Left'),
'center' => $this->t('Center'),
'right' => $this->t('Right'),
- ),
+ ],
'#default_value' => $image_element['data-align'] === '' ? 'none' : $image_element['data-align'],
- '#wrapper_attributes' => array('class' => array('container-inline')),
- '#attributes' => array('class' => array('container-inline')),
- '#parents' => array('attributes', 'data-align'),
- );
+ '#wrapper_attributes' => ['class' => ['container-inline']],
+ '#attributes' => ['class' => ['container-inline']],
+ '#parents' => ['attributes', 'data-align'],
+ ];
}
// When Drupal core's filter_caption is being used, the text editor may
// offer the ability to in-place edit the image's caption: show a toggle.
if (isset($image_element['hasCaption']) && $filter_format->filters('filter_caption')->status) {
- $form['caption'] = array(
+ $form['caption'] = [
'#title' => $this->t('Caption'),
'#type' => 'checkbox',
'#default_value' => $image_element['hasCaption'] === 'true',
- '#parents' => array('attributes', 'hasCaption'),
- );
+ '#parents' => ['attributes', 'hasCaption'],
+ ];
}
- $form['actions'] = array(
+ $form['actions'] = [
'#type' => 'actions',
- );
- $form['actions']['save_modal'] = array(
+ ];
+ $form['actions']['save_modal'] = [
'#type' => 'submit',
'#value' => $this->t('Save'),
// No regular submit-handler. This form only works via JavaScript.
- '#submit' => array(),
- '#ajax' => array(
+ '#submit' => [],
+ '#ajax' => [
'callback' => '::submitForm',
'event' => 'click',
- ),
- );
+ ],
+ ];
return $form;
}
@@ -204,22 +204,24 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
// 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));
+ $fid = $form_state->getValue(['fid', 0]);
if (!empty($fid)) {
$file = $this->fileStorage->load($fid);
$file_url = file_create_url($file->getFileUri());
// Transform absolute image URLs to relative image URLs: prevent problems
// 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-entity-uuid'), $file->uuid());
- $form_state->setValue(array('attributes', 'data-entity-type'), 'file');
+ if (!$form_state->getValue(['attributes', 'src'])) {
+ $form_state->setValue(['attributes', 'src'], $file_url);
+ }
+ $form_state->setValue(['attributes', 'data-entity-uuid'], $file->uuid());
+ $form_state->setValue(['attributes', 'data-entity-type'], 'file');
}
// When the alt attribute is set to two double quotes, transform it to the
// empty string: two double quotes signify "empty alt attribute". See above.
- if (trim($form_state->getValue(array('attributes', 'alt'))) === '""') {
- $form_state->setValue(array('attributes', 'alt'), '');
+ if (trim($form_state->getValue(['attributes', 'alt'])) === '""') {
+ $form_state->setValue(['attributes', 'alt'], '');
}
if ($form_state->getErrors()) {
diff --git a/core/modules/image/css/image.admin.css b/core/modules/image/css/image.admin.css
index 9f9878a..2983d74 100644
--- a/core/modules/image/css/image.admin.css
+++ b/core/modules/image/css/image.admin.css
@@ -13,9 +13,15 @@
top: 50%;
width: 48%;
}
+
+.image-style-preview .preview-image-wrapper div {
+ display: block;
+}
+
.image-style-preview .preview-image {
- margin: auto;
+ margin: 5px auto auto;
position: relative;
+ display: block;
}
.image-style-preview .preview-image .width {
border: 1px solid #666;
diff --git a/core/modules/image/image.install b/core/modules/image/image.install
index fd14d03..7e82f62 100644
--- a/core/modules/image/image.install
+++ b/core/modules/image/image.install
@@ -61,3 +61,25 @@ function image_requirements($phase) {
return $requirements;
}
+
+/**
+ * Add allowed attributes to existing html filters.
+ */
+function image_update_8001() {
+ $config_factory = \Drupal::configFactory();
+ foreach ($config_factory->listAll('filter.format') as $name) {
+ $config = $config_factory->getEditable($name);
+ if (!$config->get('filters.image_style.status')) {
+ continue;
+ }
+ $allowed_html = $config->get('filters.filter_html.settings.allowed_html');
+ $matches = [];
+ if (!empty($allowed_html) && preg_match('/]*)>/', $allowed_html, $matches)) {
+ $new_attributes = array_filter(explode(' ', $matches[1]));
+ $new_attributes[] = 'data-image-style';
+ $allowed_html = preg_replace('/]*)>/', '', $allowed_html);
+ $config->set('filters.filter_html.settings.allowed_html', $allowed_html);
+ $config->save();
+ }
+ }
+}
diff --git a/core/modules/image/image.module b/core/modules/image/image.module
index 7f8314a..7ec9c3f 100644
--- a/core/modules/image/image.module
+++ b/core/modules/image/image.module
@@ -5,12 +5,19 @@
* Exposes global functionality for creating image styles.
*/
+use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Entity\EntityInterface;
+use Drupal\filter\Entity\FilterFormat;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Image\ImageInterface;
+use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\file\Entity\File;
+use Drupal\file\FileInterface;
use Drupal\field\FieldStorageConfigInterface;
use Drupal\field\FieldConfigInterface;
use Drupal\image\Entity\ImageStyle;
+use Drupal\image\ImageStyleInterface;
/**
* Image style constant for user presets in the database.
@@ -61,20 +68,20 @@
function image_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.image':
- $field_ui_url = \Drupal::moduleHandler()->moduleExists('field_ui') ? \Drupal::url('help.page', array('name' => 'field_ui')) : '#';
+ $field_ui_url = \Drupal::moduleHandler()->moduleExists('field_ui') ? \Drupal::url('help.page', ['name' => 'field_ui']) : '#';
$output = '';
$output .= '
' . t('The Image module allows you to create fields that contain image files and to configure Image styles that can be used to manipulate the display of images. See the Field module help and the Field UI help pages for terminology and general information on entities, fields, and how to create and manage fields. For more information, see the online documentation for the Image module.', array(':image_styles' => \Drupal::url('entity.image_style.collection'), ':field' => \Drupal::url('help.page', array('name' => 'field')), ':field_ui' => $field_ui_url, ':image_documentation' => 'https://www.drupal.org/documentation/modules/image')) . '
'; + $output .= '' . t('The Image module allows you to create fields that contain image files and to configure Image styles that can be used to manipulate the display of images. See the Field module help and the Field UI help pages for terminology and general information on entities, fields, and how to create and manage fields. For more information, see the online documentation for the Image module.', [':image_styles' => \Drupal::url('entity.image_style.collection'), ':field' => \Drupal::url('help.page', ['name' => 'field']), ':field_ui' => $field_ui_url, ':image_documentation' => 'https://www.drupal.org/documentation/modules/image']) . '
'; $output .= '' . implode('
, ', $image_styles) . '
';
+ return t('
+ You can display images using site-wide styles by adding a data-image-style
attribute, whose values is one of the image style machine names: !image-style-machine-name-list.
' . $original_img . '
'; + + $expected_src = 'styles/medium/public/inline-images/image.png'; + $expected_width = '200'; + $expected_height = '150'; + + $expected_img = ''; + $expected_text = '' . $expected_img . '
'; + + $this->filterImageStyle + ->method('loadImageStyles') + ->willReturn([ + 'thumbnail', + 'medium', + 'large' + ]); + + $this->filterImageStyle + ->method('getImageStyleHtml') + ->with( + $this->equalTo($original_uuid), + $this->equalTo($original_image_style), + $this->anything() + ) + ->willReturn($expected_img); + + $output = $this->filterImageStyle->process($original_text, 'en'); + $this->assertEquals($expected_text, $output); + } +} diff --git a/core/modules/responsive_image/js/plugins/drupalresponsiveimagestyle/plugin.js b/core/modules/responsive_image/js/plugins/drupalresponsiveimagestyle/plugin.js new file mode 100644 index 0000000..0f3bba3 --- /dev/null +++ b/core/modules/responsive_image/js/plugins/drupalresponsiveimagestyle/plugin.js @@ -0,0 +1,112 @@ +/** + * @file + * Drupal Responsive Image Style plugin. + * + * This alters the existing CKEditor image2 widget plugin, which is already + * altered by the Drupal Image plugin, to data-responsive-image-style attribute + * to be set. + * + * @ignore + */ + +(function (CKEDITOR) { + + 'use strict'; + + CKEDITOR.plugins.add('drupalresponsiveimagestyle', { + requires: 'drupalimage', + + beforeInit: function (editor) { + // Override the image2 widget definition to handle the additional + // data-responsive-image-style attributes. + editor.on('widgetDefinition', function (event) { + var widgetDefinition = event.data; + if (widgetDefinition.name !== 'image') { + return; + } + // Override default features definitions for drupalresponsiveimagestyle. + CKEDITOR.tools.extend(widgetDefinition.features, { + responsiveimage: { + requiredContent: 'img[data-responsive-image-style]' + } + }, true); + + // Override requiredContent & allowedContent. + var requiredContent = widgetDefinition.requiredContent.getDefinition(); + requiredContent.attributes['data-responsive-image-style'] = ''; + widgetDefinition.requiredContent = new CKEDITOR.style(requiredContent); + widgetDefinition.allowedContent.img.attributes += ',!data-responsive-image-style'; + + // Override downcast(). + var originalDowncast = widgetDefinition.downcast; + widgetDefinition.downcast = function (element) { + var img = originalDowncast.call(this, element); + if (!img) { + img = findElementByName(element, 'img'); + } + img.attributes['data-responsive-image-style'] = this.data['data-responsive-image-style']; + return img; + }; + + // Override upcast(). + var originalUpcast = widgetDefinition.upcast; + widgetDefinition.upcast = function (element, data) { + if (element.name !== 'img' || !element.attributes['data-entity-type'] || !element.attributes['data-entity-uuid']) { + return; + } + // Don't initialize on pasted fake objects. + else if (element.attributes['data-cke-realelement']) { + return; + } + + // Parse the data-responsive-image-style attribute. + data['data-responsive-image-style'] = element.attributes['data-responsive-image-style']; + + // Upcast after parsing so correct element attributes are parsed. + element = originalUpcast.call(this, element, data); + + return element; + }; + + // Protected; keys of the widget data to be sent to the Drupal dialog. + // Append to the values defined by the drupalimage plugin. + // @see core/modules/ckeditor/js/plugins/drupalimage/plugin.js + CKEDITOR.tools.extend(widgetDefinition._mapDataToDialog, { + 'data-responsive-image-style': 'data-responsive-image-style' + }); + // Low priority to ensure drupalimage's event handler runs first. + }, null, null, 20); + } + }); + + /** + * Finds an element by its name. + * + * Function will check first the passed element itself and then all its + * children in DFS order. + * + * @param {CKEDITOR.htmlParser.element} element + * The element to search. + * @param {string} name + * The element name to search for. + * + * @return {?CKEDITOR.htmlParser.element} + * The found element, or null. + */ + function findElementByName(element, name) { + if (element.name === name) { + return element; + } + + var found = null; + element.forEach(function (el) { + if (el.name === name) { + found = el; + // Stop here. + return false; + } + }, CKEDITOR.NODE_ELEMENT); + return found; + } + +})(CKEDITOR); diff --git a/core/modules/responsive_image/responsive_image.install b/core/modules/responsive_image/responsive_image.install new file mode 100644 index 0000000..062c45c --- /dev/null +++ b/core/modules/responsive_image/responsive_image.install @@ -0,0 +1,27 @@ +listAll('filter.format') as $name) { + $config = $config_factory->getEditable($name); + if (!$config->get('filters.responsive_image_style.status')) { + continue; + } + $allowed_html = $config->get('filters.filter_html.settings.allowed_html'); + $matches = []; + if (!empty($allowed_html) && preg_match('/]*)>/', $allowed_html, $matches)) { + $new_attributes = array_filter(explode(' ', $matches[1])); + $new_attributes[] = 'data-responsive-image-style'; + $allowed_html = preg_replace('/]*)>/', '', $allowed_html); + $config->set('filters.filter_html.settings.allowed_html', $allowed_html); + $config->save(); + } + } +} diff --git a/core/modules/responsive_image/responsive_image.module b/core/modules/responsive_image/responsive_image.module index 89ac37f..e213c4d 100644 --- a/core/modules/responsive_image/responsive_image.module +++ b/core/modules/responsive_image/responsive_image.module @@ -5,14 +5,20 @@ * Responsive image display formatter for image fields. */ +use Drupal\Component\Utility\Unicode; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Image\ImageInterface; use Drupal\Core\Template\Attribute; use Drupal\Core\Logger\RfcLogLevel; -use Drupal\Component\Utility\Unicode; +use Drupal\Core\Render\RendererInterface; use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\breakpoint\BreakpointInterface; +use Drupal\filter\Entity\FilterFormat; +use Drupal\file\Entity\File; +use Drupal\file\FileInterface; use Drupal\image\Entity\ImageStyle; use Drupal\responsive_image\Entity\ResponsiveImageStyle; -use Drupal\Core\Image\ImageInterface; -use Drupal\breakpoint\BreakpointInterface; +use Drupal\responsive_image\ResponsiveImageStyleInterface; /** * The machine name for the empty image breakpoint image style option. @@ -28,25 +34,26 @@ function responsive_image_help($route_name, RouteMatchInterface $route_match) { case 'help.page.responsive_image': $output = ''; $output .= '' . t('The Responsive Image module provides an image formatter that allows browsers to select which image file to display based on media queries or which image file types the browser supports, using the HTML 5 picture and source elements and/or the sizes, srcset and type attributes. For more information, see the online documentation for the Responsive Image module.', array( ':responsive_image' => 'https://www.drupal.org/documentation/modules/responsive_image')) . '
'; + $output .= '' . t('The Responsive Image module provides an image formatter that allows browsers to select which image file to display based on media queries or which image file types the browser supports, using the HTML 5 picture and source elements and/or the sizes, srcset and type attributes. For more information, see the online documentation for the Responsive Image module.', [':responsive_image' => 'https://www.drupal.org/documentation/modules/responsive_image']) . '
'; $output .= '