diff --git a/core/modules/editor/src/Form/EditorImageDialog.php b/core/modules/editor/src/Form/EditorImageDialog.php index 6f18546..816ba41 100644 --- a/core/modules/editor/src/Form/EditorImageDialog.php +++ b/core/modules/editor/src/Form/EditorImageDialog.php @@ -209,7 +209,9 @@ public function submitForm(array &$form, FormStateInterface $form_state) { // 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); + if (!$form_state->getValue(array('attributes', 'src'))) { + $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'); } 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..eb5f197 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.filter_image_style.status')) { + continue; + } + $allowed_html = $config->get('filters.filter_html.settings.allowed_html'); + $matches = array(); + 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..ee49d75 100644 --- a/core/modules/image/image.module +++ b/core/modules/image/image.module @@ -6,6 +6,7 @@ */ use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\file\Entity\File; use Drupal\field\FieldStorageConfigInterface; @@ -483,3 +484,144 @@ function image_field_config_delete(FieldConfigInterface $field) { \Drupal::service('file.usage')->delete($file, 'image', 'default_image', $field->uuid()); } } + +/** + * Implements hook_form_FORM_ID_alter() for EditorImageDialog. + * + * Alters the image dialog form for text editor, to allow the user to select an + * image style. + * + * @see \Drupal\editor\Form\EditorImageDialog::buildForm() + */ +function image_form_editor_image_dialog_alter(&$form, FormStateInterface $form_state) { + /** @var \Drupal\editor\Entity\Editor $editor */ + $editor = $form_state->getBuildInfo()['args'][0]; + $filters = $editor->getFilterFormat()->filters()->getAll(); + + // Alter the editor image dialog when image style functionality is available, + // unless the responsive image style filter is also in use. + if ((isset($filters['filter_image_style']) && $filters['filter_image_style']->status)) { + $filter_image_style_status = TRUE; + } + else { + $filter_image_style_status = FALSE; + } + + if ((isset($filters['filter_responsive_image_style']) && $filters['filter_responsive_image_style']->status)) { + $filter_responsive_image_style_status = TRUE; + } + else { + $filter_responsive_image_style_status = FALSE; + } + + if ($filter_image_style_status && !$filter_responsive_image_style_status) { + // Get the image from the form. + $image_element = $form_state->getStorage()['image_element']; + + // Disallow the user from specifying image dimensions manualy. + $form['dimensions']['#access'] = FALSE; + + // Get an array of image styles to present as options for selection. + $image_style_options = image_style_options(FALSE); + + // Create a form item for image style selection. + $form['image_style'] = array( + '#type' => 'item', + '#field_prefix' => '
', + '#field_suffix' => '
', + ); + + // Add a select element to choose an image style. + $form['image_style']['selection'] = array( + '#title' => t('Image style'), + '#type' => 'select', + '#default_value' => isset($image_element['data-image-style']) ? $image_element['data-image-style'] : array_keys($image_style_options)[0], + '#options' => $image_style_options, + '#required' => TRUE, + '#wrapper_attributes' => array('class' => array('container-inline')), + '#attributes' => array('class' => array('container-inline')), + '#parents' =>array('attributes', 'data-image-style'), + ); + + // Add a checkbox to toggle preview of the image style. + $form['image_style']['preview_toggle'] = array( + '#type' => 'checkbox', + '#title' => t('Show preview'), + ); + + // Create a set of image style previews that display if toggled on. + $image_styles = ImageStyle::loadMultiple(); + /** @var \Drupal\image\ImageStyleInterface $image_style */ + foreach ($image_styles as $image_style_id => $image_style) { + $preview_arguments = array( + '#theme' => 'image_style_preview', + '#style' => $image_style, + ); + $form['image_style']['preview_' . $image_style_id] = array( + '#type' => 'fieldset', + '#title' => t('Preview of %image-style image style', array('%image-style' => $image_style->label())), + '#markup' => \Drupal::service('renderer')->render($preview_arguments), + '#states' => array( + 'visible' => [ + ':input[name="image_style[preview_toggle]"]' => array('checked' => TRUE), + ':input[name="attributes[data-image-style]"]' => array('value' => $image_style_id), + ], + ), + ); + } + + // Attach the image admin library. + $form['#attached']['library'][] = 'image/admin'; + + // Validate that the image shown in the text editor matches the image style. + $form['actions']['save_modal']['#validate'][] = 'image_form_editor_image_dialog_validate'; + } +} + +/** + * Form validation handler for EditorImageDialog. + * + * Ensures the image shown in the text editor matches the chosen image style. + * + * @see \Drupal\editor\Form\EditorImageDialog::buildForm() + * @see \Drupal\editor\Form\EditorImageDialog::validateForm() + * @see image_form_editor_image_dialog_alter() + */ +function image_form_editor_image_dialog_validate(array &$form, FormStateInterface &$form_state) { + if (!empty($form_state->getValue('fid')[0])) { + + // Get the image style attributes. + $attributes = &$form_state->getValue('attributes'); + + /** @var \Drupal\image\ImageStyleInterface $image_style */ + $image_style = \Drupal::service('entity_type.manager')->getStorage('image_style')->load($attributes['data-image-style']); + + /** @var \Drupal\file\FileInterface $file */ + $file = File::load($form_state->getValue('fid')[0]); + + // Get the URI of the image from the file. + $uri = $file->getFileUri(); + + // Set the 'src' attribute to the image style URL. FilterImageStyle will + // look at the 'data-editor-file-uuid' attribute, not the 'src' attribute to + // render the appropriate output. + $attributes['src'] = $image_style->buildUrl($uri); + + /** @var \Drupal\Core\Image\ImageInterface $image */ + $image = \Drupal::service('image.factory')->get($uri); + + if ($image->isValid()) { + // Get the original width and height of the image. + $dimensions = array( + 'width' => $image->getWidth(), + 'height' => $image->getHeight() + ); + + // Transform the 'width' and 'height' dimensions of the image based on the + // image style. + $image_style->transformDimensions($dimensions, $attributes['src']); + $attributes['width'] = $dimensions['width']; + $attributes['height'] = $dimensions['height']; + } + } +} diff --git a/core/modules/image/js/plugins/drupalimagestyle/plugin.js b/core/modules/image/js/plugins/drupalimagestyle/plugin.js new file mode 100644 index 0000000..8de686f --- /dev/null +++ b/core/modules/image/js/plugins/drupalimagestyle/plugin.js @@ -0,0 +1,112 @@ +/** + * @file + * Drupal Image Style plugin. + * + * This alters the existing CKEditor image2 widget plugin, which is already + * altered by the Drupal Image plugin, to allow for the data-image-style + * attribute to be set. + * + * @ignore + */ + +(function (CKEDITOR) { + + 'use strict'; + + CKEDITOR.plugins.add('drupalimagestyle', { + requires: 'drupalimage', + + beforeInit: function (editor) { + // Override the image2 widget definition to handle the additional + // data-image-style attributes. + editor.on('widgetDefinition', function (event) { + var widgetDefinition = event.data; + if (widgetDefinition.name !== 'image') { + return; + } + // Override default features definitions for drupalimagestyle. + CKEDITOR.tools.extend(widgetDefinition.features, { + responsiveimage: { + requiredContent: 'img[data-image-style]' + } + }, true); + + // Override requiredContent & allowedContent. + var requiredContent = widgetDefinition.requiredContent.getDefinition(); + requiredContent.attributes['data-image-style'] = ''; + widgetDefinition.requiredContent = new CKEDITOR.style(requiredContent); + widgetDefinition.allowedContent.img.attributes += ',!data-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-image-style'] = this.data['data-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-image-style attribute. + data['data-image-style'] = element.attributes['data-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-image-style': 'data-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/image/src/Plugin/CKEditorPlugin/DrupalImageStyle.php b/core/modules/image/src/Plugin/CKEditorPlugin/DrupalImageStyle.php new file mode 100644 index 0000000..30ea87c --- /dev/null +++ b/core/modules/image/src/Plugin/CKEditorPlugin/DrupalImageStyle.php @@ -0,0 +1,86 @@ +hasAssociatedFilterFormat()) { + return FALSE; + } + + // Automatically enable this plugin if the text format associated with this + // text editor uses the filter_image_style filter and the DrupalImage button + // is enabled. + $format = $editor->getFilterFormat(); + if ($format->filters('filter_image_style')->status) { + $enabled = FALSE; + $settings = $editor->getSettings(); + foreach ($settings['toolbar']['rows'] as $row) { + foreach ($row as $group) { + foreach ($group['items'] as $button) { + if ($button === 'DrupalImage') { + $enabled = TRUE; + } + } + } + } + return $enabled; + } + + return FALSE; + } + +} diff --git a/core/modules/image/src/Plugin/Filter/FilterImageStyle.php b/core/modules/image/src/Plugin/Filter/FilterImageStyle.php new file mode 100644 index 0000000..8c6a008 --- /dev/null +++ b/core/modules/image/src/Plugin/Filter/FilterImageStyle.php @@ -0,0 +1,253 @@ +entityTypeManager = $entity_type_manager; + $this->entityRepository = $entity_repository; + $this->imageFactory = $image_factory; + $this->renderer = $renderer; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static($configuration, $plugin_id, $plugin_definition, $container->get('entity_type.manager'), $container->get('entity.repository'), $container->get('image.factory'), $container->get('renderer')); + } + + /** + * {@inheritdoc} + */ + public function process($text, $langcode) { + if (stristr($text, 'data-image-style') !== FALSE) { + // Load all the image styles so each img found in the text can be checked + // to ensure it has a valid image style. + $image_styles = $this->loadImageStyles(); + + // Load the text that is being processed into XML to find images. + $dom = HTML::load($text); + $xpath = new \DOMXPath($dom); + + // Process each img element DOM element found with the necessary + // attributes. + /** @var \DOMElement $dom_element */ + foreach ($xpath->query('//*[@data-entity-type="file" and @data-entity-uuid and @data-image-style]') as $dom_element) { + // Get the UUID and image style for the file. + $file_uuid = $dom_element->getAttribute('data-entity-uuid'); + $image_style_id = $dom_element->getAttribute('data-image-style'); + + // If the image style is not a valid one, then don't transform the HTML. + if (empty($file_uuid) || !in_array($image_style_id, $image_styles)) { + continue; + } + + // Transform the HTML for the img element by applying an image style. + $altered_img = $this->getImageStyleHtml($file_uuid, $image_style_id, $dom_element); + + // Load the altered HTML into a new DOMDocument and retrieve the element. + $updated_node = HTML::load($altered_img)->getElementsByTagName('body') + ->item(0) + ->childNodes + ->item(0); + + // Import the updated node from the new DOMDocument into the original + // one, importing also the child nodes of the updated node. + $updated_node = $dom->importNode($updated_node, TRUE); + + // Finally, replace the original image node with the new image node. + $dom_element->parentNode->replaceChild($updated_node, $dom_element); + } + + // Process the filter with the newly updated DOM. + return new FilterProcessResult(HTML::serialize($dom)); + } + + // Process the filter if no image style img elements are found. + return new FilterProcessResult($text); + } + + /** + * Loads the image styles. + * + * @return string[] + */ + protected function loadImageStyles() { + return array_keys($this->entityTypeManager->getStorage('image_style')->loadMultiple()); + } + + /** + * Get the the width and height of an image based on the file UUID. + * + * @param string $file_uuid + * The UUID for the file. + * + * @return array + * The image information. + */ + protected function getImageInfo($file_uuid) { + /** @var \Drupal\file\FileInterface $file; */ + $file = $this->entityRepository->loadEntityByUuid('file', $file_uuid); + + // Determine uri, width and height of the source image. + $image_uri = $image_width = $image_height = NULL; + $image = $this->imageFactory->get($file->getFileUri()); + if ($image->isValid()) { + $image_uri = $file->getFileUri(); + $image_width = $image->getWidth(); + $image_height = $image->getHeight(); + } + + return [ + 'uri' => $image_uri, + 'width' => $image_width, + 'height' => $image_height + ]; + } + + /** + * Removes attributes that will be generated from image style theme function. + * + * @param \DOMElement $dom_element + * The DOM element for the img element. + * + * @return array + * The attributes array. + */ + protected function prepareImageAttributes(\DOMElement $dom_element) { + // Remove attributes that are no longer needed. + $dom_element->removeAttribute('data-entity-type'); + $dom_element->removeAttribute('data-entity-uuid'); + $dom_element->removeAttribute('data-image-style'); + + // Remove attributes that are generated by the image style. + $dom_element->removeAttribute('width'); + $dom_element->removeAttribute('height'); + $dom_element->removeAttribute('src'); + + // Make sure all non-regenerated attributes are retained. + $attributes = []; + for ($i = 0; $i < $dom_element->attributes->length; $i++) { + $attr = $dom_element->attributes->item($i); + $attributes[$attr->name] = $attr->value; + } + + return $attributes; + } + + /** + * Get the HTML for the img element after image style is applied. + * + * @param string $file_uuid + * The UUID for the file. + * @param string $image_style_id + * The ID for the image style. + * @param \DOMElement $dom_element + * The DOM element for the image element. + * + * @return string + * The img element with the image style applied. + */ + protected function getImageStyleHtml($file_uuid, $image_style_id, \DOMElement $dom_element) { + $image_info = $this->getImageInfo($file_uuid); + + // Remove attributes that will be generated by the image style. + $attributes = $this->prepareImageAttributes($dom_element); + + // Re-render as an image style. + $image = [ + '#theme' => 'image_style', + '#style_name' => $image_style_id, + '#uri' => $image_info['uri'], + '#width' => $image_info['width'], + '#height' => $image_info['height'], + '#attributes' => $attributes, + ]; + + return $this->renderer->render($image); + } + + /** + * {@inheritdoc} + */ + public function tips($long = FALSE) { + if ($long) { + $image_styles = $this->loadImageStyles(); + $list = '' . 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.

', ['!image-style-machine-name-list' => $list]); + } + else { + return t('You can display images using site-wide styles by adding a data-image-style attribute.'); + } + } +} diff --git a/core/modules/image/templates/image-style-preview.html.twig b/core/modules/image/templates/image-style-preview.html.twig index d6e715c..64f87aa 100644 --- a/core/modules/image/templates/image-style-preview.html.twig +++ b/core/modules/image/templates/image-style-preview.html.twig @@ -33,7 +33,7 @@
{# Preview of the original image. #}
- {{ 'original'|t }} ({{ 'view actual size'|t }}) + {{ 'original'|t }}
({{ 'view actual size'|t }})
{{ original.rendered }} @@ -45,7 +45,7 @@ {# Derivative of the image style. #}
- {{ style_name }} ({{ 'view actual size'|t }}) + {{ style_name }}
({{ 'view actual size'|t }})
{{ derivative.rendered }} diff --git a/core/modules/image/tests/src/Kernel/EditorImageStyleDialogTest.php b/core/modules/image/tests/src/Kernel/EditorImageStyleDialogTest.php new file mode 100644 index 0000000..708ad5c --- /dev/null +++ b/core/modules/image/tests/src/Kernel/EditorImageStyleDialogTest.php @@ -0,0 +1,141 @@ +installEntitySchema('file'); + $this->installSchema('system', ['key_value_expire']); + $this->installSchema('node', ['node_access']); + $this->installSchema('file', ['file_usage']); + $this->installConfig(['node']); + + // Add text formats. + $format = FilterFormat::create([ + 'format' => 'filtered_html', + 'name' => 'Filtered HTML', + 'weight' => 0, + 'filters' => [ + 'filter_image_style' => ['status' => TRUE] + ], + ]); + $format->save(); + + // Set up text editor. + $this->editor = Editor::create([ + 'format' => 'filtered_html', + 'editor' => 'ckeditor', + 'image_upload' => [ + 'max_size' => 100, + 'scheme' => 'public', + 'directory' => '', + 'status' => TRUE, + ], + ]); + $this->editor->save(); + + // Install the image module config so we have the medium image style. + $this->installConfig('image'); + + // Create a node type for testing. + $type = NodeType::create(['type' => 'page', 'name' => 'page']); + $type->save(); + node_add_body_field($type); + $this->installEntitySchema('user'); + \Drupal::service('router.builder')->rebuild(); + } + + /** + * Tests that editor image dialog works as expected. + */ + public function testEditorImageStyleDialog() { + $input = [ + 'editor_object' => [ + 'src' => '/core/modules/image/sample.png', + 'alt' => 'Balloons floating above a field.', + 'width' => '800', + 'height' => '600', + 'data-entity-type' => 'file', + 'data-entity-uuid' => 'some-uuid', + 'data-image-style' => 'medium', + ], + 'dialogOptions' => [ + 'title' => 'Edit Image', + 'dialogClass' => 'editor-image-dialog', + 'autoResize' => 'true', + ], + '_drupal_ajax' => '1', + 'ajax_page_state' => [ + 'theme' => 'bartik', + 'theme_token' => 'some-token', + 'libraries' => '', + ], + ]; + $form_state = (new FormState()) + ->setRequestMethod('POST') + ->setUserInput($input) + ->addBuildInfo('args', [$this->editor]); + + /** @var \Drupal\Core\Form\FormBuilderInterface $form_builder */ + $form_builder = $this->container->get('form_builder'); + $form_object = new EditorImageDialog(\Drupal::entityTypeManager()->getStorage('file')); + $form_id = $form_builder->getFormId($form_object, $form_state); + $form = []; + + /** @var \Drupal\Core\Render\RendererInterface $renderer */ + $renderer = \Drupal::service('renderer'); + $renderer->executeInRenderContext(new RenderContext(), function() use (&$form, $form_builder, $form_id, $form_state) { + $form = $form_builder->retrieveForm($form_id, $form_state); + $form_builder->prepareForm($form_id, $form, $form_state); + $form_builder->processForm($form_id, $form, $form_state); + }); + + // If image style is selected, image dimensions should not be available. + $this->assertFalse($form['dimensions']['#access']); + } + +} diff --git a/core/modules/image/tests/src/Unit/FilterImageStyleTest.php b/core/modules/image/tests/src/Unit/FilterImageStyleTest.php new file mode 100644 index 0000000..ad1b047 --- /dev/null +++ b/core/modules/image/tests/src/Unit/FilterImageStyleTest.php @@ -0,0 +1,106 @@ + 'image']; + $entityTypeManager = $this->prophesize(EntityTypeManager::class); + $entityRepository = $this->prophesize(EntityRepository::class); + $imageFactory = $this->prophesize(ImageFactory::class); + $renderer = $this->prophesize(Renderer::class); + + $this->filterImageStyle = $this->getMockBuilder('Drupal\image\Plugin\Filter\FilterImageStyle') + ->setConstructorArgs([ + $configuration, + $plugin_id, + $plugin_definition, + $entityTypeManager->reveal(), + $entityRepository->reveal(), + $imageFactory->reveal(), + $renderer->reveal() + ]) + ->setMethods([ + 'loadImageStyles', + 'getImageStyleHtml' + ]) + ->getMock(); + } + + public function testProcessWithoutImage() { + $output = $this->filterImageStyle->process('', 'en'); + $this->assertEquals('', $output); + } + + /** + * @covers ::process + */ + public function testProcessWithImage() { + $original_src = 'image.png'; + $original_uuid = 'abcd-1234-ghij-5678'; + $original_image_style = 'medium'; + $original_width = '400'; + $original_height = '300'; + $original_alt = 'A wooly mammoth trumpets as a crevasse breaks open in the glacier.'; + + $original_img = '' . $original_alt .''; + $original_text = '

' . $original_img . '

'; + + $expected_src = 'styles/medium/public/inline-images/image.png'; + $expected_width = '200'; + $expected_height = '150'; + + $expected_img = '' . $original_alt .''; + $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/system/css/components/container-inline.module.css b/core/modules/system/css/components/container-inline.module.css index ba99a36..103e3e3 100644 --- a/core/modules/system/css/components/container-inline.module.css +++ b/core/modules/system/css/components/container-inline.module.css @@ -11,3 +11,7 @@ .container-inline .details-wrapper { display: block; } +/* Allow items inside inline items to render themselves as blocks. */ +.container-inline .container-block { + display: block; +} diff --git a/core/profiles/standard/config/install/filter.format.basic_html.yml b/core/profiles/standard/config/install/filter.format.basic_html.yml index 92224c2..1c38622 100644 --- a/core/profiles/standard/config/install/filter.format.basic_html.yml +++ b/core/profiles/standard/config/install/filter.format.basic_html.yml @@ -3,6 +3,7 @@ status: true dependencies: module: - editor + - image name: 'Basic HTML' format: basic_html weight: 0 @@ -15,7 +16,7 @@ filters: status: true weight: -10 settings: - allowed_html: '