diff --git a/src/Umbraco.Cms.Api.Management/Factories/DictionaryPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/DictionaryPresentationFactory.cs index 65c97b113142..3462d4480273 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/DictionaryPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/DictionaryPresentationFactory.cs @@ -1,4 +1,5 @@ -using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Extensions; +using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Api.Management.Models; @@ -23,8 +24,7 @@ public async Task CreateDictionaryItemViewModelAsyn { DictionaryItemResponseModel dictionaryResponseModel = _umbracoMapper.Map(dictionaryItem)!; - var validLanguageIsoCodes = (await _languageService.GetAllAsync()) - .Select(language => language.IsoCode) + var validLanguageIsoCodes = (await _languageService.GetAllIsoCodesAsync()) .ToArray(); IDictionaryTranslation[] validTranslations = dictionaryItem.Translations .Where(t => validLanguageIsoCodes.Contains(t.LanguageIsoCode)) diff --git a/src/Umbraco.Cms.Api.Management/Factories/UserGroupPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/UserGroupPresentationFactory.cs index 099074aba5e5..b040d6334c80 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/UserGroupPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/UserGroupPresentationFactory.cs @@ -211,11 +211,7 @@ private static string CleanUserGroupNameOrAliasForXss(string input) private async Task, UserGroupOperationStatus>> MapLanguageIdsToIsoCodeAsync(IEnumerable ids) { - IEnumerable languages = await _languageService.GetAllAsync(); - string[] isoCodes = languages - .Where(x => ids.Contains(x.Id)) - .Select(x => x.IsoCode) - .ToArray(); + string[] isoCodes = await _languageService.GetIsoCodesByIdsAsync(ids.ToArray()); // if a language id does not exist, it simply not returned. // We do this so we don't have to clean up user group data when deleting languages and to make it easier to restore accidentally removed languages diff --git a/src/Umbraco.Core/Extensions/LanguageServiceExtensions.cs b/src/Umbraco.Core/Extensions/LanguageServiceExtensions.cs new file mode 100644 index 000000000000..982aa014972a --- /dev/null +++ b/src/Umbraco.Core/Extensions/LanguageServiceExtensions.cs @@ -0,0 +1,16 @@ +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Core.Extensions; + +/// +/// Provides extension methods for . +/// +public static class LanguageServiceExtensions +{ + /// + /// Retrieves all ISO codes of all languages. + /// + /// The . + /// A collection of language ISO codes. + public static async Task> GetAllIsoCodesAsync(this ILanguageService service) => (await service.GetAllAsync()).Select(x => x.IsoCode); +} diff --git a/src/Umbraco.Core/Persistence/Repositories/ILanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ILanguageRepository.cs index 849d789f3aa7..f0cc9ed73ec4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ILanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ILanguageRepository.cs @@ -38,5 +38,11 @@ public interface ILanguageRepository : IReadWriteQueryRepository /// int? GetDefaultId(); + /// + /// Gets multiple language ISO codes from the provided Ids. + /// + /// The language Ids. + /// Indicates whether to throw an exception if the provided Id is not found as a language. + /// string[] GetIsoCodesByIds(ICollection ids, bool throwOnNotFound = true); } diff --git a/src/Umbraco.Core/Routing/PublishedUrlInfoProvider.cs b/src/Umbraco.Core/Routing/PublishedUrlInfoProvider.cs index c861d9ba010d..c75108232e4a 100644 --- a/src/Umbraco.Core/Routing/PublishedUrlInfoProvider.cs +++ b/src/Umbraco.Core/Routing/PublishedUrlInfoProvider.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Extensions; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Services; @@ -42,7 +43,7 @@ public PublishedUrlInfoProvider( public async Task> GetAllAsync(IContent content) { HashSet urlInfos = []; - var cultures = (await _languageService.GetAllAsync()).Select(x => x.IsoCode).ToArray(); + IEnumerable cultures = await _languageService.GetAllIsoCodesAsync(); // First we get the urls of all cultures, using the published router, meaning we respect any extensions. foreach (var culture in cultures) diff --git a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs index a4e7fbc20905..782a0d1e5cc0 100644 --- a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Logging; using Umbraco.Cms.Core; using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Extensions; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; @@ -118,7 +119,7 @@ public static async Task> GetContentUrlsAsync( // and, not only for those assigned to domains in the branch, because we want // to show what GetUrl() would return, for every culture. var urls = new HashSet(); - var cultures = (await languageService.GetAllAsync()).Select(x => x.IsoCode).ToList(); + IEnumerable cultures = await languageService.GetAllIsoCodesAsync(); // get all URLs for all cultures // in a HashSet, so de-duplicates too diff --git a/src/Umbraco.Core/Services/ContentEditingService.cs b/src/Umbraco.Core/Services/ContentEditingService.cs index 00b3dddd254f..93d37a470a36 100644 --- a/src/Umbraco.Core/Services/ContentEditingService.cs +++ b/src/Umbraco.Core/Services/ContentEditingService.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Extensions; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Membership; @@ -104,8 +105,8 @@ public async Task x.IsoCode).ToList(); - return allowedCultures.Count == allCultures.Count ? null : allowedCultures; + IEnumerable allCultures = await _languageService.GetAllIsoCodesAsync(); + return allowedCultures.Count == allCultures.Count() ? null : allowedCultures; } // If explicit cultures are provided, we should only validate the ones the user has access to. diff --git a/src/Umbraco.Core/Services/ContentPublishingService.cs b/src/Umbraco.Core/Services/ContentPublishingService.cs index ddabe194484c..698efbda917c 100644 --- a/src/Umbraco.Core/Services/ContentPublishingService.cs +++ b/src/Umbraco.Core/Services/ContentPublishingService.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Extensions; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.ContentPublishing; @@ -165,7 +166,7 @@ public async Task validCultures = (await _languageService.GetAllAsync()).Select(x => x.IsoCode); + IEnumerable validCultures = await _languageService.GetAllIsoCodesAsync(); if (validCultures.ContainsAll(cultures) is false) { scope.Complete(); @@ -488,7 +489,7 @@ private async Task> UnpublishMultipleC return Attempt.Fail(ContentPublishingOperationStatus.CannotPublishVariantWhenNotVariant); } - var validCultures = (await _languageService.GetAllAsync()).Select(x => x.IsoCode).ToArray(); + var validCultures = (await _languageService.GetAllIsoCodesAsync()).ToArray(); foreach (var culture in cultures) { diff --git a/src/Umbraco.Core/Services/ContentValidationServiceBase.cs b/src/Umbraco.Core/Services/ContentValidationServiceBase.cs index 37b32847ad80..137962b84c1c 100644 --- a/src/Umbraco.Core/Services/ContentValidationServiceBase.cs +++ b/src/Umbraco.Core/Services/ContentValidationServiceBase.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; +using Umbraco.Cms.Core.Extensions; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.ContentEditing.Validation; @@ -137,7 +138,7 @@ public async Task ValidateCulturesAsync(ContentEditingModelBase contentEdi return invalidCultures.IsCollectionEmpty(); } - private async Task GetCultureCodes() => (await _languageService.GetAllAsync()).Select(language => language.IsoCode).ToArray(); + private async Task GetCultureCodes() => (await _languageService.GetAllIsoCodesAsync()).ToArray(); private IEnumerable ValidateProperty(IPropertyType propertyType, PropertyValueModel? propertyValueModel, PropertyValidationContext validationContext) { diff --git a/src/Umbraco.Core/Services/DocumentUrlService.cs b/src/Umbraco.Core/Services/DocumentUrlService.cs index c3d3904085f8..5d27f6d634bb 100644 --- a/src/Umbraco.Core/Services/DocumentUrlService.cs +++ b/src/Umbraco.Core/Services/DocumentUrlService.cs @@ -856,7 +856,7 @@ public async Task> ListUrlsAsync(Guid contentKey) .Concat(_contentService.GetAncestors(documentIdAttempt.Result).Select(x => x.Key).Reverse()); IEnumerable languages = await _languageService.GetAllAsync(); - var cultures = languages.ToDictionary(x=>x.IsoCode); + var cultures = languages.ToDictionary(x => x.IsoCode); Guid[] ancestorsOrSelfKeysArray = ancestorsOrSelfKeys as Guid[] ?? ancestorsOrSelfKeys.ToArray(); Dictionary>> ancestorOrSelfKeyToDomains = ancestorsOrSelfKeysArray diff --git a/src/Umbraco.Core/Services/ILanguageService.cs b/src/Umbraco.Core/Services/ILanguageService.cs index f1285b74a578..6ee7e5beba08 100644 --- a/src/Umbraco.Core/Services/ILanguageService.cs +++ b/src/Umbraco.Core/Services/ILanguageService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Core.Services; @@ -32,9 +32,9 @@ public interface ILanguageService Task GetDefaultIsoCodeAsync(); /// - /// Gets all available languages + /// Gets all available languages. /// - /// An enumerable list of objects + /// An enumerable list of objects. Task> GetAllAsync(); /// @@ -44,29 +44,29 @@ public interface ILanguageService Task> GetMultipleAsync(IEnumerable isoCodes); /// - /// Updates an existing object + /// Updates an existing object. /// /// to update /// Key of the user saving the language Task> UpdateAsync(ILanguage language, Guid userKey); /// - /// Creates a new object + /// Creates a new object. /// - /// to create - /// Key of the user creating the language + /// to create. + /// Key of the user creating the language. Task> CreateAsync(ILanguage language, Guid userKey); /// - /// Deletes a by removing it and its usages from the db + /// Deletes a by removing it and its usages from the database. /// - /// The ISO code of the to delete - /// Key of the user deleting the language + /// The ISO code of the to delete. + /// Key of the user deleting the language. Task> DeleteAsync(string isoCode, Guid userKey); /// - /// Retrieves the isoCodes of configured languages by their Ids + /// Retrieves the ISO codes of configured languages by their Ids. /// /// The ids of the configured s /// The ISO codes of the s diff --git a/src/Umbraco.Core/Services/LanguageService.cs b/src/Umbraco.Core/Services/LanguageService.cs index c815e490def9..691d0d7bd50d 100644 --- a/src/Umbraco.Core/Services/LanguageService.cs +++ b/src/Umbraco.Core/Services/LanguageService.cs @@ -1,4 +1,3 @@ -using System.Globalization; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs b/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs index 02ac583f8c9f..7dc9ccd17cba 100644 --- a/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs +++ b/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs @@ -11,6 +11,7 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Extensions; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; @@ -260,9 +261,7 @@ private bool BuildQuery(StringBuilder sb, string query, string? searchFrom, List // then nodeName will be matched normally with wildcards // the rest will be normal without wildcards - var allLangs = _languageService.GetAllAsync().GetAwaiter().GetResult() - .Select(x => x.IsoCode.ToLowerInvariant()) - .ToList(); + var allLangs = _languageService.GetAllIsoCodesAsync().GetAwaiter().GetResult().Select(x => x.ToLowerInvariant()).ToList(); // the chars [*-_] in the query will mess everything up so let's remove those // However we cannot just remove - and _ since these signify a space, so we instead replace them with that. diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexSearcherTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexSearcherTest.cs index d3f27e5c1647..31fa6adb3c43 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexSearcherTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/ExamineExternalIndexSearcherTest.cs @@ -9,12 +9,11 @@ using Lucene.Net.QueryParsers.Classic; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Extensions; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; -using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Examine; @@ -160,7 +159,7 @@ private async Task BuildQueryAsync(StringBuilder sb, string query, string? // then nodeName will be matched normally with wildcards // the rest will be normal without wildcards - var allLanguages = (await _languageService.GetAllAsync()).Select(x => x.IsoCode.ToLowerInvariant()).ToList(); + var allLanguages = (await _languageService.GetAllIsoCodesAsync()).Select(x => x.ToLowerInvariant()).ToList(); // the chars [*-_] in the query will mess everything up so let's remove those // However we cannot just remove - and _ since these signify a space, so we instead replace them with that. diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LanguageServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LanguageServiceTests.cs index 971051251531..85a177958504 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LanguageServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LanguageServiceTests.cs @@ -1,5 +1,6 @@ using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Extensions; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; @@ -28,6 +29,14 @@ public async Task Can_Get_All_Languages() Assert.That(languages.Count(), Is.EqualTo(3)); } + [Test] + public async Task Can_Get_All_Language_Iso_Codes() + { + var isoCodes = await LanguageService.GetAllIsoCodesAsync(); + Assert.That(isoCodes.Count(), Is.EqualTo(3)); + Assert.AreEqual("da-DK,en-GB,en-US", string.Join(",", isoCodes.OrderBy(x => x))); + } + [Test] public async Task Can_GetLanguageByIsoCode() {