diff --git a/composer.lock b/composer.lock index 831f747d3aa2fe79ae6405776aaf1978b71b0d92..06fdafe675ffb5e90d738d97b7865f566a923989 100644 --- a/composer.lock +++ b/composer.lock @@ -496,7 +496,7 @@ "dist": { "type": "path", "url": "core", - "reference": "7e8f42a2a16fa8db35c42d6ba0c7bcc9b3508588" + "reference": "71f1c41d5a7257b966c16db78005a9cf1e148557" }, "require": { "asm89/stack-cors": "^2.3", @@ -539,6 +539,7 @@ "symfony/mailer": "^7.2", "symfony/mime": "^7.2", "symfony/polyfill-iconv": "^1.26", + "symfony/polyfill-intl-icu": "^1.26", "symfony/process": "^7.2", "symfony/psr-http-message-bridge": "^7.2", "symfony/routing": "^7.2", @@ -577,6 +578,7 @@ "drupal/core-version": "self.version" }, "suggest": { + "ext-intl": "Needed to extend symfony/polyfill-intl-icu capability with the sorting of non-english languages.", "ext-zip": "Needed to extend the plugin.manager.archiver service capability with the handling of files in the ZIP format." }, "type": "drupal-core", @@ -3270,6 +3272,90 @@ ], "time": "2024-09-09T11:45:10+00:00" }, + { + "name": "symfony/polyfill-intl-icu", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-icu.git", + "reference": "d80a05e9904d2c2b9b95929f3e4b5d3a8f418d78" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/d80a05e9904d2c2b9b95929f3e4b5d3a8f418d78", + "reference": "d80a05e9904d2c2b9b95929f3e4b5d3a8f418d78", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance and support of other locales than \"en\"" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Icu\\": "" + }, + "classmap": [ + "Resources/stubs" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's ICU-related data and classes", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "icu", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, { "name": "symfony/polyfill-intl-idn", "version": "v1.31.0", diff --git a/composer/Metapackage/CoreRecommended/composer.json b/composer/Metapackage/CoreRecommended/composer.json index 695eec0d8ed5f882a5603fba19b3e56f924ef1ae..6544ac3850803025d18f2bc88ffda9aa0920fc61 100644 --- a/composer/Metapackage/CoreRecommended/composer.json +++ b/composer/Metapackage/CoreRecommended/composer.json @@ -48,6 +48,7 @@ "symfony/polyfill-ctype": "~v1.31.0", "symfony/polyfill-iconv": "~v1.31.0", "symfony/polyfill-intl-grapheme": "~v1.31.0", + "symfony/polyfill-intl-icu": "~v1.31.0", "symfony/polyfill-intl-idn": "~v1.31.0", "symfony/polyfill-intl-normalizer": "~v1.31.0", "symfony/polyfill-mbstring": "~v1.31.0", diff --git a/core/composer.json b/core/composer.json index 2ce89dd0c0e926dccb5fc15b7eb4dd5c11ced944..90cbbead9bab38936bb507b3264e645db0710a4d 100644 --- a/core/composer.json +++ b/core/composer.json @@ -33,6 +33,7 @@ "symfony/validator": "^7.2", "symfony/process": "^7.2", "symfony/polyfill-iconv": "^1.26", + "symfony/polyfill-intl-icu": "^1.26", "symfony/yaml": "^7.2", "revolt/event-loop": "^1.0", "twig/twig": "^3.15.0", @@ -120,6 +121,7 @@ "preferred-install": "dist" }, "suggest": { + "ext-intl": "Needed to extend symfony/polyfill-intl-icu capability with the sorting of non-english languages.", "ext-zip": "Needed to extend the plugin.manager.archiver service capability with the handling of files in the ZIP format." }, "extra": { diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php index c7edfa9395875c33bd6c941f36389df2e036c649..566a6b678a0ec671c9dc44ccdd3aeb35150d3282 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php @@ -232,10 +232,46 @@ public function createDuplicate() { return $duplicate; } + /** + * Sorts entities using collator. + */ + public static function sortEntities(array &$entities): bool { + // En is hardcoded because Symfony\Polyfill\Intl\Icu\Collator::create() is + // throwing an exception, if locale is not en. It is the only implemented + // language in the polyfill. + $collator = \Collator::create((!extension_loaded('intl')) ? ('en') : (\Drupal::service('language_manager')->getCurrentLanguage()->getId())); + return uasort($entities, function ($a, $b) use ($collator) { + return static::compare($a, $b, $collator); + }); + } + + /** + * Callback for uasort() to compare configuration entities. + */ + public static function compare(ConfigEntityInterface $a, ConfigEntityInterface $b, \Collator $collator): int { + $a_weight = $a->weight ?? 0; + $b_weight = $b->weight ?? 0; + if ($a_weight == $b_weight) { + $a_label = $a->label() ?? ''; + $b_label = $b->label() ?? ''; + if (!extension_loaded('intl')) { + return strnatcasecmp($a_label, $b_label); + } + return $collator->compare($a_label, $b_label); + } + return $a_weight <=> $b_weight; + } + /** * Callback for uasort() to sort configuration entities by weight and label. + * + * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use + * \Drupal\Core\Config\Entity\ConfigEntityBase::sortEntities() instead. + * + * @see https://www.drupal.org/project/drupal/issues/2265487 */ public static function sort(ConfigEntityInterface $a, ConfigEntityInterface $b) { + @trigger_error(__CLASS__ . '::' . __FUNCTION__ . ' is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use ' . __CLASS__ . '::sortEntities() instead. See https://www.drupal.org/project/drupal/issues/2265487', E_USER_DEPRECATED); $a_weight = $a->weight ?? 0; $b_weight = $b->weight ?? 0; if ($a_weight == $b_weight) { diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityListBuilder.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityListBuilder.php index 1c56bc080028dd44d0daba9ebfabd5a2858cbc27..7a717247248fc1ec3bf3515658b971b77ccf4146 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityListBuilder.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityListBuilder.php @@ -26,9 +26,8 @@ public function load() { $entity_ids = $this->getEntityIds(); $entities = $this->storage->loadMultipleOverrideFree($entity_ids); - // Sort the entities using the entity class's sort() method. - // See \Drupal\Core\Config\Entity\ConfigEntityBase::sort(). - uasort($entities, [$this->entityType->getClass(), 'sort']); + // Sort the entities using the entity class's sortEntities() method. + $this->entityType->getClass()::sortEntities($entities); return $entities; } diff --git a/core/lib/Drupal/Core/Datetime/Entity/DateFormat.php b/core/lib/Drupal/Core/Datetime/Entity/DateFormat.php index 8cbe76a2b95984b6b12b8b03ad6e198a170d0447..c0c1e84aa18f930fa5e2cb13a46bf8d21d69fb9a 100644 --- a/core/lib/Drupal/Core/Datetime/Entity/DateFormat.php +++ b/core/lib/Drupal/Core/Datetime/Entity/DateFormat.php @@ -82,9 +82,30 @@ public function isLocked() { } /** - * {@inheritdoc} + * Callback for uasort() to compare configuration entities. + */ + public static function compare(ConfigEntityInterface $a, ConfigEntityInterface $b, \Collator $collator): int { + if ($a->isLocked() == $b->isLocked()) { + $a_label = $a->label(); + $b_label = $b->label(); + if (!extension_loaded('intl')) { + return strnatcasecmp($a_label, $b_label); + } + return $collator->compare($a_label, $b_label); + } + return $a->isLocked() ? 1 : -1; + } + + /** + * Helper callback for uasort() to sort configuration entities. + * + * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use + * \Drupal\Core\Config\Entity\ConfigEntityBase::sortEntities() instead. + * + * @see https://www.drupal.org/project/drupal/issues/2265487 */ public static function sort(ConfigEntityInterface $a, ConfigEntityInterface $b) { + @trigger_error(__CLASS__ . '::' . __FUNCTION__ . ' is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use ' . __CLASS__ . '::sortEntities() instead. See https://www.drupal.org/project/drupal/issues/2265487', E_USER_DEPRECATED); if ($a->isLocked() == $b->isLocked()) { $a_label = $a->label(); $b_label = $b->label(); diff --git a/core/lib/Drupal/Core/Entity/EntityDisplayModeBase.php b/core/lib/Drupal/Core/Entity/EntityDisplayModeBase.php index ebb1a877f3efee7e64334a5da81d676b48030d40..c16f75d78243c5b2a4564ed2efa70e014de7ac57 100644 --- a/core/lib/Drupal/Core/Entity/EntityDisplayModeBase.php +++ b/core/lib/Drupal/Core/Entity/EntityDisplayModeBase.php @@ -58,9 +58,33 @@ abstract class EntityDisplayModeBase extends ConfigEntityBase implements EntityD protected $cache = TRUE; /** - * {@inheritdoc} + * Callback for uasort() to compare configuration entities. + */ + public static function compare(ConfigEntityInterface $a, ConfigEntityInterface $b, \Collator $collator): int { + /** @var \Drupal\Core\Entity\EntityDisplayModeInterface $a */ + /** @var \Drupal\Core\Entity\EntityDisplayModeInterface $b */ + // Sort by the type of entity the view mode is used for. + $a_type = $a->getTargetType(); + $b_type = $b->getTargetType(); + if (!extension_loaded('intl')) { + $type_order = strnatcasecmp($a_type, $b_type); + } + else { + $type_order = $collator->compare($a_type, $b_type); + } + return $type_order != 0 ? $type_order : parent::compare($a, $b, $collator); + } + + /** + * Helper callback for uasort() to sort configuration entities. + * + * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use + * \Drupal\Core\Config\Entity\ConfigEntityBase::sortEntities() instead. + * + * @see https://www.drupal.org/project/drupal/issues/2265487 */ public static function sort(ConfigEntityInterface $a, ConfigEntityInterface $b) { + @trigger_error(__CLASS__ . '::' . __FUNCTION__ . ' is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use ' . __CLASS__ . '::sortEntities() instead. See https://www.drupal.org/project/drupal/issues/2265487', E_USER_DEPRECATED); /** @var \Drupal\Core\Entity\EntityDisplayModeInterface $a */ /** @var \Drupal\Core\Entity\EntityDisplayModeInterface $b */ // Sort by the type of entity the view mode is used for. diff --git a/core/modules/block/src/BlockRepository.php b/core/modules/block/src/BlockRepository.php index e76743663430fe51927fb5e89bbb86eba0047fed..ba03ae77d06ab7d192e1be65d73bb1291a16f512 100644 --- a/core/modules/block/src/BlockRepository.php +++ b/core/modules/block/src/BlockRepository.php @@ -2,6 +2,7 @@ namespace Drupal\block; +use Drupal\block\Entity\Block; use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Plugin\Context\ContextHandlerInterface; @@ -78,7 +79,8 @@ public function getVisibleBlocksPerRegion(array &$cacheable_metadata = []) { // Merge it with the actual values to maintain the region ordering. $assignments = array_intersect_key(array_merge($empty, $full), $empty); foreach ($assignments as &$assignment) { - uasort($assignment, 'Drupal\block\Entity\Block::sort'); + // Sort the entities using the entity class's sortEntities() method. + Block::sortEntities($assignment); } return $assignments; } diff --git a/core/modules/block/src/Entity/Block.php b/core/modules/block/src/Entity/Block.php index 5e9c5f0709de9d578751c618858923ff765805d3..0e54bb634b22d734ab1b9ddb9b6ed2dccb3daa06 100644 --- a/core/modules/block/src/Entity/Block.php +++ b/core/modules/block/src/Entity/Block.php @@ -219,9 +219,38 @@ public function label() { } /** - * Sorts active blocks by weight; sorts inactive blocks by name. + * Helper callback for uasort() to compare configuration entities by weight and label. + */ + public static function compare(ConfigEntityInterface $a, ConfigEntityInterface $b, \Collator $collator): int { + // Separate enabled from disabled. + $status = (int) $b->status() - (int) $a->status(); + if ($status !== 0) { + return $status; + } + + // Sort by weight. + $weight = $a->getWeight() - $b->getWeight(); + if ($weight) { + return $weight; + } + + // Sort by label. + if (!extension_loaded('intl')) { + return strnatcasecmp($a->label(), $b->label()); + } + return $collator->compare($a->label(), $b->label()); + } + + /** + * Helper callback for uasort() to sort configuration entities. + * + * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use + * \Drupal\Core\Config\Entity\ConfigEntityBase::sortEntities() instead. + * + * @see https://www.drupal.org/project/drupal/issues/2265487 */ public static function sort(ConfigEntityInterface $a, ConfigEntityInterface $b) { + @trigger_error(__CLASS__ . '::' . __FUNCTION__ . ' is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use ' . __CLASS__ . '::sortEntities() instead. See https://www.drupal.org/project/drupal/issues/2265487', E_USER_DEPRECATED); // Separate enabled from disabled. $status = (int) $b->status() - (int) $a->status(); if ($status !== 0) { diff --git a/core/modules/block/tests/src/Unit/BlockRepositoryTest.php b/core/modules/block/tests/src/Unit/BlockRepositoryTest.php index f80c09565e6ade992a19cef4841467660ce6e3a7..1f99f4da7aa6d4bfaff4e69ea40e1101e9f8c611 100644 --- a/core/modules/block/tests/src/Unit/BlockRepositoryTest.php +++ b/core/modules/block/tests/src/Unit/BlockRepositoryTest.php @@ -6,7 +6,9 @@ use Drupal\block\BlockRepository; use Drupal\Core\Access\AccessResult; +use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Language\Language; use Drupal\Tests\UnitTestCase; /** @@ -63,6 +65,15 @@ protected function setUp(): void { 'bottom', ]); + $language_manager = $this->createMock('\Drupal\Core\Language\LanguageManagerInterface'); + $language_manager->expects($this->any()) + ->method('getCurrentLanguage') + ->with() + ->willReturn(new Language(['id' => 'en'])); + $container = new ContainerBuilder(); + $container->set('language_manager', $language_manager); + \Drupal::setContainer($container); + $theme_manager = $this->createMock('Drupal\Core\Theme\ThemeManagerInterface'); $theme_manager->expects($this->atLeastOnce()) ->method('getActiveTheme') diff --git a/core/modules/block_content/src/Controller/BlockContentController.php b/core/modules/block_content/src/Controller/BlockContentController.php index b2776f51d7d81736c08dd5171185a13ca57b1018..60a1a3ec3380c1c0cd8d9bcc3ff9f1ca4bb88ef9 100644 --- a/core/modules/block_content/src/Controller/BlockContentController.php +++ b/core/modules/block_content/src/Controller/BlockContentController.php @@ -85,7 +85,8 @@ public function add(Request $request) { $types[$type->id()] = $type; } } - uasort($types, [$this->blockContentTypeStorage->getEntityType()->getClass(), 'sort']); + // Sort the entities using the entity class's sortEntities() method. + $this->blockContentTypeStorage->getEntityType()->getClass()::sortEntities($types); if ($types && count($types) == 1) { $type = reset($types); return $this->addForm($type, $request); diff --git a/core/modules/config/tests/config_test/src/Entity/ConfigTest.php b/core/modules/config/tests/config_test/src/Entity/ConfigTest.php index 61ec27d9400bb950da2613771af7c96391915715..6b73e9251ec89c2310b97bedef48edf9b04f8509 100644 --- a/core/modules/config/tests/config_test/src/Entity/ConfigTest.php +++ b/core/modules/config/tests/config_test/src/Entity/ConfigTest.php @@ -101,13 +101,27 @@ class ConfigTest extends ConfigEntityBase implements ConfigTestInterface { protected array $array_property = []; /** - * {@inheritdoc} + * Helper callback for uasort() to sort configuration entities. + * + * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use + * \Drupal\Core\Config\Entity\ConfigEntityBase::sortEntities() instead. + * + * @see https://www.drupal.org/project/drupal/issues/2265487 */ public static function sort(ConfigEntityInterface $a, ConfigEntityInterface $b) { + @trigger_error(__CLASS__ . '::' . __FUNCTION__ . ' is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use ' . __CLASS__ . '::sortEntities() instead. See https://www.drupal.org/project/drupal/issues/2265487', E_USER_DEPRECATED); \Drupal::state()->set('config_entity_sort', TRUE); return parent::sort($a, $b); } + /** + * {@inheritdoc} + */ + public static function sortEntities(array &$entities): bool { + \Drupal::state()->set('config_entity_sortEntities', TRUE); + return parent::sortEntities($entities); + } + /** * {@inheritdoc} */ diff --git a/core/modules/config/tests/src/Functional/ConfigEntityListTest.php b/core/modules/config/tests/src/Functional/ConfigEntityListTest.php index dd4caf036c796d5b8ca5cf8e743298914bb67ea5..e8ef7138a543a34d8bdad0874fbf6287c09836b1 100644 --- a/core/modules/config/tests/src/Functional/ConfigEntityListTest.php +++ b/core/modules/config/tests/src/Functional/ConfigEntityListTest.php @@ -260,7 +260,7 @@ public function testListUI(): void { $this->submitForm($edit, 'Save'); // Ensure that the entity's sort method was called. - $this->assertTrue(\Drupal::state()->get('config_entity_sort'), 'ConfigTest::sort() was called.'); + $this->assertTrue(\Drupal::state()->get('config_entity_sortEntities'), 'ConfigTest::sortEntities() was called.'); // Confirm that the user is returned to the listing, and verify that the // text of the label and machine name appears in the list (versus elsewhere diff --git a/core/modules/field_ui/src/FieldConfigListBuilder.php b/core/modules/field_ui/src/FieldConfigListBuilder.php index 92bf9a3ff1b6f7952fa3b4d9930c94fa5dc3e6a0..66da0a4bbe53984ce1f3aaf7dad40a10dfd4587f 100644 --- a/core/modules/field_ui/src/FieldConfigListBuilder.php +++ b/core/modules/field_ui/src/FieldConfigListBuilder.php @@ -108,9 +108,8 @@ public function load() { return $field_definition instanceof FieldConfigInterface; }); - // Sort the entities using the entity class's sort() method. - // See \Drupal\Core\Config\Entity\ConfigEntityBase::sort(). - uasort($entities, [$this->entityType->getClass(), 'sort']); + // Sort the entities using the entity class's sortEntities() method. + $this->entityType->getClass()::sortEntities($entities); return $entities; } diff --git a/core/modules/filter/filter.module b/core/modules/filter/filter.module index 89b8e2e1e6f9b7c2e8433ccd617611557a6e25ee..83614f70d265ed24526837edb617337ecc711793 100644 --- a/core/modules/filter/filter.module +++ b/core/modules/filter/filter.module @@ -7,6 +7,7 @@ use Drupal\Component\Utility\Html; use Drupal\Component\Utility\Unicode; use Drupal\Core\Cache\Cache; +use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Template\Attribute; use Drupal\filter\FilterFormatInterface; @@ -38,7 +39,8 @@ function filter_formats(?AccountInterface $account = NULL) { } else { $formats['all'] = \Drupal::entityTypeManager()->getStorage('filter_format')->loadByProperties(['status' => TRUE]); - uasort($formats['all'], 'Drupal\Core\Config\Entity\ConfigEntityBase::sort'); + // Sort the entities using the entity class's sortEntities() method. + ConfigEntityBase::sortEntities($formats['all']); \Drupal::cache()->set("filter_formats:{$language_interface->getId()}", $formats['all'], Cache::PERMANENT, \Drupal::entityTypeManager()->getDefinition('filter_format')->getListCacheTags()); } } diff --git a/core/modules/filter/src/FilterPermissions.php b/core/modules/filter/src/FilterPermissions.php index 3e64fe751a8890c69be1748370c3e903dcf48834..41c5a1333e0d0cde5ea03ad5a2d92220f310ab37 100644 --- a/core/modules/filter/src/FilterPermissions.php +++ b/core/modules/filter/src/FilterPermissions.php @@ -2,6 +2,7 @@ namespace Drupal\filter; +use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; @@ -50,7 +51,8 @@ public function permissions() { // any of them are potentially unsafe. /** @var \Drupal\filter\FilterFormatInterface[] $formats */ $formats = $this->entityTypeManager->getStorage('filter_format')->loadByProperties(['status' => TRUE]); - uasort($formats, 'Drupal\Core\Config\Entity\ConfigEntityBase::sort'); + // Sort the entities using the entity class's sortEntities() method. + ConfigEntityBase::sortEntities($formats); foreach ($formats as $format) { if ($permission = $format->getPermissionName()) { $permissions[$permission] = [ diff --git a/core/modules/language/src/LanguageListBuilder.php b/core/modules/language/src/LanguageListBuilder.php index 25cd63d87234c50d54ed6d382d0fe7a4d48ebc64..926c9482ce1e4bd86c3f884af0efb4579c89eaa0 100644 --- a/core/modules/language/src/LanguageListBuilder.php +++ b/core/modules/language/src/LanguageListBuilder.php @@ -85,9 +85,8 @@ public function __construct(EntityTypeInterface $entity_type, EntityStorageInter public function load() { $entities = $this->storage->loadByProperties(['locked' => FALSE]); - // Sort the entities using the entity class's sort() method. - // See \Drupal\Core\Config\Entity\ConfigEntityBase::sort(). - uasort($entities, [$this->entityType->getClass(), 'sort']); + // Sort the entities using the entity class's sortEntities() method. + $this->entityType->getClass()::sortEntities($entities); return $entities; } diff --git a/core/modules/menu_ui/tests/src/Functional/MenuUiTest.php b/core/modules/menu_ui/tests/src/Functional/MenuUiTest.php index f7f811c5e517be3809b1820ecc8447c8c32f3bef..13693db95154b3d8af85b2a6906dbad6a58b4b7d 100644 --- a/core/modules/menu_ui/tests/src/Functional/MenuUiTest.php +++ b/core/modules/menu_ui/tests/src/Functional/MenuUiTest.php @@ -193,7 +193,8 @@ public function testMenuAdministration(): void { $menu_entities[] = $menu_entity; $menu_entity->save(); } - uasort($menu_entities, [Menu::class, 'sort']); + // Sort the entities using the entity class's sortEntities() method. + Menu::sortEntities($menu_entities); $menu_entities = array_values($menu_entities); $this->drupalGet('/admin/structure/menu'); $base_path = parse_url($this->baseUrl, PHP_URL_PATH) ?? ''; @@ -217,7 +218,8 @@ public function testMenuAdministration(): void { // natural-sort on the ones that are on page 1. sort($menu_entities); $menu_entities_page_one = array_slice($menu_entities, 50, 64, TRUE); - uasort($menu_entities_page_one, [Menu::class, 'sort']); + // Sort the entities using the entity class's sortEntities() method. + Menu::sortEntities($menu_entities_page_one); $menu_entities_page_one = array_values($menu_entities_page_one); $this->drupalGet('/admin/structure/menu', [ diff --git a/core/modules/navigation/src/NavigationContentLinks.php b/core/modules/navigation/src/NavigationContentLinks.php index 7e7d00cb4e5d1c67d7037548029d0e38cd80e5c7..5361ea3da897196f2d8909ea905a5aeb3d7ece15 100644 --- a/core/modules/navigation/src/NavigationContentLinks.php +++ b/core/modules/navigation/src/NavigationContentLinks.php @@ -123,8 +123,9 @@ private function addCreateEntityLinks(string $entity_type, string $add_route_id, // Sort all types within an entity type alphabetically. $definition = $this->entityTypeManager->getDefinition($entity_type); $types = $this->entityTypeManager->getStorage($entity_type)->loadMultiple(); - if (method_exists($definition->getClass(), 'sort')) { - uasort($types, [$definition->getClass(), 'sort']); + if (method_exists($definition->getClass(), 'sortEntities')) { + // Sort the entities using the entity class's sortEntities() method. + $definition->getClass()::sortEntities($types); } $add_content_links = []; diff --git a/core/modules/node/src/Controller/NodeController.php b/core/modules/node/src/Controller/NodeController.php index 87c9586daee00c3a86be09b2b4a07185b1751577..062962a203b35e45f29878bb404e52f914df53ae 100644 --- a/core/modules/node/src/Controller/NodeController.php +++ b/core/modules/node/src/Controller/NodeController.php @@ -79,7 +79,8 @@ public function addPage() { $content = []; $types = $this->entityTypeManager()->getStorage('node_type')->loadMultiple(); - uasort($types, [$definition->getClass(), 'sort']); + // Sort the entities using the entity class's sortEntities() method. + $definition->getClass()::sortEntities($types); // Only use node types the user has access to. foreach ($types as $type) { $access = $this->entityTypeManager()->getAccessControlHandler('node')->createAccess($type->id(), NULL, [], TRUE); diff --git a/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php b/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php index 49fe15103e1d7dfbb736c454044a9e3bf92976dc..c53062e28d6d0017505772c8eaa1f0180a4057fa 100644 --- a/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php +++ b/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php @@ -130,7 +130,8 @@ public function settingsForm(array $form, FormStateInterface $form_state) { $responsive_image_options = []; $responsive_image_styles = $this->responsiveImageStyleStorage->loadMultiple(); - uasort($responsive_image_styles, '\Drupal\responsive_image\Entity\ResponsiveImageStyle::sort'); + // Sort the entities using the entity class's sortEntities() method. + ResponsiveImageStyle::sortEntities($responsive_image_styles); if ($responsive_image_styles && !empty($responsive_image_styles)) { foreach ($responsive_image_styles as $machine_name => $responsive_image_style) { if ($responsive_image_style->hasImageStyleMappings()) { diff --git a/core/modules/search/src/Entity/SearchPage.php b/core/modules/search/src/Entity/SearchPage.php index 894da73bbac0259de6c6b8cefc10720ce2a3bfb4..e05c7616ee80d3cc5382b7eabc918e00e35126da 100644 --- a/core/modules/search/src/Entity/SearchPage.php +++ b/core/modules/search/src/Entity/SearchPage.php @@ -213,10 +213,25 @@ public static function postDelete(EntityStorageInterface $storage, array $entiti } } + /** + * Helper callback for uasort() to compare configuration entities by weight and label. + */ + public static function compare(ConfigEntityInterface $a, ConfigEntityInterface $b, \Collator $collator): int { + /** @var \Drupal\search\SearchPageInterface $a */ + /** @var \Drupal\search\SearchPageInterface $b */ + $a_status = (int) $a->status(); + $b_status = (int) $b->status(); + if ($a_status != $b_status) { + return $b_status <=> $a_status; + } + return parent::compare($a, $b, $collator); + } + /** * Helper callback for uasort() to sort search page entities by status, weight and label. */ public static function sort(ConfigEntityInterface $a, ConfigEntityInterface $b) { + @trigger_error(__CLASS__ . '::' . __FUNCTION__ . ' is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use ' . __CLASS__ . '::sortEntities() instead. See https://www.drupal.org/project/drupal/issues/2265487', E_USER_DEPRECATED); /** @var \Drupal\search\SearchPageInterface $a */ /** @var \Drupal\search\SearchPageInterface $b */ $a_status = (int) $a->status(); diff --git a/core/modules/search/src/SearchPageRepository.php b/core/modules/search/src/SearchPageRepository.php index 97606d42a0275748b976b3ff2a228db9d319b57a..c6019b515d9555db2c05eb6280845bcf195b826f 100644 --- a/core/modules/search/src/SearchPageRepository.php +++ b/core/modules/search/src/SearchPageRepository.php @@ -105,7 +105,8 @@ public function setDefaultSearchPage(SearchPageInterface $search_page) { */ public function sortSearchPages($search_pages) { $entity_type = $this->storage->getEntityType(); - uasort($search_pages, [$entity_type->getClass(), 'sort']); + // Sort the entities using the entity class's sortEntities() method. + $entity_type->getClass()::sortEntities($search_pages); return $search_pages; } diff --git a/core/modules/search/tests/src/Unit/SearchPageRepositoryTest.php b/core/modules/search/tests/src/Unit/SearchPageRepositoryTest.php index fdc9754f3bd1335e4cb8c956e69f17d464cfdc85..39e522862d86afdfd837223847614e416f31531c 100644 --- a/core/modules/search/tests/src/Unit/SearchPageRepositoryTest.php +++ b/core/modules/search/tests/src/Unit/SearchPageRepositoryTest.php @@ -4,8 +4,10 @@ namespace Drupal\Tests\search\Unit; +use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Language\Language; use Drupal\search\Entity\SearchPage; use Drupal\search\SearchPageRepository; use Drupal\Tests\UnitTestCase; @@ -49,6 +51,14 @@ class SearchPageRepositoryTest extends UnitTestCase { */ protected function setUp(): void { parent::setUp(); + $language_manager = $this->createMock('\Drupal\Core\Language\LanguageManagerInterface'); + $language_manager->expects($this->any()) + ->method('getCurrentLanguage') + ->with() + ->willReturn(new Language(['id' => 'en'])); + $container = new ContainerBuilder(); + $container->set('language_manager', $language_manager); + \Drupal::setContainer($container); $this->query = $this->createMock('Drupal\Core\Entity\Query\QueryInterface'); diff --git a/core/modules/shortcut/src/Entity/Shortcut.php b/core/modules/shortcut/src/Entity/Shortcut.php index 826fd5dfeec178a78cb3ed42ff966dfca71c388a..c84cb198f4e26160c9c99a534e6038d794e12d30 100644 --- a/core/modules/shortcut/src/Entity/Shortcut.php +++ b/core/modules/shortcut/src/Entity/Shortcut.php @@ -173,19 +173,43 @@ public function getCacheTagsToInvalidate() { } /** - * Sort shortcut objects. - * - * Callback for uasort(). + * Sorts entities using collator. + */ + public static function sortEntities(array &$entities): bool { + // En is hardcoded because Symfony\Polyfill\Intl\Icu\Collator::create() is + // throwing an exception, if locale is not en. It is the only implemented + // language in the polyfill. + $collator = \Collator::create((!extension_loaded('intl')) ? ('en') : (\Drupal::service('language_manager')->getCurrentLanguage()->getId())); + return uasort($entities, function ($a, $b) use ($collator) { + return static::compare($a, $b, $collator); + }); + } + + /** + * Helper callback for uasort() to compare configuration entities by weight and label. + */ + public static function compare(ShortcutInterface $a, ShortcutInterface $b, \Collator $collator): int { + $a_weight = $a->getWeight(); + $b_weight = $b->getWeight(); + if ($a_weight == $b_weight) { + if (!extension_loaded('intl')) { + return strnatcasecmp($a->getTitle(), $b->getTitle()); + } + return $collator->compare($a->getTitle(), $b->getTitle()); + } + return $a_weight <=> $b_weight; + } + + /** + * Helper callback for uasort() to sort configuration entities by weight and label. * - * @param \Drupal\shortcut\ShortcutInterface $a - * First item for comparison. - * @param \Drupal\shortcut\ShortcutInterface $b - * Second item for comparison. + * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use + * \Drupal\Core\Config\Entity\ConfigEntityBase::sortEntities() instead. * - * @return int - * The comparison result for uasort(). + * @see https://www.drupal.org/project/drupal/issues/2265487 */ - public static function sort(ShortcutInterface $a, ShortcutInterface $b) { + public static function sort(ShortcutInterface $a, ShortcutInterface $b): int { + @trigger_error(__CLASS__ . '::' . __FUNCTION__ . ' is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use ' . __CLASS__ . '::sortEntities() instead. See https://www.drupal.org/project/drupal/issues/2265487', E_USER_DEPRECATED); $a_weight = $a->getWeight(); $b_weight = $b->getWeight(); if ($a_weight == $b_weight) { diff --git a/core/modules/shortcut/src/Entity/ShortcutSet.php b/core/modules/shortcut/src/Entity/ShortcutSet.php index 20bcecb73bea0cfd17afc1b88507cdb7df60439f..794ed7e0e2af291e6259b01b9fc34344b5aefeec 100644 --- a/core/modules/shortcut/src/Entity/ShortcutSet.php +++ b/core/modules/shortcut/src/Entity/ShortcutSet.php @@ -131,7 +131,8 @@ public function resetLinkWeights() { */ public function getShortcuts() { $shortcuts = \Drupal::entityTypeManager()->getStorage('shortcut')->loadByProperties(['shortcut_set' => $this->id()]); - uasort($shortcuts, ['\Drupal\shortcut\Entity\Shortcut', 'sort']); + // Sort the entities using the entity class's sortEntities() method. + Shortcut::sortEntities($shortcuts); return $shortcuts; } diff --git a/core/modules/system/src/Entity/Action.php b/core/modules/system/src/Entity/Action.php index 46cebac0ffaf04982f84c522c23cd5295594fe82..993591ec6f014037ae04d150c5198f4faa496b3b 100644 --- a/core/modules/system/src/Entity/Action.php +++ b/core/modules/system/src/Entity/Action.php @@ -168,9 +168,32 @@ public function getType() { } /** - * {@inheritdoc} + * Helper callback for uasort() to compare configuration entities by weight and label. + */ + public static function compare(ConfigEntityInterface $a, ConfigEntityInterface $b, \Collator $collator): int { + /** @var \Drupal\system\ActionConfigEntityInterface $a */ + /** @var \Drupal\system\ActionConfigEntityInterface $b */ + $a_type = $a->getType(); + $b_type = $b->getType(); + if ($a_type != $b_type) { + if (!extension_loaded('intl')) { + return strnatcasecmp($a_type, $b_type); + } + return $collator->compare($a_type, $b_type); + } + return parent::compare($a, $b, $collator); + } + + /** + * Helper callback for uasort() to sort configuration entities. + * + * @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use + * \Drupal\Core\Config\Entity\ConfigEntityBase::sortEntities() instead. + * + * @see https://www.drupal.org/project/drupal/issues/2265487 */ public static function sort(ConfigEntityInterface $a, ConfigEntityInterface $b) { + @trigger_error(__CLASS__ . '::' . __FUNCTION__ . ' is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use ' . __CLASS__ . '::sortEntities() instead. See https://www.drupal.org/project/drupal/issues/2265487', E_USER_DEPRECATED); /** @var \Drupal\system\ActionConfigEntityInterface $a */ /** @var \Drupal\system\ActionConfigEntityInterface $b */ $a_type = $a->getType(); diff --git a/core/modules/user/src/Entity/Role.php b/core/modules/user/src/Entity/Role.php index 2384e6ff373e1c31ea99fe1df456b9ac19925275..6a551e71a32b311b42b2615672efe47f8156b9a4 100644 --- a/core/modules/user/src/Entity/Role.php +++ b/core/modules/user/src/Entity/Role.php @@ -175,9 +175,8 @@ public function setIsAdmin($is_admin) { */ public static function postLoad(EntityStorageInterface $storage, array &$entities) { parent::postLoad($storage, $entities); - // Sort the queried roles by their weight. - // See \Drupal\Core\Config\Entity\ConfigEntityBase::sort(). - uasort($entities, [static::class, 'sort']); + // Sort the entities using the entity class's sortEntities() method. + static::class::sortEntities($entities); } /** diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php index abbe66c964f3179fb0611d2de1c828abe75e99ad..9cb6df6058aa107f7006f4b949a5015cdda42101 100644 --- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php +++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php @@ -142,6 +142,10 @@ protected function setUp(): void { ->method('getLanguage') ->with('en') ->willReturn(new Language(['id' => 'en'])); + $this->languageManager->expects($this->any()) + ->method('getCurrentLanguage') + ->with() + ->willReturn(new Language(['id' => 'en'])); $this->cacheTagsInvalidator = $this->createMock('Drupal\Core\Cache\CacheTagsInvalidatorInterface'); @@ -529,23 +533,23 @@ public function testSort(): void { // Test sorting by label. $list = [$entity_a, $entity_b]; - usort($list, '\Drupal\Core\Config\Entity\ConfigEntityBase::sort'); - $this->assertSame($entity_b, $list[0]); + ConfigEntityBase::sortEntities($list); + $this->assertSame($entity_b, reset($list)); $list = [$entity_b, $entity_a]; - usort($list, '\Drupal\Core\Config\Entity\ConfigEntityBase::sort'); - $this->assertSame($entity_b, $list[0]); + ConfigEntityBase::sortEntities($list); + $this->assertSame($entity_b, reset($list)); // Test sorting by weight. $entity_a->weight = 0; $entity_b->weight = 1; $list = [$entity_b, $entity_a]; - usort($list, '\Drupal\Core\Config\Entity\ConfigEntityBase::sort'); - $this->assertSame($entity_a, $list[0]); + ConfigEntityBase::sortEntities($list); + $this->assertSame($entity_a, reset($list)); $list = [$entity_a, $entity_b]; - usort($list, '\Drupal\Core\Config\Entity\ConfigEntityBase::sort'); - $this->assertSame($entity_a, $list[0]); + ConfigEntityBase::sortEntities($list); + $this->assertSame($entity_a, reset($list)); } /** diff --git a/core/tests/Drupal/Tests/Core/Datetime/DrupalDateTimeTest.php b/core/tests/Drupal/Tests/Core/Datetime/DrupalDateTimeTest.php index e1ec84395f9d9dd866a67db57370368f5a5e6593..9ce747b492eeb93372f016e33a8461d7d2f7d533 100644 --- a/core/tests/Drupal/Tests/Core/Datetime/DrupalDateTimeTest.php +++ b/core/tests/Drupal/Tests/Core/Datetime/DrupalDateTimeTest.php @@ -4,6 +4,7 @@ namespace Drupal\Tests\Core\Datetime; +use Drupal\Component\DependencyInjection\ReverseContainer; use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\Language\Language; use Drupal\Core\Language\LanguageManager; @@ -16,6 +17,17 @@ */ class DrupalDateTimeTest extends UnitTestCase { + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + + $container = new ContainerBuilder(); + $container->set('Drupal\Component\DependencyInjection\ReverseContainer', new ReverseContainer($container)); + \Drupal::setContainer($container); + } + /** * Tests date diffs. *