diff --git a/core/modules/image/config/schema/image.schema.yml b/core/modules/image/config/schema/image.schema.yml index 0006ad7..a43aae2 100644 --- a/core/modules/image/config/schema/image.schema.yml +++ b/core/modules/image/config/schema/image.schema.yml @@ -11,6 +11,7 @@ image.style.*: label: 'Label' effects: type: sequence + orderby: key sequence: type: mapping mapping: diff --git a/core/modules/image/image.post_update.php b/core/modules/image/image.post_update.php index 04d8c4b..0f91dd8 100644 --- a/core/modules/image/image.post_update.php +++ b/core/modules/image/image.post_update.php @@ -7,6 +7,7 @@ use Drupal\Core\Entity\Entity\EntityViewDisplay; use Drupal\Core\Entity\Entity\EntityFormDisplay; +use Drupal\image\Entity\ImageStyle; /** * Saves the image style dependencies into form and view display entities. @@ -20,3 +21,17 @@ function image_post_update_image_style_dependencies() { $display->save(); } } + +/** + * Saves the image styles to ensure a consistent effect order. + */ +function image_post_update_image_style_effect_order() { + $styles = ImageStyle::loadMultiple(); + /** @var \Drupal\image\ImageStyleInterface[] $styles */ + foreach ($styles as $style) { + if ($style->getEffects()->count() > 1) { + // Re-save each config entity to sort effects. + $style->save(); + } + } +} diff --git a/core/modules/image/src/Entity/ImageStyle.php b/core/modules/image/src/Entity/ImageStyle.php index fb096a7..eb8aad8 100644 --- a/core/modules/image/src/Entity/ImageStyle.php +++ b/core/modules/image/src/Entity/ImageStyle.php @@ -371,6 +371,7 @@ public function getPluginCollections() { public function addImageEffect(array $configuration) { $configuration['uuid'] = $this->uuidGenerator()->generate(); $this->getEffects()->addInstanceId($configuration['uuid'], $configuration); + $this->getEffects()->sort(); return $configuration['uuid']; } diff --git a/core/modules/image/src/Tests/Update/ImageUpdateEffectOrderTest.php b/core/modules/image/src/Tests/Update/ImageUpdateEffectOrderTest.php new file mode 100644 index 0000000..9b30b4b --- /dev/null +++ b/core/modules/image/src/Tests/Update/ImageUpdateEffectOrderTest.php @@ -0,0 +1,58 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8-rc1.bare.standard.php.gz', + __DIR__ . '/../../../tests/fixtures/drupal-8.image-update-effect-order-test.php', + ]; + } + + /** + * Tests image_post_update_image_style_effect_order(). + * + * @see image_post_update_image_style_effect_order() + */ + public function testPostUpdateImageStylesDependencies() { + + $expected_order_in_config = [ + '2fe8084a-0679-46d4-9538-a2ba6917f166', + '59b6b545-7c9e-4f98-ab94-75ac3034259c', + '9d8ffc61-e928-41c9-9e12-7e3484814365', + ]; + + $expected_order_in_plugin_collection = [ + '59b6b545-7c9e-4f98-ab94-75ac3034259c', + '9d8ffc61-e928-41c9-9e12-7e3484814365', + '2fe8084a-0679-46d4-9538-a2ba6917f166', + ]; + + $style = ImageStyle::load('test'); + $this->assertNotIdentical($expected_order_in_config, array_keys($style->get('effects'))); + // Plugin collection will bne osrted correctly because it is sorted by + // weight. + $this->assertIdentical($expected_order_in_plugin_collection, array_keys(iterator_to_array($style->getEffects()->getIterator()))); + + // Run updates. + $this->runUpdates(); + + $style = ImageStyle::load('test'); + $this->assertIdentical($expected_order_in_config, array_keys($style->get('effects'))); + $this->assertIdentical($expected_order_in_plugin_collection, array_keys(iterator_to_array($style->getEffects()->getIterator()))); + } + +} diff --git a/core/modules/image/tests/fixtures/drupal-8.image-update-effect-order-test.php b/core/modules/image/tests/fixtures/drupal-8.image-update-effect-order-test.php new file mode 100644 index 0000000..343b180 --- /dev/null +++ b/core/modules/image/tests/fixtures/drupal-8.image-update-effect-order-test.php @@ -0,0 +1,23 @@ +insert('config') + ->fields(['data', 'name', 'collection']) + ->values([ + 'name' => 'image.style.test', + 'data' => serialize($config), + 'collection' => '', + ]) + ->execute(); diff --git a/core/modules/image/tests/fixtures/image.style.test.yml b/core/modules/image/tests/fixtures/image.style.test.yml new file mode 100644 index 0000000..fe8fa43 --- /dev/null +++ b/core/modules/image/tests/fixtures/image.style.test.yml @@ -0,0 +1,28 @@ +uuid: fab98f81-d3a8-481d-94ba-998d590765c3 +langcode: en +status: true +dependencies: { } +name: test +label: test +effects: + 9d8ffc61-e928-41c9-9e12-7e3484814365: + uuid: 9d8ffc61-e928-41c9-9e12-7e3484814365 + id: image_crop + weight: 2 + data: + width: 400 + height: 400 + anchor: left-top + 59b6b545-7c9e-4f98-ab94-75ac3034259c: + uuid: 59b6b545-7c9e-4f98-ab94-75ac3034259c + id: image_rotate + weight: 1 + data: + degrees: 90 + bgcolor: '' + random: false + 2fe8084a-0679-46d4-9538-a2ba6917f166: + uuid: 2fe8084a-0679-46d4-9538-a2ba6917f166 + id: image_desaturate + weight: 3 + data: { } diff --git a/core/modules/image/tests/src/Kernel/ImageStyleEffectOrderTest.php b/core/modules/image/tests/src/Kernel/ImageStyleEffectOrderTest.php new file mode 100644 index 0000000..d091761 --- /dev/null +++ b/core/modules/image/tests/src/Kernel/ImageStyleEffectOrderTest.php @@ -0,0 +1,124 @@ +phpUuidGenerator = new Php(); + parent::setUp(); + } + + /** + * Tests the dependency between ImageStyle and entity display components. + */ + public function testEffectOrder() {// Create two image styles. + /** @var \Drupal\image\ImageStyleInterface $style */ + $style = ImageStyle::create(['name' => 'test']); + $style->save(); + + // Rotate 90 degrees anticlockwise. + $effect = [ + 'id' => 'image_rotate', + 'data' => [ + 'degrees' => -90, + 'random' => FALSE, + ], + 'weight' => 1, + ]; + $this->uuidToGenerate = 'b'; + $style->addImageEffect($effect); + + // Rotate 180 degrees clockwise. + $effect = [ + 'id' => 'image_rotate', + 'data' => [ + 'degrees' => 180, + 'random' => FALSE, + ], + 'weight' => 3, + ]; + $this->uuidToGenerate = 'c'; + $style->addImageEffect($effect); + + // Effects are sorted by weight in the plugin collection but the + // configuration is sorted by by key on saving. + $style->save(); + $this->assertSame(['b', 'c'], array_keys(iterator_to_array($style->getEffects()->getIterator()))); + $this->assertSame(['b', 'c'], array_keys($style->get('effects'))); + + // Rotate 180 degrees anticlockwise. + $effect = [ + 'id' => 'image_rotate', + 'data' => [ + 'degrees' => -180, + 'random' => FALSE, + ], + 'weight' => 2, + ]; + $this->uuidToGenerate = 'a'; + $style->addImageEffect($effect); + + // Effects are sorted by weight in the plugin collection but the + // configuration is sorted by by key on saving. + $style->save(); + $this->assertSame(['b', 'a', 'c'], array_keys(iterator_to_array($style->getEffects()->getIterator()))); + $this->assertSame(['a', 'b', 'c'], array_keys($style->get('effects'))); + } + + /** + * {@inheritdoc} + */ + public function generate() { + if ($this->uuidToGenerate) { + return $this->uuidToGenerate; + } + return $this->phpUuidGenerator->generate(); + } + + /** + * {@inheritdoc} + */ + public function alter(ContainerBuilder $container) { + // Override the UUID service so we can ensure that the UUIDs generated are + // in a specific order. + $uuid_service = $container->getDefinition('uuid'); + $uuid_service->setSynthetic(TRUE); + $container->set('uuid', $this); + } + +} diff --git a/core/tests/Drupal/KernelTests/Core/Config/ConfigSchemaTest.php b/core/tests/Drupal/KernelTests/Core/Config/ConfigSchemaTest.php index 96bd18c..d591462 100644 --- a/core/tests/Drupal/KernelTests/Core/Config/ConfigSchemaTest.php +++ b/core/tests/Drupal/KernelTests/Core/Config/ConfigSchemaTest.php @@ -173,6 +173,7 @@ public function testSchemaMapping() { $expected['mapping']['label']['type'] = 'label'; $expected['mapping']['label']['label'] = 'Label'; $expected['mapping']['effects']['type'] = 'sequence'; + $expected['mapping']['effects']['orderby'] = 'key'; $expected['mapping']['effects']['sequence']['type'] = 'mapping'; $expected['mapping']['effects']['sequence']['mapping']['id']['type'] = 'string'; $expected['mapping']['effects']['sequence']['mapping']['data']['type'] = 'image.effect.[%parent.id]';