diff options
7 files changed, 262 insertions, 2 deletions
diff --git a/core/modules/breakpoint/src/BreakpointManager.php b/core/modules/breakpoint/src/BreakpointManager.php index 2e70062a856..6db88219a29 100644 --- a/core/modules/breakpoint/src/BreakpointManager.php +++ b/core/modules/breakpoint/src/BreakpointManager.php @@ -133,8 +133,9 @@ class BreakpointManager extends DefaultPluginManager implements BreakpointManage if (!in_array('1x', $definition['multipliers'])) { $definition['multipliers'][] = '1x'; } - // Ensure that multipliers are sorted correctly. - sort($definition['multipliers']); + // Ensure that multipliers are sorted numerically so 1x, 1.5x and 2x + // come out in that order instead of 1.5x, 1x, 2x. + sort($definition['multipliers'], SORT_NUMERIC); } /** diff --git a/core/modules/responsive_image/responsive_image.post_update.php b/core/modules/responsive_image/responsive_image.post_update.php index 70093a9eda2..764162d9426 100644 --- a/core/modules/responsive_image/responsive_image.post_update.php +++ b/core/modules/responsive_image/responsive_image.post_update.php @@ -5,6 +5,10 @@ * Post update functions for Responsive Image. */ +use Drupal\Core\Config\Entity\ConfigEntityUpdater; +use Drupal\responsive_image\ResponsiveImageConfigUpdater; +use Drupal\responsive_image\ResponsiveImageStyleInterface; + /** * Implements hook_removed_post_updates(). */ @@ -13,3 +17,13 @@ function responsive_image_removed_post_updates() { 'responsive_image_post_update_recreate_dependencies' => '9.0.0', ]; } + +/** + * Re-order mappings by breakpoint ID and descending numeric multiplier order. + */ +function responsive_image_post_update_order_multiplier_numerically(array &$sandbox = NULL): void { + $responsive_image_config_updater = \Drupal::classResolver(ResponsiveImageConfigUpdater::class); + \Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'responsive_image_style', function (ResponsiveImageStyleInterface $responsive_image_style) use ($responsive_image_config_updater): bool { + return $responsive_image_config_updater->orderMultipliersNumerically($responsive_image_style); + }); +} diff --git a/core/modules/responsive_image/src/Entity/ResponsiveImageStyle.php b/core/modules/responsive_image/src/Entity/ResponsiveImageStyle.php index 508c8f43302..1fe6adc286a 100644 --- a/core/modules/responsive_image/src/Entity/ResponsiveImageStyle.php +++ b/core/modules/responsive_image/src/Entity/ResponsiveImageStyle.php @@ -3,7 +3,9 @@ namespace Drupal\responsive_image\Entity; use Drupal\Core\Config\Entity\ConfigEntityBase; +use Drupal\Core\Entity\EntityStorageInterface; use Drupal\image\Entity\ImageStyle; +use Drupal\responsive_image\ResponsiveImageConfigUpdater; use Drupal\responsive_image\ResponsiveImageStyleInterface; /** @@ -113,6 +115,16 @@ class ResponsiveImageStyle extends ConfigEntityBase implements ResponsiveImageSt /** * {@inheritdoc} */ + public function preSave(EntityStorageInterface $storage) { + parent::preSave($storage); + $config_updater = \Drupal::classResolver(ResponsiveImageConfigUpdater::class); + assert($config_updater instanceof ResponsiveImageConfigUpdater); + $config_updater->orderMultipliersNumerically($this); + } + + /** + * {@inheritdoc} + */ public function addImageStyleMapping($breakpoint_id, $multiplier, array $image_style_mapping) { // If there is an existing mapping, overwrite it. foreach ($this->image_style_mappings as &$mapping) { diff --git a/core/modules/responsive_image/src/ResponsiveImageConfigUpdater.php b/core/modules/responsive_image/src/ResponsiveImageConfigUpdater.php new file mode 100644 index 00000000000..4cc56f0fc55 --- /dev/null +++ b/core/modules/responsive_image/src/ResponsiveImageConfigUpdater.php @@ -0,0 +1,47 @@ +<?php + +namespace Drupal\responsive_image; + +/** + * Provides a BC layer for modules providing old configurations. + * + * @internal + * This class is only meant to fix outdated responsive image configuration and + * its methods should not be invoked directly. It will be removed once all the + * deprecated methods have been removed. + */ +final class ResponsiveImageConfigUpdater { + + /** + * Re-order mappings by breakpoint ID and descending numeric multiplier order. + * + * @param \Drupal\responsive_image\ResponsiveImageStyleInterface $responsive_image_style + * The responsive image style + * + * @return bool + * Whether the responsive image style was updated. + */ + public function orderMultipliersNumerically(ResponsiveImageStyleInterface $responsive_image_style): bool { + $changed = FALSE; + + $mappings = $sorted = $responsive_image_style->getImageStyleMappings(); + usort($sorted, static function (array $a, array $b) { + $first = ((float) mb_substr($a['multiplier'], 0, -1)) * 100; + $second = ((float) mb_substr($b['multiplier'], 0, -1)) * 100; + if ($first === $second) { + return strcmp($a['breakpoint_id'], $b['breakpoint_id']); + } + return $first - $second; + }); + if ($sorted !== $mappings) { + $responsive_image_style->removeImageStyleMappings(); + foreach ($sorted as $mapping) { + $responsive_image_style->addImageStyleMapping($mapping['breakpoint_id'], $mapping['multiplier'], $mapping); + } + $changed = TRUE; + } + + return $changed; + } + +} diff --git a/core/modules/responsive_image/tests/fixtures/update/responsive_image-order-multipliers-numerically.php b/core/modules/responsive_image/tests/fixtures/update/responsive_image-order-multipliers-numerically.php new file mode 100644 index 00000000000..5d6c02069a5 --- /dev/null +++ b/core/modules/responsive_image/tests/fixtures/update/responsive_image-order-multipliers-numerically.php @@ -0,0 +1,71 @@ +<?php + +/** + * @file + * Test re-ordering responsive image style multipliers numerically. + */ + +use Drupal\Core\Database\Database; + +$connection = Database::getConnection(); + +// Add a responsive image style. +$styles = []; +$styles['langcode'] = 'en'; +$styles['status'] = TRUE; +$styles['dependencies']['config'][] = 'image.style.large'; +$styles['dependencies']['config'][] = 'image.style.medium'; +$styles['dependencies']['config'][] = 'image.style.thumbnail'; +$styles['id'] = 'responsive_image_style'; +$styles['uuid'] = '46225242-eb4c-4b10-9a8c-966130b18630'; +$styles['label'] = 'Responsive Image Style'; +$styles['breakpoint_group'] = 'responsive_image'; +$styles['fallback_image_style'] = 'medium'; +$styles['image_style_mappings'] = [ + [ + 'image_mapping_type' => 'sizes', + 'image_mapping' => [ + 'sizes' => '75vw', + 'sizes_image_styles' => [ + 'medium', + ], + ], + 'breakpoint_id' => 'responsive_image.viewport_sizing', + 'multiplier' => '1.5x', + ], + [ + 'image_mapping_type' => 'sizes', + 'image_mapping' => [ + 'sizes' => '100vw', + 'sizes_image_styles' => [ + 'large', + ], + ], + 'breakpoint_id' => 'responsive_image.viewport_sizing', + 'multiplier' => '2x', + ], + [ + 'image_mapping_type' => 'sizes', + 'image_mapping' => [ + 'sizes' => '50vw', + 'sizes_image_styles' => [ + 'thumbnail', + ], + ], + 'breakpoint_id' => 'responsive_image.viewport_sizing', + 'multiplier' => '1x', + ], +]; + +$connection->insert('config') + ->fields([ + 'collection', + 'name', + 'data', + ]) + ->values([ + 'collection' => '', + 'name' => 'responsive_image.styles.responsive_image_style', + 'data' => serialize($styles), + ]) + ->execute(); diff --git a/core/modules/responsive_image/tests/fixtures/update/responsive_image.php b/core/modules/responsive_image/tests/fixtures/update/responsive_image.php new file mode 100644 index 00000000000..4c6277281be --- /dev/null +++ b/core/modules/responsive_image/tests/fixtures/update/responsive_image.php @@ -0,0 +1,55 @@ +<?php + +/** + * @file + * Test fixture. + */ + +use Drupal\Core\Database\Database; + +$connection = Database::getConnection(); + +// Set the schema version. +$connection->merge('key_value') + ->fields([ + 'value' => 'i:8000;', + 'name' => 'responsive_image', + 'collection' => 'system.schema', + ]) + ->condition('collection', 'system.schema') + ->condition('name', 'responsive_image') + ->execute(); + +// Update core.extension. +$extensions = $connection->select('config') + ->fields('config', ['data']) + ->condition('collection', '') + ->condition('name', 'core.extension') + ->execute() + ->fetchField(); +$extensions = unserialize($extensions); +$extensions['module']['responsive_image'] = 0; +$connection->update('config') + ->fields(['data' => serialize($extensions)]) + ->condition('collection', '') + ->condition('name', 'core.extension') + ->execute(); + +// Add all responsive_image_removed_post_updates() as existing updates. +require_once __DIR__ . '/../../../../responsive_image/responsive_image.post_update.php'; +$existing_updates = $connection->select('key_value') + ->fields('key_value', ['value']) + ->condition('collection', 'post_update') + ->condition('name', 'existing_updates') + ->execute() + ->fetchField(); +$existing_updates = unserialize($existing_updates); +$existing_updates = array_merge( + $existing_updates, + array_keys(responsive_image_removed_post_updates()) +); +$connection->update('key_value') + ->fields(['value' => serialize($existing_updates)]) + ->condition('collection', 'post_update') + ->condition('name', 'existing_updates') + ->execute(); diff --git a/core/modules/responsive_image/tests/src/Functional/ResponsiveImageOrderMultipliersNumericallyUpdateTest.php b/core/modules/responsive_image/tests/src/Functional/ResponsiveImageOrderMultipliersNumericallyUpdateTest.php new file mode 100644 index 00000000000..7400d26ab74 --- /dev/null +++ b/core/modules/responsive_image/tests/src/Functional/ResponsiveImageOrderMultipliersNumericallyUpdateTest.php @@ -0,0 +1,60 @@ +<?php + +namespace Drupal\Tests\responsive_image\Functional; + +use Drupal\FunctionalTests\Update\UpdatePathTestBase; +use Drupal\responsive_image\Entity\ResponsiveImageStyle; + +/** + * Tests order multipliers numerically upgrade path. + * + * @group responsive_image + */ +class ResponsiveImageOrderMultipliersNumericallyUpdateTest extends UpdatePathTestBase { + + /** + * {@inheritdoc} + */ + protected function setDatabaseDumpFiles(): void { + $this->databaseDumpFiles = [ + __DIR__ . '/../../../../system/tests/fixtures/update/drupal-9.0.0.filled.standard.php.gz', + __DIR__ . '/../../fixtures/update/responsive_image.php', + __DIR__ . '/../../fixtures/update/responsive_image-order-multipliers-numerically.php', + ]; + } + + /** + * Test order multipliers numerically upgrade path. + * + * @see responsive_image_post_update_order_multiplier_numerically() + */ + public function testUpdate(): void { + $mappings = ResponsiveImageStyle::load('responsive_image_style')->getImageStyleMappings(); + $this->assertEquals('1.5x', $mappings[0]['multiplier']); + $this->assertEquals('2x', $mappings[1]['multiplier']); + $this->assertEquals('1x', $mappings[2]['multiplier']); + + $this->runUpdates(); + + $mappings = ResponsiveImageStyle::load('responsive_image_style')->getImageStyleMappings(); + $this->assertEquals('1x', $mappings[0]['multiplier']); + $this->assertEquals('1.5x', $mappings[1]['multiplier']); + $this->assertEquals('2x', $mappings[2]['multiplier']); + } + + public function testEntitySave(): void { + $image_style = ResponsiveImageStyle::load('responsive_image_style'); + $mappings = $image_style->getImageStyleMappings(); + $this->assertEquals('1.5x', $mappings[0]['multiplier']); + $this->assertEquals('2x', $mappings[1]['multiplier']); + $this->assertEquals('1x', $mappings[2]['multiplier']); + + $image_style->save(); + + $mappings = ResponsiveImageStyle::load('responsive_image_style')->getImageStyleMappings(); + $this->assertEquals('1x', $mappings[0]['multiplier']); + $this->assertEquals('1.5x', $mappings[1]['multiplier']); + $this->assertEquals('2x', $mappings[2]['multiplier']); + } + +} |