diff --git a/core/modules/image/image.post_update.php b/core/modules/image/image.post_update.php new file mode 100644 index 0000000000000000000000000000000000000000..04d8c4b7b9e31fec1df3ea889fad363508fc4155 --- /dev/null +++ b/core/modules/image/image.post_update.php @@ -0,0 +1,22 @@ +<?php + +/** + * @file + * Post-update functions for Image. + */ + +use Drupal\Core\Entity\Entity\EntityViewDisplay; +use Drupal\Core\Entity\Entity\EntityFormDisplay; + +/** + * Saves the image style dependencies into form and view display entities. + */ +function image_post_update_image_style_dependencies() { + // Merge view and form displays. Use array_values() to avoid key collisions. + $displays = array_merge(array_values(EntityViewDisplay::loadMultiple()), array_values(EntityFormDisplay::loadMultiple())); + /** @var \Drupal\Core\Entity\Display\EntityDisplayInterface[] $displays */ + foreach ($displays as $display) { + // Re-save each config entity to add missed dependencies. + $display->save(); + } +} diff --git a/core/modules/image/src/Entity/ImageStyle.php b/core/modules/image/src/Entity/ImageStyle.php index f3d020785c99645d348f88fabed269903f5905c6..85f4cebe0fba8c29215f5be6e3c05b113ac3932a 100644 --- a/core/modules/image/src/Entity/ImageStyle.php +++ b/core/modules/image/src/Entity/ImageStyle.php @@ -36,6 +36,7 @@ * "flush" = "Drupal\image\Form\ImageStyleFlushForm" * }, * "list_builder" = "Drupal\image\ImageStyleListBuilder", + * "storage" = "Drupal\image\ImageStyleStorage", * }, * admin_permission = "administer image styles", * config_prefix = "style", @@ -58,13 +59,6 @@ */ class ImageStyle extends ConfigEntityBase implements ImageStyleInterface, EntityWithPluginCollectionInterface { - /** - * The name of the image style to use as replacement upon delete. - * - * @var string - */ - protected $replacementID; - /** * The name of the image style. * @@ -128,17 +122,13 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) { public static function postDelete(EntityStorageInterface $storage, array $entities) { parent::postDelete($storage, $entities); + /** @var \Drupal\image\ImageStyleInterface[] $entities */ foreach ($entities as $style) { // Flush cached media for the deleted style. $style->flush(); - // Check whether field settings need to be updated. - // In case no replacement style was specified, all image fields that are - // using the deleted style are left in a broken state. - if (!$style->isSyncing() && $new_id = $style->getReplacementID()) { - // The deleted ID is still set as originalID. - $style->setName($new_id); - static::replaceImageStyle($style); - } + // Clear the replacement ID, if one has been previously stored. + /** @var \Drupal\image\ImageStyleStorageInterface $storage */ + $storage->clearReplacementId($style->id()); } } @@ -380,7 +370,9 @@ public function addImageEffect(array $configuration) { * {@inheritdoc} */ public function getReplacementID() { - return $this->get('replacementID'); + /** @var \Drupal\image\ImageStyleStorageInterface $storage */ + $storage = $this->entityTypeManager()->getStorage($this->getEntityTypeId()); + return $storage->getReplacementId($this->id()); } /** diff --git a/core/modules/image/src/Form/ImageStyleDeleteForm.php b/core/modules/image/src/Form/ImageStyleDeleteForm.php index 5c45d940cad31635812d48720ce8b92f3b0a8261..0638369ac7abbfc94eac7364d21b8f9ec44875c2 100644 --- a/core/modules/image/src/Form/ImageStyleDeleteForm.php +++ b/core/modules/image/src/Form/ImageStyleDeleteForm.php @@ -15,6 +15,13 @@ */ class ImageStyleDeleteForm extends EntityDeleteForm { + /** + * Replacement options. + * + * @var array + */ + protected $replacementOptions; + /** * {@inheritdoc} */ @@ -25,20 +32,28 @@ public function getQuestion() { * {@inheritdoc} */ public function getDescription() { - return $this->t('If this style is in use on the site, you may select another style to replace it. All images that have been generated for this style will be permanently deleted.'); + if (count($this->getReplacementOptions()) > 1) { + return $this->t('If this style is in use on the site, you may select another style to replace it. All images that have been generated for this style will be permanently deleted. If no replacement style is selected, the dependent configurations might need manual reconfiguration.'); + } + return $this->t('All images that have been generated for this style will be permanently deleted. The dependent configurations might need manual reconfiguration.'); } /** * {@inheritdoc} */ public function form(array $form, FormStateInterface $form_state) { - $replacement_styles = array_diff_key(image_style_options(), array($this->entity->id() => '')); - $form['replacement'] = array( - '#title' => $this->t('Replacement style'), - '#type' => 'select', - '#options' => $replacement_styles, - '#empty_option' => $this->t('No replacement, just delete'), - ); + $replacement_styles = $this->getReplacementOptions(); + // If there are non-empty options in the list, allow the user to optionally + // pick up a replacement. + if (count($replacement_styles) > 1) { + $form['replacement'] = [ + '#type' => 'select', + '#title' => $this->t('Replacement style'), + '#options' => $replacement_styles, + '#empty_option' => $this->t('- No replacement -'), + '#weight' => -5, + ]; + } return parent::form($form, $form_state); } @@ -47,9 +62,27 @@ public function form(array $form, FormStateInterface $form_state) { * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { - $this->entity->set('replacementID', $form_state->getValue('replacement')); - + // Save a selected replacement in the image style storage. It will be used + // later, in the same request, when resolving dependencies. + if ($replacement = $form_state->getValue('replacement')) { + /** @var \Drupal\image\ImageStyleStorageInterface $storage */ + $storage = $this->entityTypeManager->getStorage($this->entity->getEntityTypeId()); + $storage->setReplacementId($this->entity->id(), $replacement); + } parent::submitForm($form, $form_state); } + /** + * Returns a list of image style replacement options. + * + * @return array + * An option list suitable for the form select '#options'. + */ + protected function getReplacementOptions() { + if (!isset($this->replacementOptions)) { + $this->replacementOptions = array_diff_key(image_style_options(), [$this->getEntity()->id() => '']); + } + return $this->replacementOptions; + } + } diff --git a/core/modules/image/src/ImageStyleInterface.php b/core/modules/image/src/ImageStyleInterface.php index e1cad03bbba6f683300934d0405b4a1608cd1794..39bb583d78eb971a125d22607cf9a07238bd9e2d 100644 --- a/core/modules/image/src/ImageStyleInterface.php +++ b/core/modules/image/src/ImageStyleInterface.php @@ -17,8 +17,14 @@ interface ImageStyleInterface extends ConfigEntityInterface { /** * Returns the replacement ID. * - * @return string - * The name of the image style to use as replacement upon delete. + * @return string|null + * The replacement image style ID or NULL if no replacement has been + * selected. + * + * @deprecated in Drupal 8.0.x, will be removed before Drupal 9.0.x. Use + * \Drupal\image\ImageStyleStorageInterface::getReplacementId() instead. + * + * @see \Drupal\image\ImageStyleStorageInterface::getReplacementId() */ public function getReplacementID(); diff --git a/core/modules/image/src/ImageStyleStorage.php b/core/modules/image/src/ImageStyleStorage.php new file mode 100644 index 0000000000000000000000000000000000000000..606d3efa80dad7a4d6fbe82b10834faa4250a8d8 --- /dev/null +++ b/core/modules/image/src/ImageStyleStorage.php @@ -0,0 +1,51 @@ +<?php + +/** + * @file + * Contains \Drupal\image\ImageStyleStorage. + */ + +namespace Drupal\image; + +use Drupal\Core\Config\Entity\ConfigEntityStorage; + +/** + * Storage controller class for "image style" configuration entities. + */ +class ImageStyleStorage extends ConfigEntityStorage implements ImageStyleStorageInterface { + + /** + * Image style replacement memory storage. + * + * This value is not stored in the backend. It's used during the deletion of + * an image style to save the replacement image style in the same request. The + * value is used later, when resolving dependencies. + * + * @var string[] + * + * @see \Drupal\image\Form\ImageStyleDeleteForm::submitForm() + */ + protected $replacement = []; + + /** + * {@inheritdoc} + */ + public function setReplacementId($name, $replacement) { + $this->replacement[$name] = $replacement; + } + + /** + * {@inheritdoc} + */ + public function getReplacementId($name) { + return isset($this->replacement[$name]) ? $this->replacement[$name] : NULL; + } + + /** + * {@inheritdoc} + */ + public function clearReplacementId($name) { + unset($this->replacement[$name]); + } + +} diff --git a/core/modules/image/src/ImageStyleStorageInterface.php b/core/modules/image/src/ImageStyleStorageInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..014ff7cbefd88ee6a0e0dd77cf5d6961cfdc269c --- /dev/null +++ b/core/modules/image/src/ImageStyleStorageInterface.php @@ -0,0 +1,57 @@ +<?php + +/** + * @file + * Contains \Drupal\image\ImageStyleStorageInterface. + */ + +namespace Drupal\image; + +/** + * Interface for storage controller for "image style" configuration entities. + */ +interface ImageStyleStorageInterface { + + /** + * Stores a replacement ID for an image style being deleted. + * + * The method stores a replacement style to be used by the configuration + * dependency system when a image style is deleted. The replacement style is + * replacing the deleted style in other configuration entities that are + * depending on the image style being deleted. + * + * @param string $name + * The ID of the image style to be deleted. + * @param string $replacement + * The ID of the image style used as replacement. + */ + public function setReplacementId($name, $replacement); + + /** + * Retrieves the replacement ID of a deleted image style. + * + * The method is retrieving the value stored by ::setReplacementId(). + * + * @param string $name + * The ID of the image style to be replaced. + * + * @return string|null + * The ID of the image style used as replacement, if there's any, or NULL. + * + * @see \Drupal\image\ImageStyleStorageInterface::setReplacementId() + */ + public function getReplacementId($name); + + /** + * Clears a replacement ID from the storage. + * + * The method clears the value previously stored with ::setReplacementId(). + * + * @param string $name + * The ID of the image style to be replaced. + * + * @see \Drupal\image\ImageStyleStorageInterface::setReplacementId() + */ + public function clearReplacementId($name); + +} diff --git a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php index 55b613a8ff50101fde1d6643fec42daece2f7a63..029e7a5026cd5c536d9dfd7659c63e490163a650 100644 --- a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php @@ -14,6 +14,7 @@ use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; +use Drupal\image\Entity\ImageStyle; use Symfony\Component\DependencyInjection\ContainerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Cache\Cache; @@ -41,7 +42,7 @@ class ImageFormatter extends ImageFormatterBase implements ContainerFactoryPlugi /** * The image style entity storage. * - * @var \Drupal\Core\Entity\EntityStorageInterface + * @var \Drupal\image\ImageStyleStorageInterface */ protected $imageStyleStorage; @@ -234,4 +235,41 @@ public function viewElements(FieldItemListInterface $items, $langcode) { return $elements; } + /** + * {@inheritdoc} + */ + public function calculateDependencies() { + $dependencies = parent::calculateDependencies(); + $style_id = $this->getSetting('image_style'); + /** @var \Drupal\image\ImageStyleInterface $style */ + if ($style_id && $style = ImageStyle::load($style_id)) { + // If this formatter uses a valid image style to display the image, add + // the image style configuration entity as dependency of this formatter. + $dependencies[$style->getConfigDependencyKey()][] = $style->getConfigDependencyName(); + } + return $dependencies; + } + + /** + * {@inheritdoc} + */ + public function onDependencyRemoval(array $dependencies) { + $changed = parent::onDependencyRemoval($dependencies); + $style_id = $this->getSetting('image_style'); + /** @var \Drupal\image\ImageStyleInterface $style */ + if ($style_id && $style = ImageStyle::load($style_id)) { + if (!empty($dependencies[$style->getConfigDependencyKey()][$style->getConfigDependencyName()])) { + $replacement_id = $this->imageStyleStorage->getReplacementId($style_id); + // If a valid replacement has been provided in the storage, replace the + // image style with the replacement and signal that the formatter plugin + // settings were updated. + if ($replacement_id && ImageStyle::load($replacement_id)) { + $this->setSetting('image_style', $replacement_id); + $changed = TRUE; + } + } + } + return $changed; + } + } diff --git a/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php b/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php index 82092e65a95dc647030658caf3ac25651273e580..10ebfb5a88b5d67c0637658b4d8b31f1e6695d14 100644 --- a/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php +++ b/core/modules/image/src/Plugin/Field/FieldWidget/ImageWidget.php @@ -12,6 +12,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\file\Entity\File; use Drupal\file\Plugin\Field\FieldWidget\FileWidget; +use Drupal\image\Entity\ImageStyle; /** * Plugin implementation of the 'image_image' widget. @@ -273,4 +274,49 @@ public static function validateRequiredFields($element, FormStateInterface $form } } + /** + * {@inheritdoc} + */ + public function calculateDependencies() { + $dependencies = parent::calculateDependencies(); + $style_id = $this->getSetting('preview_image_style'); + /** @var \Drupal\image\ImageStyleInterface $style */ + if ($style_id && $style = ImageStyle::load($style_id)) { + // If this widget uses a valid image style to display the preview of the + // uploaded image, add that image style configuration entity as dependency + // of this widget. + $dependencies[$style->getConfigDependencyKey()][] = $style->getConfigDependencyName(); + } + return $dependencies; + } + + /** + * {@inheritdoc} + */ + public function onDependencyRemoval(array $dependencies) { + $changed = parent::onDependencyRemoval($dependencies); + $style_id = $this->getSetting('preview_image_style'); + /** @var \Drupal\image\ImageStyleInterface $style */ + if ($style_id && $style = ImageStyle::load($style_id)) { + if (!empty($dependencies[$style->getConfigDependencyKey()][$style->getConfigDependencyName()])) { + /** @var \Drupal\image\ImageStyleStorageInterface $storage */ + $storage = \Drupal::entityManager()->getStorage($style->getEntityTypeId()); + $replacement_id = $storage->getReplacementId($style_id); + // If a valid replacement has been provided in the storage, replace the + // preview image style with the replacement. + if ($replacement_id && ImageStyle::load($replacement_id)) { + $this->setSetting('preview_image_style', $replacement_id); + } + // If there's no replacement or the replacement is invalid, disable the + // image preview. + else { + $this->setSetting('preview_image_style', ''); + } + // Signal that the formatter plugin settings were updated. + $changed = TRUE; + } + } + return $changed; + } + } diff --git a/core/modules/image/src/Tests/ImageAdminStylesTest.php b/core/modules/image/src/Tests/ImageAdminStylesTest.php index fb59a4809a0755a8103e5508124598af01becbef..211cf8975e5461bc2e0aeb7de2ca81c2d1218f06 100644 --- a/core/modules/image/src/Tests/ImageAdminStylesTest.php +++ b/core/modules/image/src/Tests/ImageAdminStylesTest.php @@ -8,6 +8,7 @@ namespace Drupal\image\Tests; use Drupal\Component\Utility\SafeMarkup; +use Drupal\Core\Entity\Entity\EntityViewDisplay; use Drupal\image\Entity\ImageStyle; use Drupal\image\ImageStyleInterface; use Drupal\node\Entity\Node; @@ -446,6 +447,11 @@ function testConfigImport() { // Copy config to sync, and delete the image style. $sync = $this->container->get('config.storage.sync'); $active = $this->container->get('config.storage'); + // Remove the image field from the display, to avoid a dependency error + // during import. + EntityViewDisplay::load('node.article.default') + ->removeComponent($field_name) + ->save(); $this->copyConfig($active, $sync); $sync->delete('image.style.' . $style_name); $this->configImporter()->import(); diff --git a/core/modules/image/src/Tests/ImageFieldTestBase.php b/core/modules/image/src/Tests/ImageFieldTestBase.php index 271b969aea9732f70961ff2660c44503d96f0ddf..9984a76a7da5288a45c07dc9b0356ea767abc569 100644 --- a/core/modules/image/src/Tests/ImageFieldTestBase.php +++ b/core/modules/image/src/Tests/ImageFieldTestBase.php @@ -66,9 +66,11 @@ protected function setUp() { * @param array $field_settings * A list of instance settings that will be added to the instance defaults. * @param array $widget_settings - * A list of widget settings that will be added to the widget defaults. + * Widget settings to be added to the widget defaults. + * @param array $formatter_settings + * Formatter settings to be added to the formatter defaults. */ - function createImageField($name, $type_name, $storage_settings = array(), $field_settings = array(), $widget_settings = array()) { + function createImageField($name, $type_name, $storage_settings = array(), $field_settings = array(), $widget_settings = array(), $formatter_settings = array()) { entity_create('field_storage_config', array( 'field_name' => $name, 'entity_type' => 'node', @@ -95,7 +97,10 @@ function createImageField($name, $type_name, $storage_settings = array(), $field ->save(); entity_get_display('node', $type_name, 'default') - ->setComponent($name) + ->setComponent($name, array( + 'type' => 'image', + 'settings' => $formatter_settings, + )) ->save(); return $field_config; diff --git a/core/modules/image/src/Tests/ImageStyleDeleteTest.php b/core/modules/image/src/Tests/ImageStyleDeleteTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7bcedb33a375e3282d81cb31c3c1da78afdb7ed7 --- /dev/null +++ b/core/modules/image/src/Tests/ImageStyleDeleteTest.php @@ -0,0 +1,86 @@ +<?php + +/** + * @file + * Contains \Drupal\image\Tests\ImageStyleDeleteTest. + */ + +namespace Drupal\image\Tests; + +use Drupal\Core\Entity\Entity\EntityFormDisplay; +use Drupal\Core\Entity\Entity\EntityViewDisplay; + +/** + * Tests image style deletion using the UI. + * + * @group image + */ +class ImageStyleDeleteTest extends ImageFieldTestBase { + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + // Create an image field 'foo' having the image style 'medium' as widget + // preview and as formatter. + $this->createImageField('foo', 'page', [], [], ['preview_image_style' => 'medium'], ['image_style' => 'medium']); + } + + /** + * Tests image style deletion. + */ + public function testDelete() { + $this->drupalGet('admin/config/media/image-styles/manage/medium/delete'); + // Checks that the 'replacement' select element is displayed. + $this->assertFieldByName('replacement'); + // Checks that UI messages are correct. + $this->assertRaw(t('If this style is in use on the site, you may select another style to replace it. All images that have been generated for this style will be permanently deleted. If no replacement style is selected, the dependent configurations might need manual reconfiguration.')); + $this->assertNoRaw(t('All images that have been generated for this style will be permanently deleted. The dependent configurations might need manual reconfiguration.')); + + // Delete 'medium' image style but replace it with 'thumbnail'. This style + // is involved in 'node.page.default' display view and form. + $this->drupalPostForm(NULL, ['replacement' => 'thumbnail'], t('Delete')); + + /** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $view_display */ + $view_display = EntityViewDisplay::load('node.page.default'); + // Checks that the formatter setting is replaced. + if ($this->assertNotNull($component = $view_display->getComponent('foo'))) { + $this->assertIdentical($component['settings']['image_style'], 'thumbnail'); + } + /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display */ + $form_display = EntityFormDisplay::load('node.page.default'); + // Check that the widget setting is replaced. + if ($this->assertNotNull($component = $form_display->getComponent('foo'))) { + $this->assertIdentical($component['settings']['preview_image_style'], 'thumbnail'); + } + + $this->drupalGet('admin/config/media/image-styles/manage/thumbnail/delete'); + // Checks that the 'replacement' select element is displayed. + $this->assertFieldByName('replacement'); + // Checks that UI messages are correct. + $this->assertRaw(t('If this style is in use on the site, you may select another style to replace it. All images that have been generated for this style will be permanently deleted. If no replacement style is selected, the dependent configurations might need manual reconfiguration.')); + $this->assertNoRaw(t('All images that have been generated for this style will be permanently deleted. The dependent configurations might need manual reconfiguration.')); + + // Delete 'thumbnail' image style. Provide no replacement. + $this->drupalPostForm(NULL, [], t('Delete')); + + $view_display = EntityViewDisplay::load('node.page.default'); + // Checks that the formatter setting is disabled. + $this->assertNull($view_display->getComponent('foo')); + $this->assertNotNull($view_display->get('hidden')['foo']); + // Checks that widget setting is preserved with the image preview disabled. + $form_display = EntityFormDisplay::load('node.page.default'); + $this->assertNotNull($widget = $form_display->getComponent('foo')); + $this->assertIdentical($widget['settings']['preview_image_style'], ''); + + // Now, there's only one image style configured on the system: 'large'. + $this->drupalGet('admin/config/media/image-styles/manage/large/delete'); + // Checks that the 'replacement' select element is not displayed. + $this->assertNoFieldByName('replacement'); + // Checks that UI messages are correct. + $this->assertNoRaw(t('If this style is in use on the site, you may select another style to replace it. All images that have been generated for this style will be permanently deleted. If no replacement style is selected, the dependent configurations might need manual reconfiguration.')); + $this->assertRaw(t('All images that have been generated for this style will be permanently deleted. The dependent configurations might need manual reconfiguration.')); + } + +} diff --git a/core/modules/image/src/Tests/Update/ImageUpdateTest.php b/core/modules/image/src/Tests/Update/ImageUpdateTest.php new file mode 100644 index 0000000000000000000000000000000000000000..f37ca8fed771fd836a1ba28159a2d6bbf17976ed --- /dev/null +++ b/core/modules/image/src/Tests/Update/ImageUpdateTest.php @@ -0,0 +1,59 @@ +<?php + +/** + * @file + * Contains \Drupal\image\Tests\Update\ImageUpdateTest. + */ + +namespace Drupal\image\Tests\Update; + +use Drupal\system\Tests\Update\UpdatePathTestBase; + +/** + * Tests Image update path. + * + * @group image + */ +class ImageUpdateTest extends UpdatePathTestBase { + + /** + * {@inheritdoc} + */ + protected function setDatabaseDumpFiles() { + $this->databaseDumpFiles = [ + __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8-rc1.bare.standard.php.gz', + ]; + } + + /** + * Tests image_post_update_image_style_dependencies(). + * + * @see image_post_update_image_style_dependencies() + */ + public function testPostUpdateImageStylesDependencies() { + $view = 'core.entity_view_display.node.article.default'; + $form = 'core.entity_form_display.node.article.default'; + + // Check that view display 'node.article.default' doesn't depend on image + // style 'image.style.large'. + $dependencies = $this->config($view)->get('dependencies.config'); + $this->assertFalse(in_array('image.style.large', $dependencies)); + // Check that form display 'node.article.default' doesn't depend on image + // style 'image.style.thumbnail'. + $dependencies = $this->config($form)->get('dependencies.config'); + $this->assertFalse(in_array('image.style.thumbnail', $dependencies)); + + // Run updates. + $this->runUpdates(); + + // Check that view display 'node.article.default' depend on image style + // 'image.style.large'. + $dependencies = $this->config($view)->get('dependencies.config'); + $this->assertTrue(in_array('image.style.large', $dependencies)); + // Check that form display 'node.article.default' depend on image style + // 'image.style.thumbnail'. + $dependencies = $this->config($view)->get('dependencies.config'); + $this->assertTrue(in_array('image.style.large', $dependencies)); + } + +} diff --git a/core/modules/image/tests/src/Kernel/ImageStyleIntegrationTest.php b/core/modules/image/tests/src/Kernel/ImageStyleIntegrationTest.php new file mode 100644 index 0000000000000000000000000000000000000000..304718febdb33a57b770da9581cbc6c4ad37f5dd --- /dev/null +++ b/core/modules/image/tests/src/Kernel/ImageStyleIntegrationTest.php @@ -0,0 +1,117 @@ +<?php + +/** + * @file + * Contains \Drupal\Tests\image\Kernel\ImageStyleIntegrationTest. + */ + +namespace Drupal\Tests\image\Kernel; + +use Drupal\Core\Entity\Entity\EntityFormDisplay; +use Drupal\Core\Entity\Entity\EntityViewDisplay; +use Drupal\field\Entity\FieldConfig; +use Drupal\field\Entity\FieldStorageConfig; +use Drupal\image\Entity\ImageStyle; +use Drupal\KernelTests\KernelTestBase; +use Drupal\node\Entity\NodeType; + +/** + * Tests the integration of ImageStyle with the core. + * + * @group image + */ +class ImageStyleIntegrationTest extends KernelTestBase { + + /** + * {@inheritdoc} + */ + public static $modules = ['image', 'file', 'field', 'system', 'user', 'node']; + + /** + * Tests the dependency between ImageStyle and entity display components. + */ + public function testEntityDisplayDependency() { + // Create two image styles. + /** @var \Drupal\image\ImageStyleInterface $style */ + $style = ImageStyle::create(['name' => 'main_style']); + $style->save(); + /** @var \Drupal\image\ImageStyleInterface $replacement */ + $replacement = ImageStyle::create(['name' => 'replacement_style']); + $replacement->save(); + + // Create a node-type, named 'note'. + $node_type = NodeType::create(['type' => 'note']); + $node_type->save(); + + // Create an image field and attach it to the 'note' node-type. + FieldStorageConfig::create([ + 'entity_type' => 'node', + 'field_name' => 'sticker', + 'type' => 'image', + ])->save(); + FieldConfig::create([ + 'entity_type' => 'node', + 'field_name' => 'sticker', + 'bundle' => 'note', + ])->save(); + + // Create the default entity view display and set the 'sticker' field to use + // the 'main_style' images style in formatter. + /** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $view_display */ + $view_display = EntityViewDisplay::create([ + 'targetEntityType' => 'node', + 'bundle' => 'note', + 'mode' => 'default', + 'status' => TRUE, + ])->setComponent('sticker', ['settings' => ['image_style' => 'main_style']]); + $view_display->save(); + + // Create the default entity form display and set the 'sticker' field to use + // the 'main_style' images style in the widget. + /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display */ + $form_display = EntityFormDisplay::create([ + 'targetEntityType' => 'node', + 'bundle' => 'note', + 'mode' => 'default', + 'status' => TRUE, + ])->setComponent('sticker', ['settings' => ['preview_image_style' => 'main_style']]); + $form_display->save(); + + // Check that the entity displays exists before dependency removal. + $this->assertNotNull(EntityViewDisplay::load($view_display->id())); + $this->assertNotNull(EntityFormDisplay::load($form_display->id())); + + // Delete the 'main_style' image style. Before that, emulate the UI process + // of selecting a replacement style by setting the replacement image style + // ID in the image style storage. + /** @var \Drupal\image\ImageStyleStorageInterface $storage */ + $storage = $this->container->get('entity.manager')->getStorage($style->getEntityTypeId()); + $storage->setReplacementId('main_style', 'replacement_style'); + $style->delete(); + + // Check that the entity displays exists after dependency removal. + $this->assertNotNull($view_display = EntityViewDisplay::load($view_display->id())); + $this->assertNotNull($form_display = EntityFormDisplay::load($form_display->id())); + // Check that the 'sticker' formatter component exists in both displays. + $this->assertNotNull($formatter = $view_display->getComponent('sticker')); + $this->assertNotNull($widget = $form_display->getComponent('sticker')); + // Check that both displays are using now 'replacement_style' for images. + $this->assertSame('replacement_style', $formatter['settings']['image_style']); + $this->assertSame('replacement_style', $widget['settings']['preview_image_style']); + + // Delete the 'replacement_style' without setting a replacement image style. + $replacement->delete(); + + // The entity view and form displays exists after dependency removal. + $this->assertNotNull($view_display = EntityViewDisplay::load($view_display->id())); + $this->assertNotNull($form_display = EntityFormDisplay::load($form_display->id())); + // The 'sticker' formatter component should be hidden in view display. + $this->assertNull($view_display->getComponent('sticker')); + $this->assertTrue($view_display->get('hidden')['sticker']); + // The 'sticker' widget component should be active in form displays, but the + // image preview should be disabled. + $this->assertNotNull($widget = $form_display->getComponent('sticker')); + $this->assertSame('', $widget['settings']['preview_image_style']); + } + +} diff --git a/core/profiles/standard/config/install/core.entity_form_display.node.article.default.yml b/core/profiles/standard/config/install/core.entity_form_display.node.article.default.yml index 189737caca0cdf1ccd7c7305dbf423b409891234..79156b2e876f36bd76fc74f488d9e52046538d22 100644 --- a/core/profiles/standard/config/install/core.entity_form_display.node.article.default.yml +++ b/core/profiles/standard/config/install/core.entity_form_display.node.article.default.yml @@ -6,6 +6,7 @@ dependencies: - field.field.node.article.comment - field.field.node.article.field_image - field.field.node.article.field_tags + - image.style.thumbnail - node.type.article module: - comment diff --git a/core/profiles/standard/config/install/core.entity_form_display.user.user.default.yml b/core/profiles/standard/config/install/core.entity_form_display.user.user.default.yml index 107d36362f0e57f7648fcb7c0f269b68449316d3..466b6e0b38e311c316c1735184eadbd49d7b3efa 100644 --- a/core/profiles/standard/config/install/core.entity_form_display.user.user.default.yml +++ b/core/profiles/standard/config/install/core.entity_form_display.user.user.default.yml @@ -3,6 +3,7 @@ status: true dependencies: config: - field.field.user.user.user_picture + - image.style.thumbnail module: - image - user diff --git a/core/profiles/standard/config/install/core.entity_view_display.node.article.default.yml b/core/profiles/standard/config/install/core.entity_view_display.node.article.default.yml index e0d378238e45ea81a355e085c626dcd5e2567547..f880cfd5fd2faf61c0c9afbe7d5fcead2fe16ceb 100644 --- a/core/profiles/standard/config/install/core.entity_view_display.node.article.default.yml +++ b/core/profiles/standard/config/install/core.entity_view_display.node.article.default.yml @@ -6,6 +6,7 @@ dependencies: - field.field.node.article.comment - field.field.node.article.field_image - field.field.node.article.field_tags + - image.style.large - node.type.article module: - comment diff --git a/core/profiles/standard/config/install/core.entity_view_display.node.article.teaser.yml b/core/profiles/standard/config/install/core.entity_view_display.node.article.teaser.yml index 1cf18dc76847a8c38d00da0f1c8895a7dc824381..43ee079e1f86758f8c0c25043fbd0661aa4607ee 100644 --- a/core/profiles/standard/config/install/core.entity_view_display.node.article.teaser.yml +++ b/core/profiles/standard/config/install/core.entity_view_display.node.article.teaser.yml @@ -7,6 +7,7 @@ dependencies: - field.field.node.article.comment - field.field.node.article.field_image - field.field.node.article.field_tags + - image.style.medium - node.type.article module: - image diff --git a/core/profiles/standard/config/install/core.entity_view_display.user.user.compact.yml b/core/profiles/standard/config/install/core.entity_view_display.user.user.compact.yml index 9c74439bb77a64470b80525b92749424800dc0d5..4c13792449077d5a69223bee3a5d58ce6abf54db 100644 --- a/core/profiles/standard/config/install/core.entity_view_display.user.user.compact.yml +++ b/core/profiles/standard/config/install/core.entity_view_display.user.user.compact.yml @@ -4,6 +4,7 @@ dependencies: config: - core.entity_view_mode.user.compact - field.field.user.user.user_picture + - image.style.thumbnail module: - image - user diff --git a/core/profiles/standard/config/install/core.entity_view_display.user.user.default.yml b/core/profiles/standard/config/install/core.entity_view_display.user.user.default.yml index 807fefe3dd8c0be199666576c1d370559d0583ee..9e4621d5e497ec74d7e53e6a7a161733371e6fb2 100644 --- a/core/profiles/standard/config/install/core.entity_view_display.user.user.default.yml +++ b/core/profiles/standard/config/install/core.entity_view_display.user.user.default.yml @@ -3,6 +3,7 @@ status: true dependencies: config: - field.field.user.user.user_picture + - image.style.thumbnail module: - image - user