diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Culture/AllCultureController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Culture/AllCultureController.cs index f1ff94cce7..638237658a 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Culture/AllCultureController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Culture/AllCultureController.cs @@ -20,14 +20,19 @@ public class AllCultureController : CultureControllerBase [HttpGet] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] - public async Task> GetAll(int skip, int take) + public async Task> GetAll(int skip = 0, int take = 100) { - IEnumerable list = CultureInfo.GetCultures(CultureTypes.AllCultures) + CultureInfo[] all = CultureInfo.GetCultures(CultureTypes.AllCultures) .DistinctBy(x => x.Name) .OrderBy(x => x.EnglishName) - .Skip(skip) - .Take(take); + .ToArray(); - return await Task.FromResult(_umbracoMapper.Map>(list)!); + var viewModel = new PagedViewModel + { + Items = _umbracoMapper.MapEnumerable(all.Skip(skip).Take(take)), + Total = all.Length + }; + + return await Task.FromResult(viewModel); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/AllDictionaryController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/AllDictionaryController.cs index 4415a5ff5f..a8abc11540 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/AllDictionaryController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/AllDictionaryController.cs @@ -10,28 +10,22 @@ namespace Umbraco.Cms.Api.Management.Controllers.Dictionary; public class AllDictionaryController : DictionaryControllerBase { - private readonly ILocalizationService _localizationService; + private readonly IDictionaryItemService _dictionaryItemService; private readonly IUmbracoMapper _umbracoMapper; - public AllDictionaryController(ILocalizationService localizationService, IUmbracoMapper umbracoMapper) + public AllDictionaryController(IDictionaryItemService dictionaryItemService, IUmbracoMapper umbracoMapper) { - _localizationService = localizationService; + _dictionaryItemService = dictionaryItemService; _umbracoMapper = umbracoMapper; } - - /// - /// Retrieves a list with all dictionary items - /// - /// - /// The . - /// [HttpGet] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] - public async Task>> All(int skip, int take) + // FIXME: make this action slim (move logic somewhere else) + public async Task>> All(int skip = 0, int take = 100) { - IDictionaryItem[] items = _localizationService.GetDictionaryItemDescendants(null).ToArray(); + IDictionaryItem[] items = (await _dictionaryItemService.GetDescendantsAsync(null)).ToArray(); var list = new List(items.Length); // Build the proper tree structure, as we can have nested dictionary items diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/ByKeyDictionaryController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/ByKeyDictionaryController.cs index 4e1ece8db3..7578a8a2af 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/ByKeyDictionaryController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/ByKeyDictionaryController.cs @@ -9,12 +9,12 @@ namespace Umbraco.Cms.Api.Management.Controllers.Dictionary; public class ByKeyDictionaryController : DictionaryControllerBase { - private readonly ILocalizationService _localizationService; + private readonly IDictionaryItemService _dictionaryItemService; private readonly IDictionaryFactory _dictionaryFactory; - public ByKeyDictionaryController(ILocalizationService localizationService, IDictionaryFactory dictionaryFactory) + public ByKeyDictionaryController(IDictionaryItemService dictionaryItemService, IDictionaryFactory dictionaryFactory) { - _localizationService = localizationService; + _dictionaryItemService = dictionaryItemService; _dictionaryFactory = dictionaryFactory; } @@ -24,12 +24,12 @@ public class ByKeyDictionaryController : DictionaryControllerBase [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> ByKey(Guid key) { - IDictionaryItem? dictionary = _localizationService.GetDictionaryItemById(key); + IDictionaryItem? dictionary = await _dictionaryItemService.GetAsync(key); if (dictionary == null) { return NotFound(); } - return await Task.FromResult(Ok(_dictionaryFactory.CreateDictionaryItemViewModel(dictionary))); + return Ok(await _dictionaryFactory.CreateDictionaryItemViewModelAsync(dictionary)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/CreateDictionaryController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/CreateDictionaryController.cs index 76e78f4f83..cf8406eeea 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/CreateDictionaryController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/CreateDictionaryController.cs @@ -12,16 +12,16 @@ namespace Umbraco.Cms.Api.Management.Controllers.Dictionary; public class CreateDictionaryController : DictionaryControllerBase { - private readonly ILocalizationService _localizationService; + private readonly IDictionaryItemService _dictionaryItemService; private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly IDictionaryFactory _dictionaryFactory; public CreateDictionaryController( - ILocalizationService localizationService, + IDictionaryItemService dictionaryItemService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IDictionaryFactory dictionaryFactory) { - _localizationService = localizationService; + _dictionaryItemService = dictionaryItemService; _backOfficeSecurityAccessor = backOfficeSecurityAccessor; _dictionaryFactory = dictionaryFactory; } @@ -34,19 +34,13 @@ public class CreateDictionaryController : DictionaryControllerBase [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status409Conflict)] public async Task Create(DictionaryItemCreateModel dictionaryItemCreateModel) { - IEnumerable translations = _dictionaryFactory.MapTranslations(dictionaryItemCreateModel.Translations); + IDictionaryItem created = await _dictionaryFactory.MapCreateModelToDictionaryItemAsync(dictionaryItemCreateModel); - Attempt result = _localizationService.Create( - dictionaryItemCreateModel.Name, - dictionaryItemCreateModel.ParentKey, - translations, - CurrentUserId(_backOfficeSecurityAccessor)); + Attempt result = + await _dictionaryItemService.CreateAsync(created, CurrentUserId(_backOfficeSecurityAccessor)); - if (result.Success) - { - return await Task.FromResult(CreatedAtAction(controller => nameof(controller.ByKey), result.Result!.Key)); - } - - return DictionaryItemOperationStatusResult(result.Status); + return result.Success + ? CreatedAtAction(controller => nameof(controller.ByKey), result.Result!.Key) + : DictionaryItemOperationStatusResult(result.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/DeleteDictionaryController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/DeleteDictionaryController.cs index 7e86c5743a..b66965fb6b 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/DeleteDictionaryController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/DeleteDictionaryController.cs @@ -10,12 +10,12 @@ namespace Umbraco.Cms.Api.Management.Controllers.Dictionary; public class DeleteDictionaryController : DictionaryControllerBase { - private readonly ILocalizationService _localizationService; + private readonly IDictionaryItemService _dictionaryItemService; private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; - public DeleteDictionaryController(ILocalizationService localizationService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor) + public DeleteDictionaryController(IDictionaryItemService dictionaryItemService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor) { - _localizationService = localizationService; + _dictionaryItemService = dictionaryItemService; _backOfficeSecurityAccessor = backOfficeSecurityAccessor; } @@ -26,12 +26,10 @@ public class DeleteDictionaryController : DictionaryControllerBase [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task Delete(Guid key) { - Attempt result = _localizationService.Delete(key, CurrentUserId(_backOfficeSecurityAccessor)); - if (result.Success) - { - return await Task.FromResult(Ok()); - } + Attempt result = await _dictionaryItemService.DeleteAsync(key, CurrentUserId(_backOfficeSecurityAccessor)); - return DictionaryItemOperationStatusResult(result.Status); + return result.Success + ? Ok() + : DictionaryItemOperationStatusResult(result.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/ExportDictionaryController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/ExportDictionaryController.cs index 1b0236af93..9821f923c3 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/ExportDictionaryController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/ExportDictionaryController.cs @@ -11,6 +11,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.Dictionary; public class ExportDictionaryController : DictionaryControllerBase { + // FIXME: use IDictionaryItemService instead of ILocalizationService private readonly ILocalizationService _localizationService; private readonly IEntityXmlSerializer _entityXmlSerializer; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/ChildrenDictionaryTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/ChildrenDictionaryTreeController.cs index 8dfa33835f..e3e2aa59e1 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/ChildrenDictionaryTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/ChildrenDictionaryTreeController.cs @@ -10,8 +10,8 @@ namespace Umbraco.Cms.Api.Management.Controllers.Dictionary.Tree; public class ChildrenDictionaryTreeController : DictionaryTreeControllerBase { - public ChildrenDictionaryTreeController(IEntityService entityService, ILocalizationService localizationService) - : base(entityService, localizationService) + public ChildrenDictionaryTreeController(IEntityService entityService, IDictionaryItemService dictionaryItemService) + : base(entityService, dictionaryItemService) { } @@ -28,10 +28,10 @@ public class ChildrenDictionaryTreeController : DictionaryTreeControllerBase IDictionaryItem[] dictionaryItems = PaginatedDictionaryItems( pageNumber, pageSize, - LocalizationService.GetDictionaryItemChildren(parentKey), + await DictionaryItemService.GetChildrenAsync(parentKey), out var totalItems); - EntityTreeItemViewModel[] viewModels = MapTreeItemViewModels(null, dictionaryItems); + EntityTreeItemViewModel[] viewModels = await MapTreeItemViewModels(null, dictionaryItems); PagedViewModel result = PagedViewModel(viewModels, totalItems); return await Task.FromResult(Ok(result)); diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/DictionaryTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/DictionaryTreeControllerBase.cs index b2641ddf8d..4c15dced84 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/DictionaryTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/DictionaryTreeControllerBase.cs @@ -16,29 +16,42 @@ namespace Umbraco.Cms.Api.Management.Controllers.Dictionary.Tree; // tree controller base. We'll keep it though, in the hope that we can mend EntityService. public class DictionaryTreeControllerBase : EntityTreeControllerBase { - public DictionaryTreeControllerBase(IEntityService entityService, ILocalizationService localizationService) + public DictionaryTreeControllerBase(IEntityService entityService, IDictionaryItemService dictionaryItemService) : base(entityService) => - LocalizationService = localizationService; + DictionaryItemService = dictionaryItemService; // dictionary items do not currently have a known UmbracoObjectType, so we'll settle with Unknown for now protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.Unknown; - protected ILocalizationService LocalizationService { get; } + protected IDictionaryItemService DictionaryItemService { get; } - protected EntityTreeItemViewModel[] MapTreeItemViewModels(Guid? parentKey, IDictionaryItem[] dictionaryItems) - => dictionaryItems.Select(dictionaryItem => new EntityTreeItemViewModel + protected async Task MapTreeItemViewModels(Guid? parentKey, IDictionaryItem[] dictionaryItems) + { + async Task CreateEntityTreeItemViewModelAsync(IDictionaryItem dictionaryItem) { - Icon = Constants.Icons.RelationType, - Name = dictionaryItem.ItemKey, - Key = dictionaryItem.Key, - Type = Constants.UdiEntityType.DictionaryItem, - // FIXME - do not hardcode HasChildren to false for all dictionary items - HasChildren = false, - IsContainer = LocalizationService.GetDictionaryItemChildren(dictionaryItem.Key).Any(), - ParentKey = parentKey - }).ToArray(); + var hasChildren = (await DictionaryItemService.GetChildrenAsync(dictionaryItem.Key)).Any(); + return new EntityTreeItemViewModel + { + Icon = Constants.Icons.RelationType, + Name = dictionaryItem.ItemKey, + Key = dictionaryItem.Key, + Type = Constants.UdiEntityType.DictionaryItem, + HasChildren = hasChildren, + IsContainer = false, + ParentKey = parentKey + }; + } - // localization service does not (yet) allow pagination of dictionary items, we have to do it in memory for now + var items = new List(dictionaryItems.Length); + foreach (IDictionaryItem dictionaryItem in dictionaryItems) + { + items.Add(await CreateEntityTreeItemViewModelAsync(dictionaryItem)); + } + + return items.ToArray(); + } + + // language service does not (yet) allow pagination of dictionary items, we have to do it in memory for now protected IDictionaryItem[] PaginatedDictionaryItems(long pageNumber, int pageSize, IEnumerable allDictionaryItems, out long totalItems) { IDictionaryItem[] allDictionaryItemsAsArray = allDictionaryItems.ToArray(); diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/ItemsDictionaryTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/ItemsDictionaryTreeController.cs index c74cf8f340..2038f3e2e3 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/ItemsDictionaryTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/ItemsDictionaryTreeController.cs @@ -8,8 +8,8 @@ namespace Umbraco.Cms.Api.Management.Controllers.Dictionary.Tree; public class ItemsDictionaryTreeController : DictionaryTreeControllerBase { - public ItemsDictionaryTreeController(IEntityService entityService, ILocalizationService localizationService) - : base(entityService, localizationService) + public ItemsDictionaryTreeController(IEntityService entityService, IDictionaryItemService dictionaryItemService) + : base(entityService, dictionaryItemService) { } @@ -18,9 +18,9 @@ public class ItemsDictionaryTreeController : DictionaryTreeControllerBase [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public async Task>> Items([FromQuery(Name = "key")] Guid[] keys) { - IDictionaryItem[] dictionaryItems = LocalizationService.GetDictionaryItemsByIds(keys).ToArray(); + IDictionaryItem[] dictionaryItems = (await DictionaryItemService.GetManyAsync(keys)).ToArray(); - EntityTreeItemViewModel[] viewModels = MapTreeItemViewModels(null, dictionaryItems); + EntityTreeItemViewModel[] viewModels = await MapTreeItemViewModels(null, dictionaryItems); return await Task.FromResult(Ok(viewModels)); } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/RootDictionaryTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/RootDictionaryTreeController.cs index bd2fc78418..b542b4f557 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/RootDictionaryTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Tree/RootDictionaryTreeController.cs @@ -10,8 +10,8 @@ namespace Umbraco.Cms.Api.Management.Controllers.Dictionary.Tree; public class RootDictionaryTreeController : DictionaryTreeControllerBase { - public RootDictionaryTreeController(IEntityService entityService, ILocalizationService localizationService) - : base(entityService, localizationService) + public RootDictionaryTreeController(IEntityService entityService, IDictionaryItemService dictionaryItemService) + : base(entityService, dictionaryItemService) { } @@ -28,10 +28,10 @@ public class RootDictionaryTreeController : DictionaryTreeControllerBase IDictionaryItem[] dictionaryItems = PaginatedDictionaryItems( pageNumber, pageSize, - LocalizationService.GetRootDictionaryItems(), + await DictionaryItemService.GetAtRootAsync(), out var totalItems); - EntityTreeItemViewModel[] viewModels = MapTreeItemViewModels(null, dictionaryItems); + EntityTreeItemViewModel[] viewModels = await MapTreeItemViewModels(null, dictionaryItems); PagedViewModel result = PagedViewModel(viewModels, totalItems); return await Task.FromResult(Ok(result)); diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/UpdateDictionaryController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/UpdateDictionaryController.cs index d0732d4076..3da74cdf2d 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/UpdateDictionaryController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/UpdateDictionaryController.cs @@ -12,16 +12,16 @@ namespace Umbraco.Cms.Api.Management.Controllers.Dictionary; public class UpdateDictionaryController : DictionaryControllerBase { - private readonly ILocalizationService _localizationService; + private readonly IDictionaryItemService _dictionaryItemService; private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly IDictionaryFactory _dictionaryFactory; public UpdateDictionaryController( - ILocalizationService localizationService, + IDictionaryItemService dictionaryItemService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IDictionaryFactory dictionaryFactory) { - _localizationService = localizationService; + _dictionaryItemService = dictionaryItemService; _backOfficeSecurityAccessor = backOfficeSecurityAccessor; _dictionaryFactory = dictionaryFactory; } @@ -33,21 +33,19 @@ public class UpdateDictionaryController : DictionaryControllerBase [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task Update(Guid key, DictionaryItemUpdateModel dictionaryItemUpdateModel) { - IDictionaryItem? current = _localizationService.GetDictionaryItemById(key); + IDictionaryItem? current = await _dictionaryItemService.GetAsync(key); if (current == null) { return NotFound(); } - IDictionaryItem updated = _dictionaryFactory.MapUpdateModelToDictionaryItem(current, dictionaryItemUpdateModel); + IDictionaryItem updated = await _dictionaryFactory.MapUpdateModelToDictionaryItemAsync(current, dictionaryItemUpdateModel); - Attempt result = _localizationService.Update(updated, CurrentUserId(_backOfficeSecurityAccessor)); + Attempt result = + await _dictionaryItemService.UpdateAsync(updated, CurrentUserId(_backOfficeSecurityAccessor)); - if (result.Success) - { - return await Task.FromResult(Ok()); - } - - return DictionaryItemOperationStatusResult(result.Status); + return result.Success + ? Ok() + : DictionaryItemOperationStatusResult(result.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Language/AllLanguageController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Language/AllLanguageController.cs index e2d0ec7f6c..93769810bd 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Language/AllLanguageController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Language/AllLanguageController.cs @@ -1,36 +1,36 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Api.Common.ViewModels.Pagination; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.Language; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Api.Management.ViewModels.Language; -using Umbraco.Cms.Api.Common.ViewModels.Pagination; -using Umbraco.New.Cms.Core.Models; namespace Umbraco.Cms.Api.Management.Controllers.Language; public class AllLanguageController : LanguageControllerBase { - private readonly ILocalizationService _localizationService; - private readonly IUmbracoMapper _umbracoMapper; + private readonly ILanguageService _languageService; + private readonly ILanguageFactory _languageFactory; - public AllLanguageController(ILocalizationService localizationService, IUmbracoMapper umbracoMapper) + public AllLanguageController(ILanguageService languageService, ILanguageFactory languageFactory) { - _localizationService = localizationService; - _umbracoMapper = umbracoMapper; + _languageService = languageService; + _languageFactory = languageFactory; } - /// 1 - /// Returns all currently configured languages. - /// - /// [HttpGet] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] - public async Task>> GetAll(int skip, int take) + public async Task>> All(int skip = 0, int take = 100) { - PagedModel allLanguages = _localizationService.GetAllLanguagesPaged(skip, take); + ILanguage[] allLanguages = (await _languageService.GetAllAsync()).ToArray(); + var viewModel = new PagedViewModel + { + Total = allLanguages.Length, + Items = allLanguages.Skip(skip).Take(take).Select(_languageFactory.CreateLanguageViewModel).ToArray() + }; - return await Task.FromResult(_umbracoMapper.Map, PagedViewModel>(allLanguages)!); + return await Task.FromResult(Ok(viewModel)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Language/ByIdLanguageController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Language/ByIdLanguageController.cs deleted file mode 100644 index f9b823b440..0000000000 --- a/src/Umbraco.Cms.Api.Management/Controllers/Language/ByIdLanguageController.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Api.Management.ViewModels.Language; - -namespace Umbraco.Cms.Api.Management.Controllers.Language; - -public class ByIdLanguageController : LanguageControllerBase -{ - private readonly ILocalizationService _localizationService; - private readonly IUmbracoMapper _umbracoMapper; - - public ByIdLanguageController(ILocalizationService localizationService, IUmbracoMapper umbracoMapper) - { - _localizationService = localizationService; - _umbracoMapper = umbracoMapper; - } - - [HttpGet("{id:int}")] - [MapToApiVersion("1.0")] - [ProducesResponseType(typeof(NotFoundResult), StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> ById(int id) - { - ILanguage? lang = _localizationService.GetLanguageById(id); - if (lang is null) - { - return NotFound(); - } - - return await Task.FromResult(_umbracoMapper.Map(lang)); - } -} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Language/ByIsoCodeLanguageController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Language/ByIsoCodeLanguageController.cs new file mode 100644 index 0000000000..d54076d957 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Language/ByIsoCodeLanguageController.cs @@ -0,0 +1,35 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.Language; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.Language; + +public class ByIsoCodeLanguageController : LanguageControllerBase +{ + private readonly ILanguageService _languageService; + private readonly ILanguageFactory _languageFactory; + + public ByIsoCodeLanguageController(ILanguageService languageService, ILanguageFactory languageFactory) + { + _languageService = languageService; + _languageFactory = languageFactory; + } + + [HttpGet($"{{{nameof(isoCode)}}}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(LanguageViewModel), StatusCodes.Status200OK)] + public async Task> ByIsoCode(string isoCode) + { + ILanguage? language = await _languageService.GetAsync(isoCode); + if (language == null) + { + return NotFound(); + } + + return Ok(_languageFactory.CreateLanguageViewModel(language)); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Language/CreateLanguageController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Language/CreateLanguageController.cs index 720182ccce..58042e0848 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Language/CreateLanguageController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Language/CreateLanguageController.cs @@ -1,61 +1,44 @@ -using System.Globalization; -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Api.Management.Factories; using Umbraco.Cms.Api.Management.ViewModels.Language; -using Umbraco.New.Cms.Core.Services.Installer; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Api.Management.Controllers.Language; public class CreateLanguageController : LanguageControllerBase { + private readonly ILanguageFactory _languageFactory; private readonly ILanguageService _languageService; - private readonly IUmbracoMapper _umbracoMapper; - private readonly ILocalizationService _localizationService; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; - public CreateLanguageController(ILanguageService languageService, IUmbracoMapper umbracoMapper, ILocalizationService localizationService) + public CreateLanguageController( + ILanguageFactory languageFactory, + ILanguageService languageService, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor) { + _languageFactory = languageFactory; _languageService = languageService; - _umbracoMapper = umbracoMapper; - _localizationService = localizationService; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; } - /// - /// Creates or saves a language - /// [HttpPost] [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status201Created)] - // TODO: This needs to be an authorized endpoint. - public async Task Create(LanguageViewModel language) + public async Task Create(LanguageCreateModel languageCreateModel) { - if (_languageService.LanguageAlreadyExists(language.Id, language.IsoCode)) - { - // Someone is trying to create a language that already exist - ModelState.AddModelError("IsoCode", "The language " + language.IsoCode + " already exists"); - return ValidationProblem(ModelState); - } + ILanguage created = _languageFactory.MapCreateModelToLanguage(languageCreateModel); - // Creating a new lang... - CultureInfo culture; - try - { - culture = CultureInfo.GetCultureInfo(language.IsoCode); - } - catch (CultureNotFoundException) - { - ModelState.AddModelError("IsoCode", "No Culture found with name " + language.IsoCode); - return ValidationProblem(ModelState); - } + Attempt result = await _languageService.CreateAsync(created, CurrentUserId(_backOfficeSecurityAccessor)); - language.Name ??= culture.EnglishName; - - ILanguage newLang = _umbracoMapper.Map(language)!; - - _localizationService.Save(newLang); - return await Task.FromResult(Created($"api/v1.0/language/{newLang.Id}", null)); + return result.Success + ? CreatedAtAction(controller => nameof(controller.ByIsoCode), new { isoCode = result.Result.IsoCode }) + : LanguageOperationStatusResult(result.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Language/DeleteLanguageController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Language/DeleteLanguageController.cs index 35b16628de..ced428ac67 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Language/DeleteLanguageController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Language/DeleteLanguageController.cs @@ -1,50 +1,35 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Api.Common.Builders; +using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Api.Management.Controllers.Language; public class DeleteLanguageController : LanguageControllerBase { - private readonly ILocalizationService _localizationService; + private readonly ILanguageService _languageService; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; - public DeleteLanguageController(ILocalizationService localizationService) => _localizationService = localizationService; + public DeleteLanguageController(ILanguageService languageService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor) + { + _languageService = languageService; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + } - /// - /// Deletes a language with a given ID - /// - [HttpDelete("{id:int}")] + [HttpDelete($"{{{nameof(isoCode)}}}")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status200OK)] - // TODO: This needs to be an authorized endpoint. - public async Task Delete(int id) + public async Task Delete(string isoCode) { - ILanguage? language = _localizationService.GetLanguageById(id); - if (language == null) - { - return await Task.FromResult(NotFound()); - } + Attempt result = await _languageService.DeleteAsync(isoCode, CurrentUserId(_backOfficeSecurityAccessor)); - // the service would not let us do it, but test here nevertheless - if (language.IsDefault) - { - ProblemDetails invalidModelProblem = - new ProblemDetailsBuilder() - .WithTitle("Cannot delete default language") - .WithDetail($"Language '{language.IsoCode}' is currently set to 'default' and can not be deleted.") - .Build(); - - return BadRequest(invalidModelProblem); - } - - // service is happy deleting a language that's fallback for another language, - // will just remove it - so no need to check here - _localizationService.Delete(language); - - return await Task.FromResult(Ok()); + return result.Success + ? Ok() + : LanguageOperationStatusResult(result.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Language/LanguageControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Language/LanguageControllerBase.cs index df6cad192c..18ad2f9894 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Language/LanguageControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Language/LanguageControllerBase.cs @@ -1,5 +1,8 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Common.Builders; using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Api.Management.Controllers.Language; @@ -9,4 +12,30 @@ namespace Umbraco.Cms.Api.Management.Controllers.Language; [ApiVersion("1.0")] public abstract class LanguageControllerBase : ManagementApiControllerBase { + protected IActionResult LanguageOperationStatusResult(LanguageOperationStatus status) => + status switch + { + LanguageOperationStatus.InvalidFallback => BadRequest(new ProblemDetailsBuilder() + .WithTitle("Invalid fallback language") + .WithDetail("The fallback language could not be applied. This may be caused if the fallback language causes cyclic fallbacks.") + .Build()), + LanguageOperationStatus.NotFound => NotFound("The language could not be found"), + LanguageOperationStatus.MissingDefault => BadRequest(new ProblemDetailsBuilder() + .WithTitle("No default language") + .WithDetail("The attempted operation would result in having no default language defined. This is not allowed.") + .Build()), + LanguageOperationStatus.DuplicateIsoCode => BadRequest(new ProblemDetailsBuilder() + .WithTitle("Duplicate ISO code") + .WithDetail("Another language already exists with the attempted ISO code.") + .Build()), + LanguageOperationStatus.InvalidIsoCode => BadRequest(new ProblemDetailsBuilder() + .WithTitle("Invalid ISO code") + .WithDetail("The attempted ISO code does not represent a valid culture.") + .Build()), + LanguageOperationStatus.CancelledByNotification => BadRequest(new ProblemDetailsBuilder() + .WithTitle("Cancelled by notification") + .WithDetail("A notification handler prevented the language operation.") + .Build()), + _ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown dictionary operation status") + }; } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Language/UpdateLanguageController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Language/UpdateLanguageController.cs index 2452d6468e..302522cbd0 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Language/UpdateLanguageController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Language/UpdateLanguageController.cs @@ -1,66 +1,50 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Api.Management.Factories; using Umbraco.Cms.Api.Management.ViewModels.Language; -using Umbraco.New.Cms.Core.Services.Installer; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Api.Management.Controllers.Language; public class UpdateLanguageController : LanguageControllerBase { + private readonly ILanguageFactory _languageFactory; private readonly ILanguageService _languageService; - private readonly IUmbracoMapper _umbracoMapper; - private readonly ILocalizationService _localizationService; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; - public UpdateLanguageController(ILanguageService languageService, IUmbracoMapper umbracoMapper, ILocalizationService localizationService) + public UpdateLanguageController( + ILanguageFactory languageFactory, + ILanguageService languageService, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor) { + _languageFactory = languageFactory; _languageService = languageService; - _umbracoMapper = umbracoMapper; - _localizationService = localizationService; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; } - /// - /// Updates a language - /// - [HttpPut("{id:int}")] + [HttpPut($"{{{nameof(isoCode)}}}")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(NotFoundResult), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status200OK)] - // TODO: This needs to be an authorized endpoint. - public async Task Update(int id, LanguageViewModel language) + public async Task Update(string isoCode, LanguageUpdateModel languageUpdateModel) { - ILanguage? existingById = _localizationService.GetLanguageById(id); - if (existingById is null) + ILanguage? current = await _languageService.GetAsync(isoCode); + if (current is null) { - return await Task.FromResult(NotFound()); + return NotFound(); } - // note that the service will prevent the default language from being "un-defaulted" - // but does not hurt to test here - though the UI should prevent it too - if (existingById.IsDefault && !language.IsDefault) - { - ModelState.AddModelError("IsDefault", "Cannot un-default the default language."); - return await Task.FromResult(ValidationProblem(ModelState)); - } + ILanguage updated = _languageFactory.MapUpdateModelToLanguage(current, languageUpdateModel); - existingById = _umbracoMapper.Map(language, existingById); + Attempt result = await _languageService.UpdateAsync(updated, CurrentUserId(_backOfficeSecurityAccessor)); - if (!_languageService.CanUseLanguagesFallbackLanguage(existingById)) - { - ModelState.AddModelError("FallbackLanguage", "The selected fall back language does not exist."); - return await Task.FromResult(ValidationProblem(ModelState)); - } - - if (!_languageService.CanGetProperFallbackLanguage(existingById)) - { - ModelState.AddModelError("FallbackLanguage", $"The selected fall back language {_localizationService.GetLanguageById(existingById.FallbackLanguageId!.Value)} would create a circular path."); - return await Task.FromResult(ValidationProblem(ModelState)); - } - - _localizationService.Save(existingById); - return await Task.FromResult(Ok()); + return result.Success + ? Ok() + : LanguageOperationStatusResult(result.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/ManagementApiControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/ManagementApiControllerBase.cs index 45e5ce4b22..29075fce3a 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/ManagementApiControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/ManagementApiControllerBase.cs @@ -10,6 +10,9 @@ namespace Umbraco.Cms.Api.Management.Controllers; public class ManagementApiControllerBase : Controller { protected CreatedAtActionResult CreatedAtAction(Expression> action, Guid key) + => CreatedAtAction(action, new { key = key }); + + protected CreatedAtActionResult CreatedAtAction(Expression> action, object routeValues) { if (action.Body is not ConstantExpression constantExpression) { @@ -19,7 +22,7 @@ public class ManagementApiControllerBase : Controller var controllerName = ManagementApiRegexes.ControllerTypeToNameRegex().Replace(typeof(T).Name, string.Empty); var actionName = constantExpression.Value?.ToString() ?? throw new ArgumentException("Expression does not have a value."); - return base.CreatedAtAction(actionName, controllerName, new { key = key }, null); + return base.CreatedAtAction(actionName, controllerName, routeValues, null); } protected CreatedAtActionResult CreatedAtAction(Expression> action, string name) diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/LanguageBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/LanguageBuilderExtensions.cs index 30db67bf07..b3bd404eef 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/LanguageBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/LanguageBuilderExtensions.cs @@ -1,10 +1,9 @@ using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Factories; using Umbraco.Cms.Api.Management.Mapping.Culture; -using Umbraco.Cms.Api.Management.Mapping.Languages; +using Umbraco.Cms.Api.Management.Mapping.Language; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Mapping; -using Umbraco.New.Cms.Core.Services.Installer; -using Umbraco.New.Cms.Core.Services.Languages; namespace Umbraco.Cms.Api.Management.DependencyInjection; @@ -12,7 +11,7 @@ internal static class LanguageBuilderExtensions { internal static IUmbracoBuilder AddLanguages(this IUmbracoBuilder builder) { - builder.Services.AddTransient(); + builder.Services.AddTransient(); builder.WithCollectionBuilder() .Add() diff --git a/src/Umbraco.Cms.Api.Management/Factories/DictionaryFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/DictionaryFactory.cs index bf629e3139..2ed132d7b4 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/DictionaryFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/DictionaryFactory.cs @@ -11,20 +11,19 @@ namespace Umbraco.Cms.Api.Management.Factories; public class DictionaryFactory : IDictionaryFactory { private readonly IUmbracoMapper _umbracoMapper; - private readonly ILocalizationService _localizationService; + private readonly ILanguageService _languageService; - public DictionaryFactory(IUmbracoMapper umbracoMapper, ILocalizationService localizationService) + public DictionaryFactory(IUmbracoMapper umbracoMapper, ILanguageService languageService) { _umbracoMapper = umbracoMapper; - _localizationService = localizationService; + _languageService = languageService; } - public DictionaryItemViewModel CreateDictionaryItemViewModel(IDictionaryItem dictionaryItem) + public async Task CreateDictionaryItemViewModelAsync(IDictionaryItem dictionaryItem) { DictionaryItemViewModel dictionaryViewModel = _umbracoMapper.Map(dictionaryItem)!; - var validLanguageIds = _localizationService - .GetAllLanguages() + var validLanguageIds = (await _languageService.GetAllAsync()) .Select(language => language.Id) .ToArray(); IDictionaryTranslation[] validTranslations = dictionaryItem.Translations @@ -38,22 +37,22 @@ public class DictionaryFactory : IDictionaryFactory return dictionaryViewModel; } - public IDictionaryItem MapUpdateModelToDictionaryItem(IDictionaryItem current, DictionaryItemUpdateModel dictionaryItemUpdateModel) + public async Task MapUpdateModelToDictionaryItemAsync(IDictionaryItem current, DictionaryItemUpdateModel dictionaryItemUpdateModel) { IDictionaryItem updated = _umbracoMapper.Map(dictionaryItemUpdateModel, current); - MapTranslations(updated, dictionaryItemUpdateModel.Translations); + await MapTranslations(updated, dictionaryItemUpdateModel.Translations); return updated; } - public IEnumerable MapTranslations(IEnumerable translationModels) + public async Task MapCreateModelToDictionaryItemAsync(DictionaryItemCreateModel dictionaryItemUpdateModel) { - var temporaryDictionaryItem = new DictionaryItem(Guid.NewGuid().ToString()); + IDictionaryItem updated = _umbracoMapper.Map(dictionaryItemUpdateModel)!; - MapTranslations(temporaryDictionaryItem, translationModels); + await MapTranslations(updated, dictionaryItemUpdateModel.Translations); - return temporaryDictionaryItem.Translations; + return updated; } public DictionaryImportViewModel CreateDictionaryImportViewModel(FormFileUploadResult formFileUploadResult) @@ -87,18 +86,16 @@ public class DictionaryFactory : IDictionaryFactory return model; } - private void MapTranslations(IDictionaryItem dictionaryItem, IEnumerable translationModels) + private async Task MapTranslations(IDictionaryItem dictionaryItem, IEnumerable translationModels) { - var languagesByIsoCode = _localizationService - .GetAllLanguages() - .ToDictionary(l => l.IsoCode); + var languagesByIsoCode = (await _languageService.GetAllAsync()).ToDictionary(l => l.IsoCode); DictionaryItemTranslationModel[] validTranslations = translationModels .Where(translation => languagesByIsoCode.ContainsKey(translation.IsoCode)) .ToArray(); foreach (DictionaryItemTranslationModel translationModel in validTranslations) { - _localizationService.AddOrUpdateDictionaryValue(dictionaryItem, languagesByIsoCode[translationModel.IsoCode], translationModel.Translation); + dictionaryItem.AddOrUpdateDictionaryValue(languagesByIsoCode[translationModel.IsoCode], translationModel.Translation); } } } diff --git a/src/Umbraco.Cms.Api.Management/Factories/IDictionaryFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/IDictionaryFactory.cs index 209f9c0981..9dfb5e2775 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/IDictionaryFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/IDictionaryFactory.cs @@ -6,11 +6,11 @@ namespace Umbraco.Cms.Api.Management.Factories; public interface IDictionaryFactory { - IDictionaryItem MapUpdateModelToDictionaryItem(IDictionaryItem current, DictionaryItemUpdateModel dictionaryItemUpdateModel); + Task MapUpdateModelToDictionaryItemAsync(IDictionaryItem current, DictionaryItemUpdateModel dictionaryItemUpdateModel); - IEnumerable MapTranslations(IEnumerable translationModels); + Task MapCreateModelToDictionaryItemAsync(DictionaryItemCreateModel dictionaryItemUpdateModel); - DictionaryItemViewModel CreateDictionaryItemViewModel(IDictionaryItem dictionaryItem); + Task CreateDictionaryItemViewModelAsync(IDictionaryItem dictionaryItem); DictionaryImportViewModel CreateDictionaryImportViewModel(FormFileUploadResult formFileUploadResult); } diff --git a/src/Umbraco.Cms.Api.Management/Factories/ILanguageFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/ILanguageFactory.cs new file mode 100644 index 0000000000..55ede7f563 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Factories/ILanguageFactory.cs @@ -0,0 +1,13 @@ +using Umbraco.Cms.Api.Management.ViewModels.Language; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Api.Management.Factories; + +public interface ILanguageFactory +{ + LanguageViewModel CreateLanguageViewModel(ILanguage language); + + ILanguage MapCreateModelToLanguage(LanguageCreateModel languageCreateModel); + + ILanguage MapUpdateModelToLanguage(ILanguage current, LanguageUpdateModel languageUpdateModel); +} diff --git a/src/Umbraco.Cms.Api.Management/Factories/LanguageFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/LanguageFactory.cs new file mode 100644 index 0000000000..ef558fdba3 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Factories/LanguageFactory.cs @@ -0,0 +1,51 @@ +using Umbraco.Cms.Api.Management.ViewModels.Language; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Factories; + +public class LanguageFactory : ILanguageFactory +{ + // FIXME: use ILanguageService instead of ILocalizationService (pending language refactor to replace fallback language ID with fallback language IsoCode) + private readonly ILocalizationService _localizationService; + private readonly IUmbracoMapper _umbracoMapper; + + public LanguageFactory(ILocalizationService localizationService, IUmbracoMapper umbracoMapper) + { + _localizationService = localizationService; + _umbracoMapper = umbracoMapper; + } + + public LanguageViewModel CreateLanguageViewModel(ILanguage language) + { + LanguageViewModel languageViewModel = _umbracoMapper.Map(language)!; + if (language.FallbackLanguageId.HasValue) + { + languageViewModel.FallbackIsoCode = _localizationService.GetLanguageById(language.FallbackLanguageId.Value)?.IsoCode; + } + + return languageViewModel; + } + + public ILanguage MapCreateModelToLanguage(LanguageCreateModel languageCreateModel) + { + ILanguage created = _umbracoMapper.Map(languageCreateModel)!; + created.FallbackLanguageId = GetFallbackLanguageId(languageCreateModel); + + return created; + } + + public ILanguage MapUpdateModelToLanguage(ILanguage current, LanguageUpdateModel languageUpdateModel) + { + ILanguage updated = _umbracoMapper.Map(languageUpdateModel, current); + updated.FallbackLanguageId = GetFallbackLanguageId(languageUpdateModel); + + return updated; + } + + private int? GetFallbackLanguageId(LanguageModelBase languageModelBase) => + string.IsNullOrWhiteSpace(languageModelBase.FallbackIsoCode) + ? null + : _localizationService.GetLanguageByIsoCode(languageModelBase.FallbackIsoCode)?.Id; +} diff --git a/src/Umbraco.Cms.Api.Management/Mapping/Culture/CultureViewModelMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/Culture/CultureViewModelMapDefinition.cs index 71e0b26857..930ebd5068 100644 --- a/src/Umbraco.Cms.Api.Management/Mapping/Culture/CultureViewModelMapDefinition.cs +++ b/src/Umbraco.Cms.Api.Management/Mapping/Culture/CultureViewModelMapDefinition.cs @@ -1,7 +1,6 @@ using System.Globalization; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Api.Management.ViewModels.Culture; -using Umbraco.Cms.Api.Common.ViewModels.Pagination; namespace Umbraco.Cms.Api.Management.Mapping.Culture; @@ -9,11 +8,7 @@ namespace Umbraco.Cms.Api.Management.Mapping.Culture; public class CultureViewModelMapDefinition : IMapDefinition { /// - public void DefineMaps(IUmbracoMapper mapper) - { - mapper.Define, PagedViewModel>((source, context) => new PagedViewModel(), Map); - mapper.Define((source, context) => new CultureViewModel(), Map); - } + public void DefineMaps(IUmbracoMapper mapper) => mapper.Define((source, context) => new CultureViewModel(), Map); // Umbraco.Code.MapAll private static void Map(CultureInfo source, CultureViewModel target, MapperContext context) @@ -21,12 +16,4 @@ public class CultureViewModelMapDefinition : IMapDefinition target.Name = source.Name; target.EnglishName = source.EnglishName; } - - // Umbraco.Code.MapAll - private static void Map(IEnumerable source, PagedViewModel target, MapperContext context) - { - CultureInfo[] cultureInfos = source.ToArray(); - target.Items = context.MapEnumerable(cultureInfos); - target.Total = cultureInfos.Length; - } } diff --git a/src/Umbraco.Cms.Api.Management/Mapping/Dictionary/DictionaryMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/Dictionary/DictionaryMapDefinition.cs index 7c19b1d877..33fff1541c 100644 --- a/src/Umbraco.Cms.Api.Management/Mapping/Dictionary/DictionaryMapDefinition.cs +++ b/src/Umbraco.Cms.Api.Management/Mapping/Dictionary/DictionaryMapDefinition.cs @@ -1,16 +1,12 @@ using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; using Umbraco.Cms.Api.Management.ViewModels.Dictionary; +using Umbraco.Extensions; namespace Umbraco.Cms.Api.Management.Mapping.Dictionary; public class DictionaryMapDefinition : IMapDefinition { - private readonly ILocalizationService _localizationService; - - public DictionaryMapDefinition(ILocalizationService localizationService) => _localizationService = localizationService; - public void DefineMaps(IUmbracoMapper mapper) { mapper.Define((_, _) => new DictionaryItemViewModel(), Map); @@ -49,24 +45,15 @@ public class DictionaryMapDefinition : IMapDefinition target.DeleteDate = null; } - // Umbraco.Code.MapAll -Level -Translations + // Umbraco.Code.MapAll -Level private void Map(IDictionaryItem source, DictionaryOverviewViewModel target, MapperContext context) { target.Key = source.Key; target.Name = source.ItemKey; - - // add all languages and the translations - foreach (ILanguage lang in _localizationService.GetAllLanguages()) - { - var langId = lang.Id; - IDictionaryTranslation? translation = source.Translations?.FirstOrDefault(x => x.LanguageId == langId); - - target.Translations.Add( - new DictionaryTranslationOverviewViewModel - { - DisplayName = lang.CultureName, - HasTranslation = translation != null && string.IsNullOrEmpty(translation.Value) == false, - }); - } + target.TranslatedIsoCodes = source + .Translations + .Where(translation => translation.Value.IsNullOrWhiteSpace() == false && translation.Language != null) + .Select(translation => translation.Language!.IsoCode) + .ToArray(); } } diff --git a/src/Umbraco.Cms.Api.Management/Mapping/Language/LanguageViewModelsMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/Language/LanguageViewModelsMapDefinition.cs new file mode 100644 index 0000000000..9f71dce075 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Mapping/Language/LanguageViewModelsMapDefinition.cs @@ -0,0 +1,52 @@ +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Api.Management.ViewModels.Language; + +namespace Umbraco.Cms.Api.Management.Mapping.Language; + +public class LanguageViewModelsMapDefinition : IMapDefinition +{ + public void DefineMaps(IUmbracoMapper mapper) + { + mapper.Define((_, _) => new Core.Models.Language(string.Empty, string.Empty), Map); + mapper.Define((_, _) => new Core.Models.Language(string.Empty, string.Empty), Map); + mapper.Define((_, _) => new LanguageViewModel(), Map); + } + + // Umbraco.Code.MapAll -FallbackIsoCode + private static void Map(ILanguage source, LanguageViewModel target, MapperContext context) + { + target.IsoCode = source.IsoCode; + target.Name = source.CultureName; + target.IsDefault = source.IsDefault; + target.IsMandatory = source.IsMandatory; + } + + // Umbraco.Code.MapAll -Id -FallbackLanguageId -Key + private static void Map(LanguageCreateModel source, ILanguage target, MapperContext context) + { + target.CreateDate = default; + if (!string.IsNullOrEmpty(source.Name)) + { + target.CultureName = source.Name; + } + target.DeleteDate = null; + target.IsDefault = source.IsDefault; + target.IsMandatory = source.IsMandatory; + target.IsoCode = source.IsoCode; + target.UpdateDate = default; + } + + // Umbraco.Code.MapAll -Id -FallbackLanguageId -Key -IsoCode -CreateDate + private static void Map(LanguageUpdateModel source, ILanguage target, MapperContext context) + { + if (!string.IsNullOrEmpty(source.Name)) + { + target.CultureName = source.Name; + } + target.DeleteDate = null; + target.IsDefault = source.IsDefault; + target.IsMandatory = source.IsMandatory; + target.UpdateDate = default; + } +} diff --git a/src/Umbraco.Cms.Api.Management/Mapping/Languages/LanguageViewModelsMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/Languages/LanguageViewModelsMapDefinition.cs deleted file mode 100644 index 289f8636c9..0000000000 --- a/src/Umbraco.Cms.Api.Management/Mapping/Languages/LanguageViewModelsMapDefinition.cs +++ /dev/null @@ -1,70 +0,0 @@ -using NPoco.FluentMappings; -using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Api.Management.ViewModels.Language; -using Umbraco.Cms.Api.Common.ViewModels.Pagination; -using Umbraco.New.Cms.Core.Models; - -namespace Umbraco.Cms.Api.Management.Mapping.Languages; - -public class LanguageViewModelsMapDefinition : IMapDefinition -{ - public void DefineMaps(IUmbracoMapper mapper) - { - mapper.Define((source, context) => new Language(string.Empty, string.Empty), Map); - mapper.Define((source, context) => new LanguageViewModel(), Map); - mapper.Define, PagedViewModel>((source, context) => new PagedViewModel(), Map); - - } - - // Umbraco.Code.MapAll - private static void Map(ILanguage source, LanguageViewModel target, MapperContext context) - { - target.Id = source.Id; - target.IsoCode = source.IsoCode; - target.Name = source.CultureName; - target.IsDefault = source.IsDefault; - target.IsMandatory = source.IsMandatory; - target.FallbackLanguageId = source.FallbackLanguageId; - } - - - // Umbraco.Code.MapAll - private static void Map(LanguageViewModel source, ILanguage target, MapperContext context) - { - target.CreateDate = default; - if (!string.IsNullOrEmpty(source.Name)) - { - target.CultureName = source.Name; - } - - target.DeleteDate = null; - target.FallbackLanguageId = source.FallbackLanguageId; - target.Id = source.Id; - target.IsDefault = source.IsDefault; - target.IsMandatory = source.IsMandatory; - target.IsoCode = source.IsoCode; - target.Key = default; - target.UpdateDate = default; - } - - private static void Map(PagedModel source, PagedViewModel target, MapperContext context) - { - List temp = context.MapEnumerable(source.Items); - - // Put the default language first in the list & then sort rest by a-z - LanguageViewModel? defaultLang = temp.SingleOrDefault(x => x.IsDefault); - - var languages = new List(); - - // insert default lang first, then remaining language a-z - if (defaultLang is not null) - { - languages.Add(defaultLang); - languages.AddRange(temp.Where(x => x != defaultLang).OrderBy(x => x.Name)); - } - - target.Items = languages; - target.Total = source.Total; - } -} diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json index 36a567fd55..7d90885c3f 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi.json +++ b/src/Umbraco.Cms.Api.Management/OpenApi.json @@ -18,7 +18,8 @@ "in": "query", "schema": { "type": "integer", - "format": "int32" + "format": "int32", + "default": 0 } }, { @@ -26,7 +27,8 @@ "in": "query", "schema": { "type": "integer", - "format": "int32" + "format": "int32", + "default": 100 } } ], @@ -522,7 +524,8 @@ "in": "query", "schema": { "type": "integer", - "format": "int32" + "format": "int32", + "default": 0 } }, { @@ -530,7 +533,8 @@ "in": "query", "schema": { "type": "integer", - "format": "int32" + "format": "int32", + "default": 100 } } ], @@ -1901,36 +1905,6 @@ } }, "/umbraco/management/api/v1/language": { - "post": { - "tags": [ - "Language" - ], - "operationId": "PostLanguage", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Language" - } - } - } - }, - "responses": { - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } - }, - "201": { - "description": "Created" - } - } - }, "get": { "tags": [ "Language" @@ -1942,7 +1916,8 @@ "in": "query", "schema": { "type": "integer", - "format": "int32" + "format": "int32", + "default": 0 } }, { @@ -1950,7 +1925,8 @@ "in": "query", "schema": { "type": "integer", - "format": "int32" + "format": "int32", + "default": 100 } } ], @@ -1966,22 +1942,61 @@ } } } + }, + "post": { + "tags": [ + "Language" + ], + "operationId": "PostLanguage", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LanguageCreateModel" + } + } + } + }, + "responses": { + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "201": { + "description": "Created" + } + } } }, - "/umbraco/management/api/v1/language/{id}": { + "/umbraco/management/api/v1/language/{isoCode}": { "get": { "tags": [ "Language" ], - "operationId": "GetLanguageById", + "operationId": "GetLanguageByIsoCode", "parameters": [ { - "name": "id", + "name": "isoCode", "in": "path", "required": true, "schema": { - "type": "integer", - "format": "int32" + "type": "string" } } ], @@ -1991,7 +2006,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/NotFoundResult" + "$ref": "#/components/schemas/ProblemDetails" } } } @@ -2012,15 +2027,14 @@ "tags": [ "Language" ], - "operationId": "DeleteLanguageById", + "operationId": "DeleteLanguageByIsoCode", "parameters": [ { - "name": "id", + "name": "isoCode", "in": "path", "required": true, "schema": { - "type": "integer", - "format": "int32" + "type": "string" } } ], @@ -2054,15 +2068,14 @@ "tags": [ "Language" ], - "operationId": "PutLanguageById", + "operationId": "PutLanguageByIsoCode", "parameters": [ { - "name": "id", + "name": "isoCode", "in": "path", "required": true, "schema": { - "type": "integer", - "format": "int32" + "type": "string" } } ], @@ -2070,7 +2083,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Language" + "$ref": "#/components/schemas/LanguageUpdateModel" } } } @@ -5612,25 +5625,11 @@ "type": "integer", "format": "int32" }, - "translations": { + "translatedIsoCodes": { "type": "array", "items": { - "$ref": "#/components/schemas/DictionaryTranslationOverview" - }, - "readOnly": true - } - }, - "additionalProperties": false - }, - "DictionaryTranslationOverview": { - "type": "object", - "properties": { - "displayName": { - "type": "string", - "nullable": true - }, - "hasTranslation": { - "type": "boolean" + "type": "string" + } } }, "additionalProperties": false @@ -6374,22 +6373,10 @@ "additionalProperties": false }, "Language": { - "required": [ - "isoCode" - ], "type": "object", "properties": { - "id": { - "type": "integer", - "format": "int32" - }, - "isoCode": { - "minLength": 1, - "type": "string" - }, "name": { - "type": "string", - "nullable": true + "type": "string" }, "isDefault": { "type": "boolean" @@ -6397,9 +6384,52 @@ "isMandatory": { "type": "boolean" }, - "fallbackLanguageId": { - "type": "integer", - "format": "int32", + "fallbackIsoCode": { + "type": "string", + "nullable": true + }, + "isoCode": { + "type": "string" + } + }, + "additionalProperties": false + }, + "LanguageCreateModel": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "isDefault": { + "type": "boolean" + }, + "isMandatory": { + "type": "boolean" + }, + "fallbackIsoCode": { + "type": "string", + "nullable": true + }, + "isoCode": { + "type": "string" + } + }, + "additionalProperties": false + }, + "LanguageUpdateModel": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "isDefault": { + "type": "boolean" + }, + "isMandatory": { + "type": "boolean" + }, + "fallbackIsoCode": { + "type": "string", "nullable": true } }, diff --git a/src/Umbraco.Cms.Api.Management/Services/LoadDictionaryItemService.cs b/src/Umbraco.Cms.Api.Management/Services/LoadDictionaryItemService.cs index 3fd791bcc0..0fa8596c3e 100644 --- a/src/Umbraco.Cms.Api.Management/Services/LoadDictionaryItemService.cs +++ b/src/Umbraco.Cms.Api.Management/Services/LoadDictionaryItemService.cs @@ -11,6 +11,7 @@ namespace Umbraco.Cms.Api.Management.Services; public class LoadDictionaryItemService : ILoadDictionaryItemService { private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + // FIXME: use IDictionaryItemService instead of ILocalizationService private readonly ILocalizationService _localizationService; private readonly PackageDataInstallation _packageDataInstallation; private readonly ILogger _logger; @@ -26,6 +27,8 @@ public class LoadDictionaryItemService : ILoadDictionaryItemService _packageDataInstallation = packageDataInstallation; _logger = logger; } + + // FIXME: use Guid key, not integer ID for parent identification public IDictionaryItem Load(string filePath, int? parentId) { var xmlDocument = new XmlDocument { XmlResolver = null }; diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryOverviewViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryOverviewViewModel.cs index 05eb2fe6e3..fbb5d39664 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryOverviewViewModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryOverviewViewModel.cs @@ -1,14 +1,7 @@ -using Umbraco.Cms.Core.Models.ContentEditing; - -namespace Umbraco.Cms.Api.Management.ViewModels.Dictionary; +namespace Umbraco.Cms.Api.Management.ViewModels.Dictionary; public class DictionaryOverviewViewModel { - /// - /// Initializes a new instance of the class. - /// - public DictionaryOverviewViewModel() => Translations = new List(); - /// /// Gets or sets the key. /// @@ -27,5 +20,5 @@ public class DictionaryOverviewViewModel /// /// Sets the translations. /// - public List Translations { get; } + public IEnumerable TranslatedIsoCodes { get; set; } = Array.Empty(); } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryTranslationOverviewViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryTranslationOverviewViewModel.cs deleted file mode 100644 index d7d5f9e568..0000000000 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryTranslationOverviewViewModel.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Umbraco.Cms.Api.Management.ViewModels.Dictionary; - -public class DictionaryTranslationOverviewViewModel -{ - /// - /// Gets or sets the display name. - /// - public string? DisplayName { get; set; } - - /// - /// Gets or sets a value indicating whether has translation. - /// - public bool HasTranslation { get; set; } -} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Language/LanguageCreateModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Language/LanguageCreateModel.cs new file mode 100644 index 0000000000..05d54a977c --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Language/LanguageCreateModel.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.Language; + +public class LanguageCreateModel : LanguageModelBase +{ + public string IsoCode { get; set; } = string.Empty; +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Language/LanguageModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Language/LanguageModelBase.cs new file mode 100644 index 0000000000..4083f1ebfe --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Language/LanguageModelBase.cs @@ -0,0 +1,12 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.Language; + +public class LanguageModelBase +{ + public string Name { get; set; } = string.Empty; + + public bool IsDefault { get; set; } + + public bool IsMandatory { get; set; } + + public string? FallbackIsoCode { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Language/LanguageUpdateModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Language/LanguageUpdateModel.cs new file mode 100644 index 0000000000..840a7a2a9e --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Language/LanguageUpdateModel.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.Language; + +public class LanguageUpdateModel : LanguageModelBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Language/LanguageViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Language/LanguageViewModel.cs index a5cc1a7f1a..2618178cbd 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Language/LanguageViewModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Language/LanguageViewModel.cs @@ -1,19 +1,6 @@ -using System.ComponentModel.DataAnnotations; +namespace Umbraco.Cms.Api.Management.ViewModels.Language; -namespace Umbraco.Cms.Api.Management.ViewModels.Language; - -public class LanguageViewModel +public class LanguageViewModel : LanguageModelBase { - public int Id { get; set; } - - [Required(AllowEmptyStrings = false)] - public string IsoCode { get; set; } = null!; - - public string? Name { get; set; } - - public bool IsDefault { get; set; } - - public bool IsMandatory { get; set; } - - public int? FallbackLanguageId { get; set; } + public string IsoCode { get; set; } = string.Empty; } diff --git a/src/Umbraco.Core/CompatibilitySuppressions.xml b/src/Umbraco.Core/CompatibilitySuppressions.xml index 77f7c39d35..5171302578 100644 --- a/src/Umbraco.Core/CompatibilitySuppressions.xml +++ b/src/Umbraco.Core/CompatibilitySuppressions.xml @@ -308,6 +308,13 @@ lib/net7.0/Umbraco.Core.dll true + + CP0006 + M:Umbraco.Cms.Core.Services.ILocalizationService.Create(Umbraco.Cms.Core.Models.ILanguage,System.Int32) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + CP0006 M:Umbraco.Cms.Core.Services.ILocalizationService.Delete(System.Guid,System.Int32) @@ -315,6 +322,13 @@ lib/net7.0/Umbraco.Core.dll true + + CP0006 + M:Umbraco.Cms.Core.Services.ILocalizationService.Delete(System.String,System.Int32) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + CP0006 M:Umbraco.Cms.Core.Services.ILocalizationService.Update(Umbraco.Cms.Core.Models.IDictionaryItem,System.Int32) @@ -322,6 +336,13 @@ lib/net7.0/Umbraco.Core.dll true + + CP0006 + M:Umbraco.Cms.Core.Services.ILocalizationService.Update(Umbraco.Cms.Core.Models.ILanguage,System.Int32) + lib/net7.0/Umbraco.Core.dll + lib/net7.0/Umbraco.Core.dll + true + CP0006 P:Umbraco.Cms.Core.Models.IDataType.ConfigurationData diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index c2d90bdfe2..cfa6a3d52e 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -281,6 +281,8 @@ namespace Umbraco.Cms.Core.DependencyInjection Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); diff --git a/src/Umbraco.Core/Models/DictionaryItemExtensions.cs b/src/Umbraco.Core/Models/DictionaryItemExtensions.cs index 3e6c051201..fc03cb929c 100644 --- a/src/Umbraco.Core/Models/DictionaryItemExtensions.cs +++ b/src/Umbraco.Core/Models/DictionaryItemExtensions.cs @@ -26,4 +26,33 @@ public static class DictionaryItemExtensions IDictionaryTranslation? defaultTranslation = d.Translations.FirstOrDefault(x => x.Language?.Id == 1); return defaultTranslation == null ? string.Empty : defaultTranslation.Value; } + + /// + /// Adds or updates a translation for a dictionary item and language + /// + /// + /// + /// + public static void AddOrUpdateDictionaryValue(this IDictionaryItem item, ILanguage language, string value) + { + IDictionaryTranslation? existing = item.Translations?.FirstOrDefault(x => x.Language?.Id == language.Id); + if (existing != null) + { + existing.Value = value; + } + else + { + if (item.Translations is not null) + { + item.Translations = new List(item.Translations) + { + new DictionaryTranslation(language, value), + }; + } + else + { + item.Translations = new List { new DictionaryTranslation(language, value) }; + } + } + } } diff --git a/src/Umbraco.Core/Services/DictionaryItemService.cs b/src/Umbraco.Core/Services/DictionaryItemService.cs new file mode 100644 index 0000000000..2f8a44b1df --- /dev/null +++ b/src/Umbraco.Core/Services/DictionaryItemService.cs @@ -0,0 +1,320 @@ +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Persistence.Querying; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Core.Services; + +internal class DictionaryItemService : RepositoryService, IDictionaryItemService +{ + private readonly IDictionaryRepository _dictionaryRepository; + private readonly IAuditRepository _auditRepository; + private readonly ILanguageService _languageService; + + public DictionaryItemService( + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IDictionaryRepository dictionaryRepository, + IAuditRepository auditRepository, + ILanguageService languageService) + : base(provider, loggerFactory, eventMessagesFactory) + { + _dictionaryRepository = dictionaryRepository; + _auditRepository = auditRepository; + _languageService = languageService; + } + + /// + public async Task GetAsync(Guid id) + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) + { + IDictionaryItem? item = _dictionaryRepository.Get(id); + + // ensure the lazy Language callback is assigned + EnsureDictionaryItemLanguageCallback(item); + return await Task.FromResult(item); + } + } + + /// + public async Task GetAsync(string key) + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) + { + IDictionaryItem? item = _dictionaryRepository.Get(key); + + // ensure the lazy Language callback is assigned + EnsureDictionaryItemLanguageCallback(item); + return await Task.FromResult(item); + } + } + + /// + public async Task> GetManyAsync(params Guid[] ids) + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) + { + IEnumerable items = _dictionaryRepository.GetMany(ids).ToArray(); + + // ensure the lazy Language callback is assigned + foreach (IDictionaryItem item in items) + { + EnsureDictionaryItemLanguageCallback(item); + } + + return await Task.FromResult(items); + } + } + + /// + public async Task> GetManyAsync(params string[] keys) + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) + { + IEnumerable items = _dictionaryRepository.GetManyByKeys(keys).ToArray(); + + // ensure the lazy Language callback is assigned + foreach (IDictionaryItem item in items) + { + EnsureDictionaryItemLanguageCallback(item); + } + + return await Task.FromResult(items); + } + } + + /// + public async Task> GetChildrenAsync(Guid parentId) + => await GetByQueryAsync(Query().Where(x => x.ParentId == parentId)); + + /// + public async Task> GetDescendantsAsync(Guid? parentId) + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) + { + IDictionaryItem[] items = _dictionaryRepository.GetDictionaryItemDescendants(parentId).ToArray(); + + // ensure the lazy Language callback is assigned + foreach (IDictionaryItem item in items) + { + EnsureDictionaryItemLanguageCallback(item); + } + + return await Task.FromResult(items); + } + } + + /// + public async Task> GetAtRootAsync() + => await GetByQueryAsync(Query().Where(x => x.ParentId == null)); + + /// + public async Task ExistsAsync(string key) + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) + { + IDictionaryItem? item = _dictionaryRepository.Get(key); + return await Task.FromResult(item != null); + } + } + + /// + public async Task> CreateAsync(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId) + { + if (dictionaryItem.Id != 0) + { + return Attempt.FailWithStatus(DictionaryItemOperationStatus.InvalidId, dictionaryItem); + } + + return await SaveAsync( + dictionaryItem, + () => + { + if (_dictionaryRepository.Get(dictionaryItem.Key) != null) + { + return DictionaryItemOperationStatus.DuplicateKey; + } + + return DictionaryItemOperationStatus.Success; + }, + AuditType.New, + "Create DictionaryItem", + userId); + } + + /// + public async Task> UpdateAsync( + IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId) + => await SaveAsync( + dictionaryItem, + () => + { + // is there an item to update? + if (_dictionaryRepository.Exists(dictionaryItem.Id) == false) + { + return DictionaryItemOperationStatus.ItemNotFound; + } + + return DictionaryItemOperationStatus.Success; + }, + AuditType.Save, + "Update DictionaryItem", + userId); + + /// + public async Task> DeleteAsync(Guid id, int userId = Constants.Security.SuperUserId) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + IDictionaryItem? dictionaryItem = _dictionaryRepository.Get(id); + if (dictionaryItem == null) + { + return Attempt.FailWithStatus(DictionaryItemOperationStatus.ItemNotFound, null); + } + + EventMessages eventMessages = EventMessagesFactory.Get(); + var deletingNotification = new DictionaryItemDeletingNotification(dictionaryItem, eventMessages); + if (await scope.Notifications.PublishCancelableAsync(deletingNotification)) + { + scope.Complete(); + return Attempt.FailWithStatus(DictionaryItemOperationStatus.CancelledByNotification, dictionaryItem); + } + + _dictionaryRepository.Delete(dictionaryItem); + scope.Notifications.Publish( + new DictionaryItemDeletedNotification(dictionaryItem, eventMessages) + .WithStateFrom(deletingNotification)); + + Audit(AuditType.Delete, "Delete DictionaryItem", userId, dictionaryItem.Id, nameof(DictionaryItem)); + + scope.Complete(); + return await Task.FromResult(Attempt.SucceedWithStatus(DictionaryItemOperationStatus.Success, dictionaryItem)); + } + } + + private async Task> SaveAsync( + IDictionaryItem dictionaryItem, + Func operationValidation, + AuditType auditType, + string auditMessage, + int userId) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + DictionaryItemOperationStatus status = operationValidation(); + if (status != DictionaryItemOperationStatus.Success) + { + return Attempt.FailWithStatus(status, dictionaryItem); + } + + // validate the parent + if (HasValidParent(dictionaryItem) == false) + { + return Attempt.FailWithStatus(DictionaryItemOperationStatus.ParentNotFound, dictionaryItem); + } + + // do we have an item key collision (item keys must be unique)? + if (HasItemKeyCollision(dictionaryItem)) + { + return Attempt.FailWithStatus(DictionaryItemOperationStatus.DuplicateItemKey, dictionaryItem); + } + + // ensure valid languages for all translations + ILanguage[] allLanguages = (await _languageService.GetAllAsync()).ToArray(); + RemoveInvalidTranslations(dictionaryItem, allLanguages); + + EventMessages eventMessages = EventMessagesFactory.Get(); + var savingNotification = new DictionaryItemSavingNotification(dictionaryItem, eventMessages); + if (await scope.Notifications.PublishCancelableAsync(savingNotification)) + { + scope.Complete(); + return Attempt.FailWithStatus(DictionaryItemOperationStatus.CancelledByNotification, dictionaryItem); + } + + _dictionaryRepository.Save(dictionaryItem); + + // ensure the lazy Language callback is assigned + EnsureDictionaryItemLanguageCallback(dictionaryItem); + scope.Notifications.Publish( + new DictionaryItemSavedNotification(dictionaryItem, eventMessages).WithStateFrom(savingNotification)); + + Audit(auditType, auditMessage, userId, dictionaryItem.Id, nameof(DictionaryItem)); + scope.Complete(); + + return await Task.FromResult(Attempt.SucceedWithStatus(DictionaryItemOperationStatus.Success, dictionaryItem)); + } + } + + private async Task> GetByQueryAsync(IQuery query) + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) + { + IDictionaryItem[] items = _dictionaryRepository.Get(query).ToArray(); + + // ensure the lazy Language callback is assigned + foreach (IDictionaryItem item in items) + { + EnsureDictionaryItemLanguageCallback(item); + } + + return await Task.FromResult(items); + } + } + + private void Audit(AuditType type, string message, int userId, int objectId, string? entityType) => + _auditRepository.Save(new AuditItem(objectId, type, userId, entityType, message)); + + /// + /// This is here to take care of a hack - the DictionaryTranslation model contains an ILanguage reference which we + /// don't want but + /// we cannot remove it because it would be a large breaking change, so we need to make sure it's resolved lazily. This + /// is because + /// if developers have a lot of dictionary items and translations, the caching and cloning size gets much larger + /// because of + /// the large object graphs. So now we don't cache or clone the attached ILanguage + /// + private void EnsureDictionaryItemLanguageCallback(IDictionaryItem? d) + { + if (d is not DictionaryItem item) + { + return; + } + + // TODO: obsolete this! + item.GetLanguage = GetLanguageById; + IEnumerable translations = item.Translations.OfType(); + foreach (DictionaryTranslation trans in translations) + { + trans.GetLanguage = GetLanguageById; + } + } + + private bool HasValidParent(IDictionaryItem dictionaryItem) + => dictionaryItem.ParentId.HasValue == false || _dictionaryRepository.Get(dictionaryItem.ParentId.Value) != null; + + private void RemoveInvalidTranslations(IDictionaryItem dictionaryItem, IEnumerable allLanguages) + { + IDictionaryTranslation[] translationsAsArray = dictionaryItem.Translations.ToArray(); + if (translationsAsArray.Any() == false) + { + return; + } + + var allLanguageIds = allLanguages.Select(language => language.Id).ToArray(); + dictionaryItem.Translations = translationsAsArray.Where(translation => allLanguageIds.Contains(translation.LanguageId)).ToArray(); + } + + private bool HasItemKeyCollision(IDictionaryItem dictionaryItem) + { + IDictionaryItem? itemKeyCollision = _dictionaryRepository.Get(dictionaryItem.ItemKey); + return itemKeyCollision != null && itemKeyCollision.Key != dictionaryItem.Key; + } + + private ILanguage? GetLanguageById(int id) => _languageService.GetAllAsync().GetAwaiter().GetResult().FirstOrDefault(l => l.Id == id); +} diff --git a/src/Umbraco.Core/Services/IDictionaryItemService.cs b/src/Umbraco.Core/Services/IDictionaryItemService.cs new file mode 100644 index 0000000000..9d6fce5220 --- /dev/null +++ b/src/Umbraco.Core/Services/IDictionaryItemService.cs @@ -0,0 +1,93 @@ +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Core.Services; + +public interface IDictionaryItemService +{ + /// + /// Gets a by its id + /// + /// Id of the + /// + /// + /// + Task GetAsync(Guid id); + + /// + /// Gets a by by its key + /// + /// Key of the + /// + /// + /// + Task GetAsync(string key); + + /// + /// Gets a collection of by their ids + /// + /// Ids of the + /// + /// A collection of + /// + Task> GetManyAsync(params Guid[] ids); + + /// + /// Gets a collection of by their keys + /// + /// Keys of the + /// + /// A collection of + /// + Task> GetManyAsync(params string[] keys); + + /// + /// Gets a list of children for a + /// + /// Id of the parent + /// An enumerable list of objects + Task> GetChildrenAsync(Guid parentId); + + /// + /// Gets a list of descendants for a + /// + /// Id of the parent, null will return all dictionary items + /// An enumerable list of objects + Task> GetDescendantsAsync(Guid? parentId); + + /// + /// Gets the root/top objects + /// + /// An enumerable list of objects + Task> GetAtRootAsync(); + + /// + /// Checks if a with given key exists + /// + /// Key of the + /// True if a exists, otherwise false + Task ExistsAsync(string key); + + /// + /// Creates and saves a new dictionary item and assigns translations to all applicable languages if specified + /// + /// to create + /// Optional id of the user saving the dictionary item + /// + Task> CreateAsync(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId); + + /// + /// Updates an existing object + /// + /// to update + /// Optional id of the user saving the dictionary item + Task> UpdateAsync(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId); + + /// + /// Deletes a object and its related translations + /// as well as its children. + /// + /// The ID of the to delete + /// Optional id of the user deleting the dictionary item + Task> DeleteAsync(Guid id, int userId = Constants.Security.SuperUserId); +} diff --git a/src/Umbraco.Core/Services/ILanguageService.cs b/src/Umbraco.Core/Services/ILanguageService.cs new file mode 100644 index 0000000000..8217844e28 --- /dev/null +++ b/src/Umbraco.Core/Services/ILanguageService.cs @@ -0,0 +1,52 @@ +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Core.Services; + +public interface ILanguageService +{ + /// + /// Gets a by its iso code + /// + /// Iso Code of the language (ie. en-US) + /// + /// + /// + Task GetAsync(string isoCode); + + /// + /// Gets the default language ISO code. + /// + /// + /// This can be optimized and bypass all deep cloning. + /// + /// The default language ISO code + Task GetDefaultIsoCodeAsync(); + + /// + /// Gets all available languages + /// + /// An enumerable list of objects + Task> GetAllAsync(); + + /// + /// Updates an existing object + /// + /// to update + /// Optional id of the user saving the language + Task> UpdateAsync(ILanguage language, int userId = Constants.Security.SuperUserId); + + /// + /// Creates a new object + /// + /// to create + /// Optional id of the user creating the language + Task> CreateAsync(ILanguage language, int userId = Constants.Security.SuperUserId); + + /// + /// Deletes a by removing it and its usages from the db + /// + /// The ISO code of the to delete + /// Optional id of the user deleting the language + Task> DeleteAsync(string isoCode, int userId = Constants.Security.SuperUserId); +} diff --git a/src/Umbraco.Core/Services/ILocalizationService.cs b/src/Umbraco.Core/Services/ILocalizationService.cs index 7cd403b4ab..e1d8848c4f 100644 --- a/src/Umbraco.Core/Services/ILocalizationService.cs +++ b/src/Umbraco.Core/Services/ILocalizationService.cs @@ -1,5 +1,4 @@ using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services.OperationStatus; using Umbraco.New.Cms.Core.Models; namespace Umbraco.Cms.Core.Services; @@ -7,6 +6,7 @@ namespace Umbraco.Cms.Core.Services; /// /// Defines the Localization Service, which is an easy access to operations involving Languages and Dictionary /// +[Obsolete("Please use ILanguageService and IDictionaryItemService for localization. Will be removed in V15.")] public interface ILocalizationService : IService { // Possible to-do list: @@ -22,6 +22,7 @@ public interface ILocalizationService : IService /// /// /// + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] void AddOrUpdateDictionaryValue(IDictionaryItem item, ILanguage? language, string value); /// @@ -31,23 +32,9 @@ public interface ILocalizationService : IService /// /// /// - [Obsolete("Please use Create. Will be removed in V15")] + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] IDictionaryItem CreateDictionaryItemWithIdentity(string key, Guid? parentId, string? defaultValue = null); - /// - /// Creates and saves a new dictionary item and assigns translations to all applicable languages if specified - /// - /// - /// - /// - /// Optional id of the user saving the dictionary item - /// - Attempt Create( - string key, - Guid? parentId, - IEnumerable? translations = null, - int userId = Constants.Security.SuperUserId); - /// /// Gets a by its id /// @@ -55,6 +42,7 @@ public interface ILocalizationService : IService /// /// /// + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] IDictionaryItem? GetDictionaryItemById(int id); /// @@ -64,6 +52,7 @@ public interface ILocalizationService : IService /// /// /// + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] IDictionaryItem? GetDictionaryItemById(Guid id); /// @@ -73,6 +62,7 @@ public interface ILocalizationService : IService /// /// A collection of /// + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] IEnumerable GetDictionaryItemsByIds(params Guid[] ids) => Array.Empty(); /// @@ -82,6 +72,7 @@ public interface ILocalizationService : IService /// /// /// + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] IDictionaryItem? GetDictionaryItemByKey(string key); /// @@ -91,6 +82,7 @@ public interface ILocalizationService : IService /// /// A collection of /// + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] IEnumerable GetDictionaryItemsByKeys(params string[] keys) => Array.Empty(); /// @@ -98,6 +90,7 @@ public interface ILocalizationService : IService /// /// Id of the parent /// An enumerable list of objects + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] IEnumerable GetDictionaryItemChildren(Guid parentId); /// @@ -105,12 +98,14 @@ public interface ILocalizationService : IService /// /// Id of the parent, null will return all dictionary items /// An enumerable list of objects + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] IEnumerable GetDictionaryItemDescendants(Guid? parentId); /// /// Gets the root/top objects /// /// An enumerable list of objects + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] IEnumerable GetRootDictionaryItems(); /// @@ -118,6 +113,7 @@ public interface ILocalizationService : IService /// /// Key of the /// True if a exists, otherwise false + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] bool DictionaryItemExists(string key); /// @@ -125,33 +121,18 @@ public interface ILocalizationService : IService /// /// to save /// Optional id of the user saving the dictionary item - [Obsolete("Please use Update. Will be removed in V15")] + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] void Save(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId); - /// - /// Updates an existing object - /// - /// to update - /// Optional id of the user saving the dictionary item - Attempt Update(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId); - /// /// Deletes a object and its related translations /// as well as its children. /// /// to delete /// Optional id of the user deleting the dictionary item - [Obsolete("Please use the Delete method that takes an ID and returns an Attempt. Will be removed in V15")] + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] void Delete(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId); - /// - /// Deletes a object and its related translations - /// as well as its children. - /// - /// The ID of the to delete - /// Optional id of the user deleting the dictionary item - Attempt Delete(Guid id, int userId = Constants.Security.SuperUserId); - /// /// Gets a by its id /// @@ -159,6 +140,7 @@ public interface ILocalizationService : IService /// /// /// + [Obsolete("Please use ILanguageService for language operations. Will be removed in V15.")] ILanguage? GetLanguageById(int id); /// @@ -168,6 +150,7 @@ public interface ILocalizationService : IService /// /// /// + [Obsolete("Please use ILanguageService for language operations. Will be removed in V15.")] ILanguage? GetLanguageByIsoCode(string? isoCode); /// @@ -176,6 +159,7 @@ public interface ILocalizationService : IService /// /// This can be optimized and bypass all deep cloning. /// + [Obsolete("Please use ILanguageService for language operations. Will be removed in V15.")] int? GetLanguageIdByIsoCode(string isoCode); /// @@ -184,6 +168,7 @@ public interface ILocalizationService : IService /// /// This can be optimized and bypass all deep cloning. /// + [Obsolete("Please use ILanguageService for language operations. Will be removed in V15.")] string? GetLanguageIsoCodeById(int id); /// @@ -192,6 +177,7 @@ public interface ILocalizationService : IService /// /// This can be optimized and bypass all deep cloning. /// + [Obsolete("Please use ILanguageService for language operations. Will be removed in V15.")] string GetDefaultLanguageIsoCode(); /// @@ -200,12 +186,14 @@ public interface ILocalizationService : IService /// /// This can be optimized and bypass all deep cloning. /// + [Obsolete("Please use ILanguageService for language operations. Will be removed in V15.")] int? GetDefaultLanguageId(); /// /// Gets all available languages /// /// An enumerable list of objects + [Obsolete("Please use ILanguageService for language operations. Will be removed in V15.")] IEnumerable GetAllLanguages(); /// @@ -213,6 +201,7 @@ public interface ILocalizationService : IService /// /// to save /// Optional id of the user saving the language + [Obsolete("Please use ILanguageService for language operations. Will be removed in V15.")] void Save(ILanguage language, int userId = Constants.Security.SuperUserId); /// @@ -220,14 +209,17 @@ public interface ILocalizationService : IService /// /// to delete /// Optional id of the user deleting the language + [Obsolete("Please use ILanguageService for language operations. Will be removed in V15.")] void Delete(ILanguage language, int userId = Constants.Security.SuperUserId); /// /// Gets the full dictionary key map. /// /// The full dictionary key map. + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] Dictionary GetDictionaryItemKeyMap(); + [Obsolete("Please use ILanguageService for language operations. Will be removed in V15.")] PagedModel GetAllLanguagesPaged(int skip, int take) { ILanguage[] all = GetAllLanguages().Skip(skip).Take(take).ToArray(); diff --git a/src/Umbraco.Core/Services/LanguageService.cs b/src/Umbraco.Core/Services/LanguageService.cs new file mode 100644 index 0000000000..604b494799 --- /dev/null +++ b/src/Umbraco.Core/Services/LanguageService.cs @@ -0,0 +1,263 @@ +using System.Globalization; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Core.Services; + +internal class LanguageService : RepositoryService, ILanguageService +{ + private readonly ILanguageRepository _languageRepository; + private readonly IAuditRepository _auditRepository; + + public LanguageService( + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + ILanguageRepository languageRepository, + IAuditRepository auditRepository) + : base(provider, loggerFactory, eventMessagesFactory) + { + _languageRepository = languageRepository; + _auditRepository = auditRepository; + } + + /// + public async Task GetAsync(string isoCode) + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) + { + return await Task.FromResult(_languageRepository.GetByIsoCode(isoCode)); + } + } + + /// + public async Task GetDefaultIsoCodeAsync() + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) + { + return await Task.FromResult(_languageRepository.GetDefaultIsoCode()); + } + } + + /// + public async Task> GetAllAsync() + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) + { + return await Task.FromResult(_languageRepository.GetMany()); + } + } + + /// + public async Task> UpdateAsync(ILanguage language, int userId = Constants.Security.SuperUserId) + => await SaveAsync( + language, + () => + { + ILanguage? currentLanguage = _languageRepository.Get(language.Id); + if (currentLanguage == null) + { + return LanguageOperationStatus.NotFound; + } + + // ensure we don't un-default the default language + if (currentLanguage.IsDefault && !language.IsDefault) + { + return LanguageOperationStatus.MissingDefault; + } + + return LanguageOperationStatus.Success; + }, + AuditType.Save, + "Update Language", + userId); + + /// + public async Task> CreateAsync(ILanguage language, int userId = Constants.Security.SuperUserId) + { + if (language.Id != 0) + { + return Attempt.FailWithStatus(LanguageOperationStatus.InvalidId, language); + } + + return await SaveAsync( + language, + () => + { + // ensure no duplicates by ISO code + if (_languageRepository.GetByIsoCode(language.IsoCode) != null) + { + return LanguageOperationStatus.DuplicateIsoCode; + } + + return LanguageOperationStatus.Success; + }, + AuditType.New, + "Create Language", + userId); + } + + /// + public async Task> DeleteAsync(string isoCode, int userId = Constants.Security.SuperUserId) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + // write-lock languages to guard against race conds when dealing with default language + scope.WriteLock(Constants.Locks.Languages); + + ILanguage? language = _languageRepository.GetByIsoCode(isoCode); + if (language == null) + { + return Attempt.FailWithStatus(LanguageOperationStatus.NotFound, null); + } + + if (language.IsDefault) + { + return Attempt.FailWithStatus(LanguageOperationStatus.MissingDefault, language); + } + + EventMessages eventMessages = EventMessagesFactory.Get(); + var deletingLanguageNotification = new LanguageDeletingNotification(language, eventMessages); + if (await scope.Notifications.PublishCancelableAsync(deletingLanguageNotification)) + { + scope.Complete(); + return Attempt.FailWithStatus(LanguageOperationStatus.CancelledByNotification, language); + } + + // NOTE: Other than the fall-back language, there aren't any other constraints in the db, so possible references aren't deleted + _languageRepository.Delete(language); + + scope.Notifications.Publish( + new LanguageDeletedNotification(language, eventMessages).WithStateFrom(deletingLanguageNotification)); + + Audit(AuditType.Delete, "Delete Language", userId, language.Id, UmbracoObjectTypes.Language.GetName()); + scope.Complete(); + return await Task.FromResult(Attempt.SucceedWithStatus(LanguageOperationStatus.Success, language)); + } + } + + private async Task> SaveAsync( + ILanguage language, + Func operationValidation, + AuditType auditType, + string auditMessage, + int userId) + { + if (HasValidIsoCode(language) == false) + { + return Attempt.FailWithStatus(LanguageOperationStatus.InvalidIsoCode, language); + } + + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + // write-lock languages to guard against race conds when dealing with default language + scope.WriteLock(Constants.Locks.Languages); + + LanguageOperationStatus status = operationValidation(); + if (status != LanguageOperationStatus.Success) + { + return Attempt.FailWithStatus(status, language); + } + + // validate the fallback language - within write-lock (important!) + if (HasInvalidFallbackLanguage(language)) + { + return Attempt.FailWithStatus(LanguageOperationStatus.InvalidFallback, language); + } + + EventMessages eventMessages = EventMessagesFactory.Get(); + var savingNotification = new LanguageSavingNotification(language, eventMessages); + if (await scope.Notifications.PublishCancelableAsync(savingNotification)) + { + scope.Complete(); + return Attempt.FailWithStatus(LanguageOperationStatus.CancelledByNotification, language); + } + + _languageRepository.Save(language); + scope.Notifications.Publish( + new LanguageSavedNotification(language, eventMessages).WithStateFrom(savingNotification)); + + Audit(auditType, auditMessage, userId, language.Id, UmbracoObjectTypes.Language.GetName()); + + scope.Complete(); + return await Task.FromResult(Attempt.SucceedWithStatus(LanguageOperationStatus.Success, language)); + } + } + + private void Audit(AuditType type, string message, int userId, int objectId, string? entityType) => + _auditRepository.Save(new AuditItem(objectId, type, userId, entityType, message)); + + private bool HasInvalidFallbackLanguage(ILanguage language) + { + // no fallback language = valid + if (language.FallbackLanguageId.HasValue == false) + { + return false; + } + + // does the fallback language actually exist? + var languages = _languageRepository.GetMany().ToDictionary(x => x.Id, x => x); + if (languages.ContainsKey(language.FallbackLanguageId.Value) == false) + { + return true; + } + + // does the fallback language create a cycle? + if (CreatesCycle(language, languages)) + { + // explicitly logging this because it may not be obvious, specially with implicit cyclic fallbacks + LoggerFactory + .CreateLogger() + .Log(LogLevel.Error, $"Cannot use language {languages[language.FallbackLanguageId.Value].IsoCode} as fallback for language {language.IsoCode} as this would create a fallback cycle."); + + return true; + } + + return false; + } + + private bool CreatesCycle(ILanguage language, IDictionary languages) + { + // a new language is not referenced yet, so cannot be part of a cycle + if (!language.HasIdentity) + { + return false; + } + + var id = language.FallbackLanguageId; + + // assuming languages does not already contains a cycle, this must end + while (true) + { + if (!id.HasValue) + { + return false; // no fallback means no cycle + } + + if (id.Value == language.Id) + { + return true; // back to language = cycle! + } + + id = languages[id.Value].FallbackLanguageId; // else keep chaining + } + } + + private static bool HasValidIsoCode(ILanguage language) + { + try + { + var culture = CultureInfo.GetCultureInfo(language.IsoCode); + return culture.Name == language.IsoCode; + } + catch (CultureNotFoundException) + { + return false; + } + } +} diff --git a/src/Umbraco.Core/Services/LocalizationService.cs b/src/Umbraco.Core/Services/LocalizationService.cs index 73faa6c460..b7b26f38cc 100644 --- a/src/Umbraco.Core/Services/LocalizationService.cs +++ b/src/Umbraco.Core/Services/LocalizationService.cs @@ -1,7 +1,8 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services.OperationStatus; @@ -13,12 +14,16 @@ namespace Umbraco.Cms.Core.Services; /// Represents the Localization Service, which is an easy access to operations involving and /// /// +[Obsolete("Please use ILanguageService and IDictionaryItemService for localization. Will be removed in V15.")] internal class LocalizationService : RepositoryService, ILocalizationService { private readonly IAuditRepository _auditRepository; private readonly IDictionaryRepository _dictionaryRepository; private readonly ILanguageRepository _languageRepository; + private readonly ILanguageService _languageService; + private readonly IDictionaryItemService _dictionaryItemService; + [Obsolete("Please use constructor with language and dictionary services. Will be removed in V15")] public LocalizationService( ICoreScopeProvider provider, ILoggerFactory loggerFactory, @@ -26,11 +31,35 @@ internal class LocalizationService : RepositoryService, ILocalizationService IDictionaryRepository dictionaryRepository, IAuditRepository auditRepository, ILanguageRepository languageRepository) + : this( + provider, + loggerFactory, + eventMessagesFactory, + dictionaryRepository, + auditRepository, + languageRepository, + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService()) + { + } + + [Obsolete("Please use ILanguageService and IDictionaryItemService for localization. Will be removed in V15.")] + public LocalizationService( + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IDictionaryRepository dictionaryRepository, + IAuditRepository auditRepository, + ILanguageRepository languageRepository, + ILanguageService languageService, + IDictionaryItemService dictionaryItemService) : base(provider, loggerFactory, eventMessagesFactory) { _dictionaryRepository = dictionaryRepository; _auditRepository = auditRepository; _languageRepository = languageRepository; + _languageService = languageService; + _dictionaryItemService = dictionaryItemService; } /// @@ -43,6 +72,7 @@ internal class LocalizationService : RepositoryService, ILocalizationService /// /// This does not save the item, that needs to be done explicitly /// + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] public void AddOrUpdateDictionaryValue(IDictionaryItem item, ILanguage? language, string value) { if (item == null) @@ -55,25 +85,7 @@ internal class LocalizationService : RepositoryService, ILocalizationService throw new ArgumentNullException(nameof(language)); } - IDictionaryTranslation? existing = item.Translations?.FirstOrDefault(x => x.Language?.Id == language.Id); - if (existing != null) - { - existing.Value = value; - } - else - { - if (item.Translations is not null) - { - item.Translations = new List(item.Translations) - { - new DictionaryTranslation(language, value), - }; - } - else - { - item.Translations = new List { new DictionaryTranslation(language, value) }; - } - } + item.AddOrUpdateDictionaryValue(language, value); } /// @@ -83,7 +95,7 @@ internal class LocalizationService : RepositoryService, ILocalizationService /// /// /// - [Obsolete("Please use Create. Will be removed in V15")] + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] public IDictionaryItem CreateDictionaryItemWithIdentity(string key, Guid? parentId, string? defaultValue = null) { IEnumerable translations = defaultValue.IsNullOrWhiteSpace() @@ -92,70 +104,16 @@ internal class LocalizationService : RepositoryService, ILocalizationService .Select(language => new DictionaryTranslation(language, defaultValue!)) .ToArray(); - Attempt result = Create(key, parentId, translations); - return result.Success - ? result.Result! + Attempt result = _dictionaryItemService + .CreateAsync(new DictionaryItem(parentId, key) { Translations = translations }) + .GetAwaiter() + .GetResult(); + // mimic old service behavior + return result.Success || result.Status == DictionaryItemOperationStatus.CancelledByNotification + ? result.Result : throw new ArgumentException($"Could not create a dictionary item with key: {key} under parent: {parentId}"); } - /// - public Attempt Create( - string key, - Guid? parentId, - IEnumerable? translations = null, - int userId = Constants.Security.SuperUserId) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) - { - // validate the parent - if (parentId.HasValue && parentId.Value != Guid.Empty) - { - IDictionaryItem? parent = GetDictionaryItemById(parentId.Value); - if (parent == null) - { - return Attempt.FailWithStatus(DictionaryItemOperationStatus.ParentNotFound, null); - } - } - - var item = new DictionaryItem(parentId, key); - - // do we have an item key collision (item keys must be unique)? - if (HasItemKeyCollision(item)) - { - return Attempt.FailWithStatus(DictionaryItemOperationStatus.DuplicateItemKey, null); - } - - IDictionaryTranslation[] translationsAsArray = translations?.ToArray() ?? Array.Empty(); - if (translationsAsArray.Any()) - { - var allLanguageIds = GetAllLanguages().Select(language => language.Id).ToArray(); - item.Translations = translationsAsArray.Where(translation => allLanguageIds.Contains(translation.LanguageId)).ToArray(); - } - - EventMessages eventMessages = EventMessagesFactory.Get(); - var savingNotification = new DictionaryItemSavingNotification(item, eventMessages); - - if (scope.Notifications.PublishCancelable(savingNotification)) - { - scope.Complete(); - return Attempt.FailWithStatus(DictionaryItemOperationStatus.CancelledByNotification, item); - } - - _dictionaryRepository.Save(item); - - // ensure the lazy Language callback is assigned - EnsureDictionaryItemLanguageCallback(item); - - scope.Notifications.Publish( - new DictionaryItemSavedNotification(item, eventMessages).WithStateFrom(savingNotification)); - - Audit(AuditType.New, "Create DictionaryItem", userId, item.Id, "DictionaryItem"); - scope.Complete(); - - return Attempt.SucceedWithStatus(DictionaryItemOperationStatus.Success, item); - } - } - /// /// Gets a by its id /// @@ -163,6 +121,7 @@ internal class LocalizationService : RepositoryService, ILocalizationService /// /// /// + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] public IDictionaryItem? GetDictionaryItemById(int id) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) @@ -182,17 +141,9 @@ internal class LocalizationService : RepositoryService, ILocalizationService /// /// /// + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] public IDictionaryItem? GetDictionaryItemById(Guid id) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - IDictionaryItem? item = _dictionaryRepository.Get(id); - - // ensure the lazy Language callback is assigned - EnsureDictionaryItemLanguageCallback(item); - return item; - } - } + => _dictionaryItemService.GetAsync(id).GetAwaiter().GetResult(); /// /// Gets a collection by their ids @@ -201,21 +152,9 @@ internal class LocalizationService : RepositoryService, ILocalizationService /// /// A collection of /// + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] public IEnumerable GetDictionaryItemsByIds(params Guid[] ids) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - IEnumerable items = _dictionaryRepository.GetMany(ids).ToArray(); - - // ensure the lazy Language callback is assigned - foreach (IDictionaryItem item in items) - { - EnsureDictionaryItemLanguageCallback(item); - } - - return items; - } - } + => _dictionaryItemService.GetManyAsync(ids).GetAwaiter().GetResult(); /// /// Gets a by its key @@ -224,17 +163,9 @@ internal class LocalizationService : RepositoryService, ILocalizationService /// /// /// + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] public IDictionaryItem? GetDictionaryItemByKey(string key) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - IDictionaryItem? item = _dictionaryRepository.Get(key); - - // ensure the lazy Language callback is assigned - EnsureDictionaryItemLanguageCallback(item); - return item; - } - } + => _dictionaryItemService.GetAsync(key).GetAwaiter().GetResult(); /// /// Gets a collection of by their keys @@ -243,159 +174,60 @@ internal class LocalizationService : RepositoryService, ILocalizationService /// /// A collection of /// + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] public IEnumerable GetDictionaryItemsByKeys(params string[] keys) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - IEnumerable items = _dictionaryRepository.GetManyByKeys(keys).ToArray(); + => _dictionaryItemService.GetManyAsync(keys).GetAwaiter().GetResult(); - // ensure the lazy Language callback is assigned - foreach (IDictionaryItem item in items) - { - EnsureDictionaryItemLanguageCallback(item); - } - return items; - } - } - - /// - /// Gets a list of children for a - /// - /// Id of the parent - /// An enumerable list of objects - public IEnumerable GetDictionaryItemChildren(Guid parentId) - { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - var query = Query().Where(x => x.ParentId == parentId); - var items = _dictionaryRepository.Get(query).ToArray(); - //ensure the lazy Language callback is assigned - foreach (var item in items) - EnsureDictionaryItemLanguageCallback(item); - - return items; - } - } + /// + /// Gets a list of children for a + /// + /// Id of the parent + /// An enumerable list of objects + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] + public IEnumerable GetDictionaryItemChildren(Guid parentId) + => _dictionaryItemService.GetChildrenAsync(parentId).GetAwaiter().GetResult(); /// /// Gets a list of descendants for a /// /// Id of the parent, null will return all dictionary items /// An enumerable list of objects + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] public IEnumerable GetDictionaryItemDescendants(Guid? parentId) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - IDictionaryItem[] items = _dictionaryRepository.GetDictionaryItemDescendants(parentId).ToArray(); + => _dictionaryItemService.GetDescendantsAsync(parentId).GetAwaiter().GetResult(); - // ensure the lazy Language callback is assigned - foreach (IDictionaryItem item in items) - { - EnsureDictionaryItemLanguageCallback(item); - } - - return items; - } - } - - /// - /// Gets the root/top objects - /// - /// An enumerable list of objects - public IEnumerable GetRootDictionaryItems() - { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - var query = Query().Where(x => x.ParentId == null); - var items = _dictionaryRepository.Get(query).ToArray(); - //ensure the lazy Language callback is assigned - foreach (var item in items) - EnsureDictionaryItemLanguageCallback(item); - return items; - } - } + /// + /// Gets the root/top objects + /// + /// An enumerable list of objects + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] + public IEnumerable GetRootDictionaryItems() + => _dictionaryItemService.GetAtRootAsync().GetAwaiter().GetResult(); /// /// Checks if a with given key exists /// /// Key of the /// True if a exists, otherwise false + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] public bool DictionaryItemExists(string key) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - IDictionaryItem? item = _dictionaryRepository.Get(key); - return item != null; - } - } + => _dictionaryItemService.ExistsAsync(key).GetAwaiter().GetResult(); /// /// Saves a object /// /// to save /// Optional id of the user saving the dictionary item - [Obsolete("Please use Update. Will be removed in V15")] + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] public void Save(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId) { - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + if (dictionaryItem.Id > 0) { - EventMessages eventMessages = EventMessagesFactory.Get(); - var savingNotification = new DictionaryItemSavingNotification(dictionaryItem, eventMessages); - if (scope.Notifications.PublishCancelable(savingNotification)) - { - scope.Complete(); - return; - } - - _dictionaryRepository.Save(dictionaryItem); - - // ensure the lazy Language callback is assigned - // ensure the lazy Language callback is assigned - EnsureDictionaryItemLanguageCallback(dictionaryItem); - scope.Notifications.Publish( - new DictionaryItemSavedNotification(dictionaryItem, eventMessages).WithStateFrom(savingNotification)); - - Audit(AuditType.Save, "Save DictionaryItem", userId, dictionaryItem.Id, "DictionaryItem"); - scope.Complete(); + _dictionaryItemService.UpdateAsync(dictionaryItem, userId).GetAwaiter().GetResult(); } - } - - /// - public Attempt Update(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + else { - // is there an item to update? - if (_dictionaryRepository.Exists(dictionaryItem.Id) == false) - { - return Attempt.FailWithStatus(DictionaryItemOperationStatus.ItemNotFound, dictionaryItem); - } - - // do we have an item key collision (item keys must be unique)? - if (HasItemKeyCollision(dictionaryItem)) - { - return Attempt.FailWithStatus(DictionaryItemOperationStatus.DuplicateItemKey, dictionaryItem); - } - - EventMessages eventMessages = EventMessagesFactory.Get(); - var savingNotification = new DictionaryItemSavingNotification(dictionaryItem, eventMessages); - if (scope.Notifications.PublishCancelable(savingNotification)) - { - scope.Complete(); - return Attempt.FailWithStatus(DictionaryItemOperationStatus.CancelledByNotification, dictionaryItem); - } - - _dictionaryRepository.Save(dictionaryItem); - - // ensure the lazy Language callback is assigned - EnsureDictionaryItemLanguageCallback(dictionaryItem); - scope.Notifications.Publish( - new DictionaryItemSavedNotification(dictionaryItem, eventMessages).WithStateFrom(savingNotification)); - - Audit(AuditType.Save, "Update DictionaryItem", userId, dictionaryItem.Id, "DictionaryItem"); - scope.Complete(); - - return Attempt.SucceedWithStatus(DictionaryItemOperationStatus.Success, dictionaryItem); + _dictionaryItemService.CreateAsync(dictionaryItem, userId).GetAwaiter().GetResult(); } } @@ -405,40 +237,9 @@ internal class LocalizationService : RepositoryService, ILocalizationService /// /// to delete /// Optional id of the user deleting the dictionary item - [Obsolete("Please use the Delete method that takes an ID and returns an Attempt. Will be removed in V15")] + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] public void Delete(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId) - => Delete(dictionaryItem.Key, userId); - - /// - public Attempt Delete(Guid id, int userId = Constants.Security.SuperUserId) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) - { - IDictionaryItem? dictionaryItem = _dictionaryRepository.Get(id); - if (dictionaryItem == null) - { - return Attempt.FailWithStatus(DictionaryItemOperationStatus.ItemNotFound, null); - } - - EventMessages eventMessages = EventMessagesFactory.Get(); - var deletingNotification = new DictionaryItemDeletingNotification(dictionaryItem, eventMessages); - if (scope.Notifications.PublishCancelable(deletingNotification)) - { - scope.Complete(); - return Attempt.FailWithStatus(DictionaryItemOperationStatus.CancelledByNotification, dictionaryItem); - } - - _dictionaryRepository.Delete(dictionaryItem); - scope.Notifications.Publish( - new DictionaryItemDeletedNotification(dictionaryItem, eventMessages) - .WithStateFrom(deletingNotification)); - - Audit(AuditType.Delete, "Delete DictionaryItem", userId, dictionaryItem.Id, "DictionaryItem"); - - scope.Complete(); - return Attempt.SucceedWithStatus(DictionaryItemOperationStatus.Success, dictionaryItem); - } - } + => _dictionaryItemService.DeleteAsync(dictionaryItem.Key, userId).GetAwaiter().GetResult(); /// /// Gets a by its id @@ -447,6 +248,7 @@ internal class LocalizationService : RepositoryService, ILocalizationService /// /// /// + [Obsolete("Please use ILanguageService for language operations. Will be removed in V15.")] public ILanguage? GetLanguageById(int id) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) @@ -462,29 +264,20 @@ internal class LocalizationService : RepositoryService, ILocalizationService /// /// /// + [Obsolete("Please use ILanguageService for language operations. Will be removed in V15.")] public ILanguage? GetLanguageByIsoCode(string? isoCode) { - if (isoCode is null) - { - return null; - } - - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _languageRepository.GetByIsoCode(isoCode); - } + ArgumentException.ThrowIfNullOrEmpty(isoCode); + return _languageService.GetAsync(isoCode).GetAwaiter().GetResult(); } /// + [Obsolete("Please use ILanguageService for language operations. Will be removed in V15.")] public int? GetLanguageIdByIsoCode(string isoCode) - { - using (ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _languageRepository.GetIdByIsoCode(isoCode); - } - } + => _languageService.GetAsync(isoCode).GetAwaiter().GetResult()?.Id; /// + [Obsolete("Please use ILanguageService for language operations. Will be removed in V15.")] public string? GetLanguageIsoCodeById(int id) { using (ScopeProvider.CreateCoreScope(autoComplete: true)) @@ -494,15 +287,12 @@ internal class LocalizationService : RepositoryService, ILocalizationService } /// + [Obsolete("Please use ILanguageService for language operations. Will be removed in V15.")] public string GetDefaultLanguageIsoCode() - { - using (ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _languageRepository.GetDefaultIsoCode(); - } - } + => _languageService.GetDefaultIsoCodeAsync().GetAwaiter().GetResult(); /// + [Obsolete("Please use ILanguageService for language operations. Will be removed in V15.")] public int? GetDefaultLanguageId() { using (ScopeProvider.CreateCoreScope(autoComplete: true)) @@ -515,58 +305,26 @@ internal class LocalizationService : RepositoryService, ILocalizationService /// Gets all available languages /// /// An enumerable list of objects + [Obsolete("Please use ILanguageService for language operations. Will be removed in V15.")] public IEnumerable GetAllLanguages() - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _languageRepository.GetMany(); - } - } + => _languageService.GetAllAsync().GetAwaiter().GetResult(); /// /// Saves a object /// /// to save /// Optional id of the user saving the language + [Obsolete("Please use ILanguageService for language operations. Will be removed in V15.")] public void Save(ILanguage language, int userId = Constants.Security.SuperUserId) { - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + Attempt result = language.Id > 0 + ? _languageService.UpdateAsync(language, userId).GetAwaiter().GetResult() + : _languageService.CreateAsync(language, userId).GetAwaiter().GetResult(); + + // mimic old Save behavior + if (result.Status == LanguageOperationStatus.InvalidFallback) { - // write-lock languages to guard against race conds when dealing with default language - scope.WriteLock(Constants.Locks.Languages); - - // look for cycles - within write-lock - if (language.FallbackLanguageId.HasValue) - { - var languages = _languageRepository.GetMany().ToDictionary(x => x.Id, x => x); - if (!languages.ContainsKey(language.FallbackLanguageId.Value)) - { - throw new InvalidOperationException( - $"Cannot save language {language.IsoCode} with fallback id={language.FallbackLanguageId.Value} which is not a valid language id."); - } - - if (CreatesCycle(language, languages)) - { - throw new InvalidOperationException( - $"Cannot save language {language.IsoCode} with fallback {languages[language.FallbackLanguageId.Value].IsoCode} as it would create a fallback cycle."); - } - } - - EventMessages eventMessages = EventMessagesFactory.Get(); - var savingNotification = new LanguageSavingNotification(language, eventMessages); - if (scope.Notifications.PublishCancelable(savingNotification)) - { - scope.Complete(); - return; - } - - _languageRepository.Save(language); - scope.Notifications.Publish( - new LanguageSavedNotification(language, eventMessages).WithStateFrom(savingNotification)); - - Audit(AuditType.Save, "Save Language", userId, language.Id, UmbracoObjectTypes.Language.GetName()); - - scope.Complete(); + throw new InvalidOperationException($"Cannot save language {language.IsoCode} with fallback id={language.FallbackLanguageId}."); } } @@ -575,32 +333,11 @@ internal class LocalizationService : RepositoryService, ILocalizationService /// /// to delete /// Optional id of the user deleting the language + [Obsolete("Please use ILanguageService for language operations. Will be removed in V15.")] public void Delete(ILanguage language, int userId = Constants.Security.SuperUserId) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) - { - // write-lock languages to guard against race conds when dealing with default language - scope.WriteLock(Constants.Locks.Languages); - - EventMessages eventMessages = EventMessagesFactory.Get(); - var deletingLanguageNotification = new LanguageDeletingNotification(language, eventMessages); - if (scope.Notifications.PublishCancelable(deletingLanguageNotification)) - { - scope.Complete(); - return; - } - - // NOTE: Other than the fall-back language, there aren't any other constraints in the db, so possible references aren't deleted - _languageRepository.Delete(language); - - scope.Notifications.Publish( - new LanguageDeletedNotification(language, eventMessages).WithStateFrom(deletingLanguageNotification)); - - Audit(AuditType.Delete, "Delete Language", userId, language.Id, UmbracoObjectTypes.Language.GetName()); - scope.Complete(); - } - } + => _languageService.DeleteAsync(language.IsoCode, userId).GetAwaiter().GetResult(); + [Obsolete("Please use IDictionaryItemService for dictionary item operations. Will be removed in V15.")] public Dictionary GetDictionaryItemKeyMap() { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) @@ -609,36 +346,6 @@ internal class LocalizationService : RepositoryService, ILocalizationService } } - private bool CreatesCycle(ILanguage language, IDictionary languages) - { - // a new language is not referenced yet, so cannot be part of a cycle - if (!language.HasIdentity) - { - return false; - } - - var id = language.FallbackLanguageId; - - // assuming languages does not already contains a cycle, this must end - while (true) - { - if (!id.HasValue) - { - return false; // no fallback means no cycle - } - - if (id.Value == language.Id) - { - return true; // back to language = cycle! - } - - id = languages[id.Value].FallbackLanguageId; // else keep chaining - } - } - - private void Audit(AuditType type, string message, int userId, int objectId, string? entityType) => - _auditRepository.Save(new AuditItem(objectId, type, userId, entityType, message)); - /// /// This is here to take care of a hack - the DictionaryTranslation model contains an ILanguage reference which we /// don't want but @@ -665,10 +372,4 @@ internal class LocalizationService : RepositoryService, ILocalizationService } } } - - private bool HasItemKeyCollision(IDictionaryItem dictionaryItem) - { - IDictionaryItem? itemKeyCollision = _dictionaryRepository.Get(dictionaryItem.ItemKey); - return itemKeyCollision != null && itemKeyCollision.Key != dictionaryItem.Key; - } } diff --git a/src/Umbraco.Core/Services/OperationStatus/DictionaryItemOperationStatus.cs b/src/Umbraco.Core/Services/OperationStatus/DictionaryItemOperationStatus.cs index d15e97c7ba..b993118ddb 100644 --- a/src/Umbraco.Core/Services/OperationStatus/DictionaryItemOperationStatus.cs +++ b/src/Umbraco.Core/Services/OperationStatus/DictionaryItemOperationStatus.cs @@ -6,5 +6,7 @@ public enum DictionaryItemOperationStatus CancelledByNotification, DuplicateItemKey, ItemNotFound, - ParentNotFound + ParentNotFound, + InvalidId, + DuplicateKey } diff --git a/src/Umbraco.Core/Services/OperationStatus/LanguageOperationStatus.cs b/src/Umbraco.Core/Services/OperationStatus/LanguageOperationStatus.cs new file mode 100644 index 0000000000..414211b3ee --- /dev/null +++ b/src/Umbraco.Core/Services/OperationStatus/LanguageOperationStatus.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Cms.Core.Services.OperationStatus; + +public enum LanguageOperationStatus +{ + Success, + CancelledByNotification, + InvalidFallback, + NotFound, + MissingDefault, + DuplicateIsoCode, + InvalidIsoCode, + InvalidId +} diff --git a/src/Umbraco.New.Cms.Core/Services/Installer/ILanguageService.cs b/src/Umbraco.New.Cms.Core/Services/Installer/ILanguageService.cs deleted file mode 100644 index 29bb2df265..0000000000 --- a/src/Umbraco.New.Cms.Core/Services/Installer/ILanguageService.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Umbraco.Cms.Core.Models; - -namespace Umbraco.New.Cms.Core.Services.Installer; - -public interface ILanguageService -{ - bool LanguageAlreadyExists(int id, string isoCode); - - bool CanUseLanguagesFallbackLanguage(ILanguage language); - bool CanGetProperFallbackLanguage(ILanguage existingById); -} diff --git a/src/Umbraco.New.Cms.Core/Services/Languages/LanguageService.cs b/src/Umbraco.New.Cms.Core/Services/Languages/LanguageService.cs deleted file mode 100644 index 73a4ded3d5..0000000000 --- a/src/Umbraco.New.Cms.Core/Services/Languages/LanguageService.cs +++ /dev/null @@ -1,92 +0,0 @@ -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; -using Umbraco.New.Cms.Core.Services.Installer; - -namespace Umbraco.New.Cms.Core.Services.Languages; - -public class LanguageService : ILanguageService -{ - private readonly ILocalizationService _localizationService; - - public LanguageService(ILocalizationService localizationService) - { - _localizationService = localizationService; - } - - public bool LanguageAlreadyExists(int id, string isoCode) - { - // this is prone to race conditions but the service will not let us proceed anyways - ILanguage? existingByCulture = _localizationService.GetLanguageByIsoCode(isoCode); - - // the localization service might return the generic language even when queried for specific ones (e.g. "da" when queried for "da-DK") - // - we need to handle that explicitly - if (existingByCulture?.IsoCode != isoCode) - { - existingByCulture = null; - } - - if (existingByCulture != null && id != existingByCulture.Id) - { - return true; - } - - ILanguage? existingById = id != default ? _localizationService.GetLanguageById(id) : null; - return existingById is not null; - } - - public bool CanUseLanguagesFallbackLanguage(ILanguage language) - { - if (!language.FallbackLanguageId.HasValue) - { - return false; - } - - var languages = _localizationService.GetAllLanguages().ToDictionary(x => x.Id, x => x); - return languages.ContainsKey(language.FallbackLanguageId.Value); - - } - - public bool CanGetProperFallbackLanguage(ILanguage existingById) - { - // modifying an existing language can create a fallback, verify - // note that the service will check again, dealing with race conditions - if (existingById.FallbackLanguageId.HasValue) - { - var languages = _localizationService.GetAllLanguages().ToDictionary(x => x.Id, x => x); - - if (CreatesCycle(existingById, languages)) - { - return false; - } - } - - return true; - } - - - // see LocalizationService - private bool CreatesCycle(ILanguage language, IDictionary languages) - { - // a new language is not referenced yet, so cannot be part of a cycle - if (!language.HasIdentity) - { - return false; - } - - var id = language.FallbackLanguageId; - while (true) // assuming languages does not already contains a cycle, this must end - { - if (!id.HasValue) - { - return false; // no fallback means no cycle - } - - if (id.Value == language.Id) - { - return true; // back to language = cycle! - } - - id = languages[id.Value].FallbackLanguageId; // else keep chaining - } - } -} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Packaging/CreatedPackagesRepositoryTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Packaging/CreatedPackagesRepositoryTests.cs index f526541bbb..d94fa07d20 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Packaging/CreatedPackagesRepositoryTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Packaging/CreatedPackagesRepositoryTests.cs @@ -47,6 +47,8 @@ public class CreatedPackagesRepositoryTests : UmbracoIntegrationTest private IMacroService MacroService => GetRequiredService(); + private IDictionaryItemService DictionaryItemService => GetRequiredService(); + private ILocalizationService LocalizationService => GetRequiredService(); private IEntityXmlSerializer EntityXmlSerializer => GetRequiredService(); @@ -166,13 +168,13 @@ public class CreatedPackagesRepositoryTests : UmbracoIntegrationTest } [Test] - public void GivenNestedDictionaryItems_WhenPackageExported_ThenTheXmlIsNested() + public async Task GivenNestedDictionaryItems_WhenPackageExported_ThenTheXmlIsNested() { - var parent = LocalizationService.Create("Parent", null).Result!; - var child1 = LocalizationService.Create("Child1", parent.Key).Result!; - var child2 = LocalizationService.Create("Child2", child1.Key).Result!; - var child3 = LocalizationService.Create("Child3", child2.Key).Result!; - var child4 = LocalizationService.Create("Child4", child3.Key).Result!; + var parent = (await DictionaryItemService.CreateAsync(new DictionaryItem("Parent"))).Result; + var child1 = (await DictionaryItemService.CreateAsync(new DictionaryItem(parent.Key, "Child1"))).Result; + var child2 = (await DictionaryItemService.CreateAsync(new DictionaryItem(child1.Key, "Child2"))).Result; + var child3 = (await DictionaryItemService.CreateAsync(new DictionaryItem(child2.Key, "Child3"))).Result; + var child4 = (await DictionaryItemService.CreateAsync(new DictionaryItem(child3.Key, "Child4"))).Result; var def = new PackageDefinition { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TelemetryProviderTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TelemetryProviderTests.cs index c7a032df8f..7f9dcf5175 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TelemetryProviderTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TelemetryProviderTests.cs @@ -53,7 +53,7 @@ public class TelemetryProviderTests : UmbracoIntegrationTest private PropertyEditorTelemetryProvider PropertyEditorTelemetryProvider => GetRequiredService(); - private ILocalizationService LocalizationService => GetRequiredService(); + private ILanguageService LanguageService => GetRequiredService(); private IUserService UserService => GetRequiredService(); @@ -128,14 +128,14 @@ public class TelemetryProviderTests : UmbracoIntegrationTest } [Test] - public void Language_Telemetry_Can_Get_Languages() + public async Task Language_Telemetry_Can_Get_Languages() { // Arrange var langTwo = _languageBuilder.WithCultureInfo("da-DK").Build(); var langThree = _languageBuilder.WithCultureInfo("se-SV").Build(); - LocalizationService.Save(langTwo); - LocalizationService.Save(langThree); + await LanguageService.CreateAsync(langTwo); + await LanguageService.CreateAsync(langThree); IEnumerable result = null; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Variants/ContentVariantAllowedActionTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Variants/ContentVariantAllowedActionTests.cs index 1c4142e287..2a0b9c3168 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Variants/ContentVariantAllowedActionTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Variants/ContentVariantAllowedActionTests.cs @@ -1,5 +1,4 @@ -using System.Linq; -using NUnit.Framework; +using NUnit.Framework; using Umbraco.Cms.Core.Actions; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; @@ -18,28 +17,28 @@ public class ContentVariantAllowedActionTests : UmbracoTestServerTestBase { private const string UsIso = "en-US"; private const string DkIso = "da-DK"; - private ILocalizationService LocalizationService => GetRequiredService(); + private ILanguageService LanguageService => GetRequiredService(); private IUserService UserService => GetRequiredService(); private IUmbracoMapper UmbracoMapper => GetRequiredService(); [SetUp] - public void SetUpTestDate() + public async Task SetUpTestDate() { var dk = new Language(DkIso, "Danish"); - LocalizationService.Save(dk); + await LanguageService.CreateAsync(dk); } [Test] - public void CanCheckIfUserHasAccessToLanguage() + public async Task CanCheckIfUserHasAccessToLanguage() { // setup user groups var user = UserBuilder.CreateUser(); UserService.Save(user); var userGroup = UserGroupBuilder.CreateUserGroup(); - var languageId = LocalizationService.GetLanguageIdByIsoCode(DkIso); + var languageId = (await LanguageService.GetAsync(DkIso))?.Id; userGroup.AddAllowedLanguage(languageId!.Value); UserService.Save(userGroup, new []{ user.Id}); var currentUser = UserService.GetUserById(user.Id); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Packaging/PackageDataInstallationTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Packaging/PackageDataInstallationTests.cs index 40bdd6cf67..4c3efd5df9 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Packaging/PackageDataInstallationTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Packaging/PackageDataInstallationTests.cs @@ -1,9 +1,6 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; -using System.Linq; using System.Xml.Linq; using NUnit.Framework; using Umbraco.Cms.Core; @@ -17,7 +14,6 @@ using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services.Importing; -using Umbraco.Extensions; namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Packaging; @@ -26,7 +22,9 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Packaging; [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, WithApplication = true)] public class PackageDataInstallationTests : UmbracoIntegrationTestWithContent { - private ILocalizationService LocalizationService => GetRequiredService(); + private ILanguageService LanguageService => GetRequiredService(); + + private IDictionaryItemService DictionaryItemService => GetRequiredService(); private IMacroService MacroService => GetRequiredService(); @@ -559,7 +557,7 @@ public class PackageDataInstallationTests : UmbracoIntegrationTestWithContent } [Test] - public void Can_Import_DictionaryItems() + public async Task Can_Import_DictionaryItems() { // Arrange const string expectedEnglishParentValue = "ParentValue"; @@ -570,20 +568,20 @@ public class PackageDataInstallationTests : UmbracoIntegrationTestWithContent var newPackageXml = XElement.Parse(ImportResources.Dictionary_Package); var dictionaryItemsElement = newPackageXml.Elements("DictionaryItems").First(); - AddLanguages(); + await AddLanguages(); // Act PackageDataInstallation.ImportDictionaryItems(dictionaryItemsElement.Elements("DictionaryItem"), 0); // Assert - AssertDictionaryItem("Parent", expectedEnglishParentValue, "en-GB"); - AssertDictionaryItem("Parent", expectedNorwegianParentValue, "nb-NO"); - AssertDictionaryItem("Child", expectedEnglishChildValue, "en-GB"); - AssertDictionaryItem("Child", expectedNorwegianChildValue, "nb-NO"); + await AssertDictionaryItem("Parent", expectedEnglishParentValue, "en-GB"); + await AssertDictionaryItem("Parent", expectedNorwegianParentValue, "nb-NO"); + await AssertDictionaryItem("Child", expectedEnglishChildValue, "en-GB"); + await AssertDictionaryItem("Child", expectedNorwegianChildValue, "nb-NO"); } [Test] - public void Can_Import_Nested_DictionaryItems() + public async Task Can_Import_Nested_DictionaryItems() { // Arrange const string parentKey = "Parent"; @@ -592,25 +590,25 @@ public class PackageDataInstallationTests : UmbracoIntegrationTestWithContent var newPackageXml = XElement.Parse(ImportResources.Dictionary_Package); var dictionaryItemsElement = newPackageXml.Elements("DictionaryItems").First(); - AddLanguages(); + await AddLanguages(); // Act var dictionaryItems = PackageDataInstallation.ImportDictionaryItems(dictionaryItemsElement.Elements("DictionaryItem"), 0); // Assert - Assert.That(LocalizationService.DictionaryItemExists(parentKey), "DictionaryItem parentKey does not exist"); - Assert.That(LocalizationService.DictionaryItemExists(childKey), "DictionaryItem childKey does not exist"); + Assert.That(await DictionaryItemService.ExistsAsync(parentKey), "DictionaryItem parentKey does not exist"); + Assert.That(await DictionaryItemService.ExistsAsync(childKey), "DictionaryItem childKey does not exist"); - var parentDictionaryItem = LocalizationService.GetDictionaryItemByKey(parentKey); - var childDictionaryItem = LocalizationService.GetDictionaryItemByKey(childKey); + var parentDictionaryItem = await DictionaryItemService.GetAsync(parentKey); + var childDictionaryItem = await DictionaryItemService.GetAsync(childKey); Assert.That(parentDictionaryItem.ParentId, Is.Not.EqualTo(childDictionaryItem.ParentId)); Assert.That(childDictionaryItem.ParentId, Is.EqualTo(parentDictionaryItem.Key)); } [Test] - public void WhenExistingDictionaryKey_ImportsNewChildren() + public async Task WhenExistingDictionaryKey_ImportsNewChildren() { // Arrange const string expectedEnglishParentValue = "ExistingParentValue"; @@ -621,21 +619,21 @@ public class PackageDataInstallationTests : UmbracoIntegrationTestWithContent var newPackageXml = XElement.Parse(ImportResources.Dictionary_Package); var dictionaryItemsElement = newPackageXml.Elements("DictionaryItems").First(); - AddLanguages(); - AddExistingEnglishAndNorwegianParentDictionaryItem(expectedEnglishParentValue, expectedNorwegianParentValue); + await AddLanguages(); + await AddExistingEnglishAndNorwegianParentDictionaryItem(expectedEnglishParentValue, expectedNorwegianParentValue); // Act PackageDataInstallation.ImportDictionaryItems(dictionaryItemsElement.Elements("DictionaryItem"), 0); // Assert - AssertDictionaryItem("Parent", expectedEnglishParentValue, "en-GB"); - AssertDictionaryItem("Parent", expectedNorwegianParentValue, "nb-NO"); - AssertDictionaryItem("Child", expectedEnglishChildValue, "en-GB"); - AssertDictionaryItem("Child", expectedNorwegianChildValue, "nb-NO"); + await AssertDictionaryItem("Parent", expectedEnglishParentValue, "en-GB"); + await AssertDictionaryItem("Parent", expectedNorwegianParentValue, "nb-NO"); + await AssertDictionaryItem("Child", expectedEnglishChildValue, "en-GB"); + await AssertDictionaryItem("Child", expectedNorwegianChildValue, "nb-NO"); } [Test] - public void WhenExistingDictionaryKey_OnlyAddsNewLanguages() + public async Task WhenExistingDictionaryKey_OnlyAddsNewLanguages() { // Arrange const string expectedEnglishParentValue = "ExistingParentValue"; @@ -646,21 +644,21 @@ public class PackageDataInstallationTests : UmbracoIntegrationTestWithContent var newPackageXml = XElement.Parse(ImportResources.Dictionary_Package); var dictionaryItemsElement = newPackageXml.Elements("DictionaryItems").First(); - AddLanguages(); - AddExistingEnglishParentDictionaryItem(expectedEnglishParentValue); + await AddLanguages(); + await AddExistingEnglishParentDictionaryItem(expectedEnglishParentValue); // Act PackageDataInstallation.ImportDictionaryItems(dictionaryItemsElement.Elements("DictionaryItem"), 0); // Assert - AssertDictionaryItem("Parent", expectedEnglishParentValue, "en-GB"); - AssertDictionaryItem("Parent", expectedNorwegianParentValue, "nb-NO"); - AssertDictionaryItem("Child", expectedEnglishChildValue, "en-GB"); - AssertDictionaryItem("Child", expectedNorwegianChildValue, "nb-NO"); + await AssertDictionaryItem("Parent", expectedEnglishParentValue, "en-GB"); + await AssertDictionaryItem("Parent", expectedNorwegianParentValue, "nb-NO"); + await AssertDictionaryItem("Child", expectedEnglishChildValue, "en-GB"); + await AssertDictionaryItem("Child", expectedNorwegianChildValue, "nb-NO"); } [Test] - public void Can_Import_Languages() + public async Task Can_Import_Languages() { // Arrange var newPackageXml = XElement.Parse(ImportResources.Dictionary_Package); @@ -668,7 +666,7 @@ public class PackageDataInstallationTests : UmbracoIntegrationTestWithContent // Act var languages = PackageDataInstallation.ImportLanguages(languageItemsElement.Elements("Language"), 0); - var allLanguages = LocalizationService.GetAllLanguages(); + var allLanguages = await LanguageService.GetAllAsync(); // Assert Assert.That(languages.Any(x => x.HasIdentity == false), Is.False); @@ -848,55 +846,63 @@ public class PackageDataInstallationTests : UmbracoIntegrationTestWithContent }); } - private void AddLanguages() + private async Task AddLanguages() { var norwegian = new Language("nb-NO", "Norwegian Bokmål (Norway)"); var english = new Language("en-GB", "English (United Kingdom)"); - LocalizationService.Save(norwegian, 0); - LocalizationService.Save(english, 0); + await LanguageService.CreateAsync(norwegian, 0); + await LanguageService.CreateAsync(english, 0); } - private void AssertDictionaryItem(string dictionaryItemName, string expectedValue, string cultureCode) + private async Task AssertDictionaryItem(string dictionaryItemName, string expectedValue, string cultureCode) { - Assert.That(LocalizationService.DictionaryItemExists(dictionaryItemName), "DictionaryItem key does not exist"); - var dictionaryItem = LocalizationService.GetDictionaryItemByKey(dictionaryItemName); + Assert.That(await DictionaryItemService.ExistsAsync(dictionaryItemName), "DictionaryItem key does not exist"); + var dictionaryItem = await DictionaryItemService.GetAsync(dictionaryItemName); var translation = dictionaryItem.Translations.SingleOrDefault(i => i.Language.IsoCode == cultureCode); Assert.IsNotNull(translation, "Translation to {0} was not added", cultureCode); var value = translation.Value; Assert.That(value, Is.EqualTo(expectedValue), "Translation value was not set"); } - private void AddExistingEnglishParentDictionaryItem(string expectedEnglishParentValue) + private async Task AddExistingEnglishParentDictionaryItem(string expectedEnglishParentValue) { - var languages = LocalizationService.GetAllLanguages().ToList(); + var languages = (await LanguageService.GetAllAsync()).ToList(); var englishLanguage = languages.Single(l => l.IsoCode == "en-GB"); - LocalizationService.Save( + + // This matches what is in the package.xml file + var key = new Guid("28f2e02a-8c66-4fcd-85e3-8524d551c0d3"); + var result = await DictionaryItemService.CreateAsync( new DictionaryItem("Parent") { - // This matches what is in the package.xml file - Key = new Guid("28f2e02a-8c66-4fcd-85e3-8524d551c0d3"), + Key = key, Translations = new List { new DictionaryTranslation(englishLanguage, expectedEnglishParentValue) } }); + Assert.IsTrue(result.Success); + Assert.AreEqual(key, result.Result.Key); } - private void AddExistingEnglishAndNorwegianParentDictionaryItem(string expectedEnglishParentValue, string expectedNorwegianParentValue) + private async Task AddExistingEnglishAndNorwegianParentDictionaryItem(string expectedEnglishParentValue, string expectedNorwegianParentValue) { - var languages = LocalizationService.GetAllLanguages().ToList(); + var languages = (await LanguageService.GetAllAsync()).ToList(); var englishLanguage = languages.Single(l => l.IsoCode == "en-GB"); var norwegianLanguage = languages.Single(l => l.IsoCode == "nb-NO"); - LocalizationService.Save( + + var key = new Guid("28f2e02a-8c66-4fcd-85e3-8524d551c0d3"); + var result = await DictionaryItemService.CreateAsync( new DictionaryItem("Parent") { // This matches what is in the package.xml file - Key = new Guid("28f2e02a-8c66-4fcd-85e3-8524d551c0d3"), + Key = key, Translations = new List { new DictionaryTranslation(englishLanguage, expectedEnglishParentValue), new DictionaryTranslation(norwegianLanguage, expectedNorwegianParentValue) } }); + Assert.IsTrue(result.Success); + Assert.AreEqual(key, result.Result.Key); } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DictionaryRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DictionaryRepositoryTest.cs index de1c0b33f0..e10a104100 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DictionaryRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DictionaryRepositoryTest.cs @@ -1,9 +1,6 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; @@ -18,15 +15,15 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos public class DictionaryRepositoryTest : UmbracoIntegrationTest { [SetUp] - public void SetUp() => CreateTestData(); + public async Task SetUp() => await CreateTestData(); private IDictionaryRepository CreateRepository() => GetRequiredService(); [Test] - public void Can_Perform_Get_By_Key_On_DictionaryRepository() + public async Task Can_Perform_Get_By_Key_On_DictionaryRepository() { // Arrange - var localizationService = GetRequiredService(); + var languageService = GetRequiredService(); var provider = ScopeProvider; using (provider.CreateScope()) { @@ -35,7 +32,7 @@ public class DictionaryRepositoryTest : UmbracoIntegrationTest { Translations = new List { - new DictionaryTranslation(localizationService.GetLanguageByIsoCode("en-US"), "Hello world") + new DictionaryTranslation(await languageService.GetAsync("en-US"), "Hello world") } }; @@ -54,10 +51,10 @@ public class DictionaryRepositoryTest : UmbracoIntegrationTest } [Test] - public void Can_Perform_Get_By_UniqueId_On_DictionaryRepository() + public async Task Can_Perform_Get_By_UniqueId_On_DictionaryRepository() { // Arrange - var localizationService = GetRequiredService(); + var languageService = GetRequiredService(); var provider = ScopeProvider; using (provider.CreateScope()) { @@ -66,7 +63,7 @@ public class DictionaryRepositoryTest : UmbracoIntegrationTest { Translations = new List { - new DictionaryTranslation(localizationService.GetLanguageByIsoCode("en-US"), "Hello world") + new DictionaryTranslation(await languageService.GetAsync("en-US"), "Hello world") } }; @@ -85,10 +82,10 @@ public class DictionaryRepositoryTest : UmbracoIntegrationTest } [Test] - public void Can_Perform_Get_On_DictionaryRepository() + public async Task Can_Perform_Get_On_DictionaryRepository() { // Arrange - var localizationService = GetRequiredService(); + var languageService = GetRequiredService(); var provider = ScopeProvider; using (provider.CreateScope()) { @@ -97,7 +94,7 @@ public class DictionaryRepositoryTest : UmbracoIntegrationTest { Translations = new List { - new DictionaryTranslation(localizationService.GetLanguageByIsoCode("en-US"), "Hello world") + new DictionaryTranslation(await languageService.GetAsync("en-US"), "Hello world") } }; @@ -310,17 +307,17 @@ public class DictionaryRepositoryTest : UmbracoIntegrationTest } [Test] - public void Can_Perform_Update_WithNewTranslation_On_DictionaryRepository() + public async Task Can_Perform_Update_WithNewTranslation_On_DictionaryRepository() { // Arrange - var localizationService = GetRequiredService(); + var languageService = GetRequiredService(); var provider = ScopeProvider; using (provider.CreateScope()) { var repository = CreateRepository(); var languageNo = new Language("nb-NO", "Norwegian Bokmål (Norway)"); - localizationService.Save(languageNo); + await languageService.CreateAsync(languageNo); // Act var item = repository.Get(1); @@ -396,22 +393,31 @@ public class DictionaryRepositoryTest : UmbracoIntegrationTest } } - public void CreateTestData() + public async Task CreateTestData() { - var localizationService = GetRequiredService(); - var language = localizationService.GetLanguageByIsoCode("en-US"); + var languageService = GetRequiredService(); + var dictionaryItemService = GetRequiredService(); + var language = await languageService.GetAsync("en-US"); var languageDK = new Language("da-DK", "Danish (Denmark)"); - localizationService.Save(languageDK); //Id 2 + await languageService.CreateAsync(languageDK); //Id 2 - localizationService.Create("Read More", null, new List - { - new DictionaryTranslation(language, "Read More"), new DictionaryTranslation(languageDK, "Læs mere") - }); // Id 1 + await dictionaryItemService.CreateAsync( + new DictionaryItem("Read More") + { + Translations = new List + { + new DictionaryTranslation(language, "Read More"), new DictionaryTranslation(languageDK, "Læs mere") + } + }); // Id 1 - localizationService.Create("Article", null, new List - { - new DictionaryTranslation(language, "Article"), new DictionaryTranslation(languageDK, "Artikel") - }); // Id 2 + await dictionaryItemService.CreateAsync( + new DictionaryItem("Article") + { + Translations = new List + { + new DictionaryTranslation(language, "Article"), new DictionaryTranslation(languageDK, "Artikel") + } + }); // Id 2 } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/LanguageRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/LanguageRepositoryTest.cs index 3d9b6c30e1..703829c351 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/LanguageRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/LanguageRepositoryTest.cs @@ -367,22 +367,22 @@ public class LanguageRepositoryTest : UmbracoIntegrationTest private LanguageRepository CreateRepository(IScopeProvider provider) => new((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger()); - private void CreateTestData() + private async Task CreateTestData() { - var localizationService = GetRequiredService(); + var languageService = GetRequiredService(); //Id 1 is en-US - when Umbraco is installed var languageDK = new Language("da-DK", "Danish (Denmark)"); - localizationService.Save(languageDK); //Id 2 + await languageService.CreateAsync(languageDK); //Id 2 var languageSE = new Language("sv-SE", "Swedish (Sweden)"); - localizationService.Save(languageSE); //Id 3 + await languageService.CreateAsync(languageSE); //Id 3 var languageDE = new Language("de-DE", "German (Germany)"); - localizationService.Save(languageDE); //Id 4 + await languageService.CreateAsync(languageDE); //Id 4 var languagePT = new Language("pt-PT", "Portuguese (Portugal)"); - localizationService.Save(languagePT); //Id 5 + await languageService.CreateAsync(languagePT); //Id 5 } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs index b0d3dfcd1a..ce17a40685 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs @@ -28,7 +28,9 @@ public class ScopedRepositoryTests : UmbracoIntegrationTest { private IUserService UserService => GetRequiredService(); - private ILocalizationService LocalizationService => GetRequiredService(); + private ILanguageService LanguageService => GetRequiredService(); + + private IDictionaryItemService DictionaryItemService => GetRequiredService(); protected override void ConfigureTestServices(IServiceCollection services) { @@ -138,19 +140,19 @@ public class ScopedRepositoryTests : UmbracoIntegrationTest [TestCase(true)] [TestCase(false)] - public void FullDataSetRepositoryCachePolicy(bool complete) + public async Task FullDataSetRepositoryCachePolicy(bool complete) { var scopeProvider = (ScopeProvider)ScopeProvider; - var service = LocalizationService; + var service = LanguageService; var globalCache = AppCaches.IsolatedCaches.GetOrCreate(typeof(ILanguage)); ILanguage lang = new Language("fr-FR", "French (France)"); - service.Save(lang); + await service.CreateAsync(lang); // global cache has been flushed, reload var globalFullCached = (IEnumerable)globalCache.Get(GetCacheTypeKey(), () => null); Assert.IsNull(globalFullCached); - var reload = service.GetLanguageById(lang.Id); + var reload = await service.GetAsync(lang.IsoCode); // global cache contains the entity globalFullCached = (IEnumerable)globalCache.Get(GetCacheTypeKey(), () => null); @@ -173,12 +175,12 @@ public class ScopedRepositoryTests : UmbracoIntegrationTest // Use IsMandatory of isocode to ensure publishedContent cache is not also rebuild lang.IsMandatory = true; - service.Save(lang); + await service.UpdateAsync(lang); // scoped cache has been flushed, reload var scopeFullCached = (IEnumerable)scopedCache.Get(GetCacheTypeKey(), () => null); Assert.IsNull(scopeFullCached); - reload = service.GetLanguageById(lang.Id); + reload = await service.GetAsync(lang.IsoCode); // scoped cache contains the "new" entity scopeFullCached = (IEnumerable)scopedCache.Get(GetCacheTypeKey(), () => null); @@ -217,7 +219,7 @@ public class ScopedRepositoryTests : UmbracoIntegrationTest } // get again, updated if completed - lang = service.GetLanguageById(lang.Id); + lang = await service.GetAsync(lang.IsoCode); Assert.AreEqual(complete, lang.IsMandatory); // global cache contains the entity again @@ -231,28 +233,31 @@ public class ScopedRepositoryTests : UmbracoIntegrationTest [TestCase(true)] [TestCase(false)] - public void SingleItemsOnlyRepositoryCachePolicy(bool complete) + public async Task SingleItemsOnlyRepositoryCachePolicy(bool complete) { var scopeProvider = (ScopeProvider)ScopeProvider; - var service = LocalizationService; + var languageService = LanguageService; + var dictionaryItemService = DictionaryItemService; var globalCache = AppCaches.IsolatedCaches.GetOrCreate(typeof(IDictionaryItem)); var lang = new Language("fr-FR", "French (France)"); - service.Save(lang); + await languageService.CreateAsync(lang); - var item = service.Create( - "item-key", - null, - new IDictionaryTranslation[] { new DictionaryTranslation(lang.Id, "item-value") }).Result!; + var item = (await dictionaryItemService.CreateAsync( + new DictionaryItem("item-key") + { + Translations = new IDictionaryTranslation[] { new DictionaryTranslation(lang.Id, "item-value") } + })).Result; - // Refresh the cache manually because we can't unbind - service.GetDictionaryItemById(item.Id); - service.GetLanguageById(lang.Id); + // Refresh the keyed cache manually + await dictionaryItemService.GetAsync(item.Key); + await languageService.GetAsync(lang.IsoCode); // global cache contains the entity - var globalCached = (IDictionaryItem)globalCache.Get(GetCacheIdKey(item.Id), () => null); + var globalCached = (IDictionaryItem)globalCache.Get(GetCacheIdKey(item.Key), () => null); Assert.IsNotNull(globalCached); Assert.AreEqual(item.Id, globalCached.Id); + Assert.AreEqual(item.Key, globalCached.Key); Assert.AreEqual("item-key", globalCached.ItemKey); Assert.IsNull(scopeProvider.AmbientScope); @@ -267,16 +272,18 @@ public class ScopedRepositoryTests : UmbracoIntegrationTest Assert.AreNotSame(globalCache, scopedCache); item.ItemKey = "item-changed"; - service.Update(item); + await dictionaryItemService.UpdateAsync(item); // scoped cache contains the "new" entity - var scopeCached = (IDictionaryItem)scopedCache.Get(GetCacheIdKey(item.Id), () => null); + // Refresh the keyed cache manually + await dictionaryItemService.GetAsync(item.Key); + var scopeCached = (IDictionaryItem)scopedCache.Get(GetCacheIdKey(item.Key), () => null); Assert.IsNotNull(scopeCached); Assert.AreEqual(item.Id, scopeCached.Id); Assert.AreEqual("item-changed", scopeCached.ItemKey); // global cache is unchanged - globalCached = (IDictionaryItem)globalCache.Get(GetCacheIdKey(item.Id), () => null); + globalCached = (IDictionaryItem)globalCache.Get(GetCacheIdKey(item.Key), () => null); Assert.IsNotNull(globalCached); Assert.AreEqual(item.Id, globalCached.Id); Assert.AreEqual("item-key", globalCached.ItemKey); @@ -289,7 +296,7 @@ public class ScopedRepositoryTests : UmbracoIntegrationTest Assert.IsNull(scopeProvider.AmbientScope); - globalCached = (IDictionaryItem)globalCache.Get(GetCacheIdKey(item.Id), () => null); + globalCached = (IDictionaryItem)globalCache.Get(GetCacheIdKey(item.Key), () => null); if (complete) { // global cache has been cleared @@ -302,11 +309,11 @@ public class ScopedRepositoryTests : UmbracoIntegrationTest } // get again, updated if completed - item = service.GetDictionaryItemById(item.Id); + item = await dictionaryItemService.GetAsync(item.Key); Assert.AreEqual(complete ? "item-changed" : "item-key", item.ItemKey); // global cache contains the entity again - globalCached = (IDictionaryItem)globalCache.Get(GetCacheIdKey(item.Id), () => null); + globalCached = (IDictionaryItem)globalCache.Get(GetCacheIdKey(item.Key), () => null); Assert.IsNotNull(globalCached); Assert.AreEqual(item.Id, globalCached.Id); Assert.AreEqual(complete ? "item-changed" : "item-key", globalCached.ItemKey); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceNotificationTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceNotificationTests.cs index 586eeb0873..1e8e09c071 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceNotificationTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceNotificationTests.cs @@ -1,11 +1,8 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Linq; using NUnit.Framework; using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; @@ -14,7 +11,6 @@ using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; -using Umbraco.Extensions; namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; @@ -42,7 +38,7 @@ public class ContentServiceNotificationTests : UmbracoIntegrationTest private ContentService ContentService => (ContentService)GetRequiredService(); - private ILocalizationService LocalizationService => GetRequiredService(); + private ILanguageService LanguageService => GetRequiredService(); private IFileService FileService => GetRequiredService(); @@ -67,9 +63,9 @@ public class ContentServiceNotificationTests : UmbracoIntegrationTest } [Test] - public void Saving_Culture() + public async Task Saving_Culture() { - LocalizationService.Save(new Language("fr-FR", "French (France)")); + await LanguageService.CreateAsync(new Language("fr-FR", "French (France)")); _contentType.Variations = ContentVariation.Culture; foreach (var propertyType in _contentType.PropertyTypes) @@ -178,9 +174,9 @@ public class ContentServiceNotificationTests : UmbracoIntegrationTest } [Test] - public void Publishing_Culture() + public async Task Publishing_Culture() { - LocalizationService.Save(new Language("fr-FR", "French (France)")); + await LanguageService.CreateAsync(new Language("fr-FR", "French (France)")); _contentType.Variations = ContentVariation.Culture; foreach (var propertyType in _contentType.PropertyTypes) @@ -338,9 +334,9 @@ public class ContentServiceNotificationTests : UmbracoIntegrationTest } [Test] - public void Unpublishing_Culture() + public async Task Unpublishing_Culture() { - LocalizationService.Save(new Language("fr-FR", "French (France)")); + await LanguageService.CreateAsync(new Language("fr-FR", "French (France)")); _contentType.Variations = ContentVariation.Culture; foreach (var propertyType in _contentType.PropertyTypes) diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServicePublishBranchTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServicePublishBranchTests.cs index e5ef5789db..089666248f 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServicePublishBranchTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServicePublishBranchTests.cs @@ -22,7 +22,7 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest { private IContentService ContentService => GetRequiredService(); - private ILocalizationService LocalizationService => GetRequiredService(); + private ILanguageService LanguageService => GetRequiredService(); private IContentTypeService ContentTypeService => GetRequiredService(); @@ -443,11 +443,11 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest private void CreateTypes(out IContentType iContentType, out IContentType vContentType) { var langDe = new Language("de", "German") { IsDefault = true }; - LocalizationService.Save(langDe); + LanguageService.CreateAsync(langDe).GetAwaiter().GetResult(); var langRu = new Language("ru", "Russian"); - LocalizationService.Save(langRu); + LanguageService.CreateAsync(langRu).GetAwaiter().GetResult(); var langEs = new Language("es", "Spanish"); - LocalizationService.Save(langEs); + LanguageService.CreateAsync(langEs).GetAwaiter().GetResult(); iContentType = new ContentType(ShortStringHelper, -1) { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTagsTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTagsTests.cs index 88da06b009..550fedcb41 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTagsTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTagsTests.cs @@ -39,7 +39,7 @@ public class ContentServiceTagsTests : UmbracoIntegrationTest private IDataTypeService DataTypeService => GetRequiredService(); - private ILocalizationService LocalizationService => GetRequiredService(); + private ILanguageService LanguageService => GetRequiredService(); private IFileService FileService => GetRequiredService(); @@ -89,13 +89,12 @@ public class ContentServiceTagsTests : UmbracoIntegrationTest } [Test] - public void TagsCanBeVariant() + public async Task TagsCanBeVariant() { - var languageService = LocalizationService; - var language = new LanguageBuilder() + var language = new LanguageBuilder() .WithCultureInfo("fr-FR") .Build(); - LocalizationService.Save(language); // en-US is already there + await LanguageService.CreateAsync(language); // en-US is already there var template = TemplateBuilder.CreateTextPageTemplate(); FileService.SaveTemplate(template); @@ -148,9 +147,9 @@ public class ContentServiceTagsTests : UmbracoIntegrationTest } [Test] - public void TagsCanBecomeVariant() + public async Task TagsCanBecomeVariant() { - var enId = LocalizationService.GetLanguageIdByIsoCode("en-US").Value; + var enId = (await LanguageService.GetAsync("en-US"))!.Id; var template = TemplateBuilder.CreateTextPageTemplate(); FileService.SaveTemplate(template); @@ -224,14 +223,12 @@ public class ContentServiceTagsTests : UmbracoIntegrationTest } [Test] - public void TagsCanBecomeInvariant() + public async Task TagsCanBecomeInvariant() { var language = new LanguageBuilder() .WithCultureInfo("fr-FR") .Build(); - LocalizationService.Save(language); // en-US is already there - - var enId = LocalizationService.GetLanguageIdByIsoCode("en-US").Value; + await LanguageService.CreateAsync(language); // en-US is already there var template = TemplateBuilder.CreateTextPageTemplate(); FileService.SaveTemplate(template); @@ -285,14 +282,12 @@ public class ContentServiceTagsTests : UmbracoIntegrationTest } [Test] - public void TagsCanBecomeInvariant2() + public async Task TagsCanBecomeInvariant2() { var language = new LanguageBuilder() .WithCultureInfo("fr-FR") .Build(); - LocalizationService.Save(language); // en-US is already there - - var enId = LocalizationService.GetLanguageIdByIsoCode("en-US").Value; + await LanguageService.CreateAsync(language); // en-US is already there var template = TemplateBuilder.CreateTextPageTemplate(); FileService.SaveTemplate(template); @@ -332,14 +327,12 @@ public class ContentServiceTagsTests : UmbracoIntegrationTest } [Test] - public void TagsCanBecomeInvariantByPropertyType() + public async Task TagsCanBecomeInvariantByPropertyType() { var language = new LanguageBuilder() .WithCultureInfo("fr-FR") .Build(); - LocalizationService.Save(language); // en-US is already there - - var enId = LocalizationService.GetLanguageIdByIsoCode("en-US").Value; + await LanguageService.CreateAsync(language); // en-US is already there var template = TemplateBuilder.CreateTextPageTemplate(); FileService.SaveTemplate(template); @@ -393,7 +386,7 @@ public class ContentServiceTagsTests : UmbracoIntegrationTest } [Test] - public void TagsCanBecomeInvariantByPropertyTypeAndBackToVariant() + public async Task TagsCanBecomeInvariantByPropertyTypeAndBackToVariant() { var frValue = new string[] { "hello", "world", "some", "tags", "plus" }; var enValue = new string[] { "hello", "world", "another", "one" }; @@ -401,7 +394,7 @@ public class ContentServiceTagsTests : UmbracoIntegrationTest var language = new LanguageBuilder() .WithCultureInfo("fr-FR") .Build(); - LocalizationService.Save(language); // en-US is already there + await LanguageService.CreateAsync(language); // en-US is already there var template = TemplateBuilder.CreateTextPageTemplate(); FileService.SaveTemplate(template); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs index 0623f89170..9d1242d5c7 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs @@ -50,7 +50,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent private IDataTypeService DataTypeService => GetRequiredService(); - private ILocalizationService LocalizationService => GetRequiredService(); + private ILanguageService LanguageService => GetRequiredService(); private IAuditService AuditService => GetRequiredService(); @@ -190,7 +190,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent } [Test] - public void Perform_Scheduled_Publishing() + public async Task Perform_Scheduled_Publishing() { var langUk = new LanguageBuilder() .WithCultureInfo("en-GB") @@ -200,8 +200,8 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent .WithCultureInfo("fr-FR") .Build(); - LocalizationService.Save(langFr); - LocalizationService.Save(langUk); + await LanguageService.CreateAsync(langFr); + await LanguageService.CreateAsync(langUk); var ctInvariant = ContentTypeBuilder.CreateBasicContentType("invariantPage"); ContentTypeService.Save(ctInvariant); @@ -837,7 +837,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent } [Test] - public void Unpublishing_Mandatory_Language_Unpublishes_Document() + public async Task Unpublishing_Mandatory_Language_Unpublishes_Document() { var langUk = new LanguageBuilder() .WithCultureInfo("en-GB") @@ -848,8 +848,8 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent .WithCultureInfo("fr-FR") .Build(); - LocalizationService.Save(langFr); - LocalizationService.Save(langUk); + await LanguageService.CreateAsync(langFr); + await LanguageService.CreateAsync(langUk); var contentType = ContentTypeBuilder.CreateBasicContentType(); contentType.Variations = ContentVariation.Culture; @@ -941,7 +941,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent } [Test] - public void Pending_Invariant_Property_Changes_Affect_Default_Language_Edited_State() + public async Task Pending_Invariant_Property_Changes_Affect_Default_Language_Edited_State() { // Arrange var langGb = new LanguageBuilder() @@ -952,8 +952,8 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent .WithCultureInfo("fr-FR") .Build(); - LocalizationService.Save(langFr); - LocalizationService.Save(langGb); + await LanguageService.CreateAsync(langFr); + await LanguageService.CreateAsync(langGb); var contentType = ContentTypeBuilder.CreateMetaContentType(); contentType.Variations = ContentVariation.Culture; @@ -1018,7 +1018,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent } [Test] - public void Can_Unpublish_Content_Variation_And_Detect_Changed_Cultures() + public async Task Can_Unpublish_Content_Variation_And_Detect_Changed_Cultures() { // Arrange var langGb = new LanguageBuilder() @@ -1030,8 +1030,8 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent .WithCultureInfo("fr-FR") .Build(); - LocalizationService.Save(langFr); - LocalizationService.Save(langGb); + await LanguageService.CreateAsync(langFr); + await LanguageService.CreateAsync(langGb); var contentType = ContentTypeBuilder.CreateBasicContentType(); contentType.Variations = ContentVariation.Culture; @@ -1211,7 +1211,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent } [Test] - public void Can_Publish_And_Unpublish_Cultures_In_Single_Operation() + public async Task Can_Publish_And_Unpublish_Cultures_In_Single_Operation() { // TODO: This is using an internal API - we aren't exposing this publicly (at least for now) but we'll keep the test around var langFr = new LanguageBuilder() @@ -1220,8 +1220,8 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent var langDa = new LanguageBuilder() .WithCultureInfo("da") .Build(); - LocalizationService.Save(langFr); - LocalizationService.Save(langDa); + await LanguageService.CreateAsync(langFr); + await LanguageService.CreateAsync(langDa); var ct = ContentTypeBuilder.CreateBasicContentType(); ct.Variations = ContentVariation.Culture; @@ -2317,7 +2317,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent } [Test] - public void Can_Rollback_Version_On_Multilingual() + public async Task Can_Rollback_Version_On_Multilingual() { var langFr = new LanguageBuilder() .WithCultureInfo("fr") @@ -2325,8 +2325,8 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent var langDa = new LanguageBuilder() .WithCultureInfo("da") .Build(); - LocalizationService.Save(langFr); - LocalizationService.Save(langDa); + await LanguageService.CreateAsync(langFr); + await LanguageService.CreateAsync(langDa); var template = TemplateBuilder.CreateTextPageTemplate(); FileService.SaveTemplate(template); @@ -2838,9 +2838,9 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent } [Test] - public void Ensure_Invariant_Name() + public async Task Ensure_Invariant_Name() { - var languageService = LocalizationService; + var languageService = LanguageService; var langUk = new LanguageBuilder() .WithCultureInfo("en-GB") @@ -2850,8 +2850,8 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent .WithCultureInfo("fr-FR") .Build(); - languageService.Save(langFr); - languageService.Save(langUk); + await languageService.CreateAsync(langFr); + await languageService.CreateAsync(langUk); var contentTypeService = ContentTypeService; @@ -2879,9 +2879,9 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent } [Test] - public void Ensure_Unique_Culture_Names() + public async Task Ensure_Unique_Culture_Names() { - var languageService = LocalizationService; + var languageService = LanguageService; var langUk = new LanguageBuilder() .WithCultureInfo("en-GB") @@ -2891,8 +2891,8 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent .WithCultureInfo("fr-FR") .Build(); - languageService.Save(langFr); - languageService.Save(langUk); + await languageService.CreateAsync(langFr); + await languageService.CreateAsync(langUk); var contentTypeService = ContentTypeService; @@ -2921,9 +2921,9 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent } [Test] - public void Can_Get_Paged_Children_WithFilterAndOrder() + public async Task Can_Get_Paged_Children_WithFilterAndOrder() { - var languageService = LocalizationService; + var languageService = LanguageService; var langUk = new LanguageBuilder() .WithCultureInfo("en-GB") @@ -2937,9 +2937,9 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent .WithCultureInfo("da-DK") .Build(); - languageService.Save(langFr); - languageService.Save(langUk); - languageService.Save(langDa); + await languageService.CreateAsync(langFr); + await languageService.CreateAsync(langUk); + await languageService.CreateAsync(langDa); var contentTypeService = ContentTypeService; @@ -3054,9 +3054,9 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent } [Test] - public void Can_SaveRead_Variations() + public async Task Can_SaveRead_Variations() { - var languageService = LocalizationService; + var languageService = LanguageService; var langPt = new LanguageBuilder() .WithCultureInfo("pt-PT") .WithIsDefault(true) @@ -3071,9 +3071,9 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent .WithCultureInfo("de-DE") .Build(); - languageService.Save(langFr); - languageService.Save(langUk); - languageService.Save(langDe); + await languageService.CreateAsync(langFr); + await languageService.CreateAsync(langUk); + await languageService.CreateAsync(langDe); var contentTypeService = ContentTypeService; @@ -3486,8 +3486,8 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent langFr = (Language)new LanguageBuilder() .WithCultureInfo("fr-FR") .Build(); - LocalizationService.Save(langFr); - LocalizationService.Save(langUk); + LanguageService.CreateAsync(langFr).GetAwaiter().GetResult(); + LanguageService.CreateAsync(langUk).GetAwaiter().GetResult(); contentType = ContentTypeBuilder.CreateBasicContentType(); contentType.Variations = ContentVariation.Culture; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs index d4b9279487..a7f1220ba9 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs @@ -1,12 +1,9 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Linq; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; @@ -16,7 +13,6 @@ using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping; -using Umbraco.Extensions; namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; @@ -32,7 +28,7 @@ public class ContentTypeServiceVariantsTests : UmbracoIntegrationTest private IRedirectUrlService RedirectUrlService => GetRequiredService(); - private ILocalizationService LocalizationService => GetRequiredService(); + private ILanguageService LanguageService => GetRequiredService(); protected override void CustomTestSetup(IUmbracoBuilder builder) @@ -250,7 +246,7 @@ public class ContentTypeServiceVariantsTests : UmbracoIntegrationTest [TestCase(ContentVariation.CultureAndSegment, ContentVariation.Culture)] [TestCase(ContentVariation.CultureAndSegment, ContentVariation.Segment)] [TestCase(ContentVariation.CultureAndSegment, ContentVariation.CultureAndSegment)] - public void Preserve_Content_Name_After_Content_Type_Variation_Change(ContentVariation contentTypeVariationFrom, + public async Task Preserve_Content_Name_After_Content_Type_Variation_Change(ContentVariation contentTypeVariationFrom, ContentVariation contentTypeVariationTo) { var contentType = ContentTypeBuilder.CreateBasicContentType(); @@ -265,7 +261,7 @@ public class ContentTypeServiceVariantsTests : UmbracoIntegrationTest var nlContentName = "Content nl-NL"; var nlCulture = "nl-NL"; - LocalizationService.Save(new Language(nlCulture, "Dutch (Netherlands)")); + await LanguageService.CreateAsync(new Language(nlCulture, "Dutch (Netherlands)")); var includeCultureNames = contentType.Variations.HasFlag(ContentVariation.Culture); @@ -534,11 +530,11 @@ public class ContentTypeServiceVariantsTests : UmbracoIntegrationTest } [Test] - public void Change_Variations_SimpleContentType_VariantToInvariantAndBack() + public async Task Change_Variations_SimpleContentType_VariantToInvariantAndBack() { // one simple content type, variant, with both variant and invariant properties // can change it to invariant and back - CreateFrenchAndEnglishLangs(); + await CreateFrenchAndEnglishLangs(); var contentType = CreateContentType(ContentVariation.Culture); @@ -625,7 +621,7 @@ public class ContentTypeServiceVariantsTests : UmbracoIntegrationTest } [Test] - public void Change_Variations_SimpleContentType_InvariantToVariantAndBack() + public async Task Change_Variations_SimpleContentType_InvariantToVariantAndBack() { // one simple content type, invariant // can change it to variant and back @@ -633,9 +629,9 @@ public class ContentTypeServiceVariantsTests : UmbracoIntegrationTest var globalSettings = new GlobalSettings(); var languageEn = new Language("en", "English") { IsDefault = true }; - LocalizationService.Save(languageEn); + await LanguageService.CreateAsync(languageEn); var languageFr = new Language("fr", "French"); - LocalizationService.Save(languageFr); + await LanguageService.CreateAsync(languageFr); var contentType = CreateContentType(ContentVariation.Nothing); @@ -719,11 +715,11 @@ public class ContentTypeServiceVariantsTests : UmbracoIntegrationTest } [Test] - public void Change_Variations_SimpleContentType_VariantPropertyToInvariantAndBack() + public async Task Change_Variations_SimpleContentType_VariantPropertyToInvariantAndBack() { // one simple content type, variant, with both variant and invariant properties // can change an invariant property to variant and back - CreateFrenchAndEnglishLangs(); + await CreateFrenchAndEnglishLangs(); var contentType = CreateContentType(ContentVariation.Culture); @@ -814,12 +810,12 @@ public class ContentTypeServiceVariantsTests : UmbracoIntegrationTest [TestCase(ContentVariation.Culture, ContentVariation.Segment)] [TestCase(ContentVariation.CultureAndSegment, ContentVariation.Nothing)] [TestCase(ContentVariation.CultureAndSegment, ContentVariation.Segment)] - public void Change_Property_Variations_From_Variant_To_Invariant_And_Ensure_Edited_Values_Are_Renormalized( + public async Task Change_Property_Variations_From_Variant_To_Invariant_And_Ensure_Edited_Values_Are_Renormalized( ContentVariation variant, ContentVariation invariant) { // one simple content type, variant, with both variant and invariant properties // can change an invariant property to variant and back - CreateFrenchAndEnglishLangs(); + await CreateFrenchAndEnglishLangs(); var contentType = CreateContentType(ContentVariation.Culture | ContentVariation.Segment); @@ -938,12 +934,12 @@ public class ContentTypeServiceVariantsTests : UmbracoIntegrationTest [TestCase(ContentVariation.Nothing, ContentVariation.CultureAndSegment)] [TestCase(ContentVariation.Segment, ContentVariation.Culture)] [TestCase(ContentVariation.Segment, ContentVariation.CultureAndSegment)] - public void Change_Property_Variations_From_Invariant_To_Variant_And_Ensure_Edited_Values_Are_Renormalized( + public async Task Change_Property_Variations_From_Invariant_To_Variant_And_Ensure_Edited_Values_Are_Renormalized( ContentVariation invariant, ContentVariation variant) { // one simple content type, variant, with both variant and invariant properties // can change an invariant property to variant and back - CreateFrenchAndEnglishLangs(); + await CreateFrenchAndEnglishLangs(); var contentType = CreateContentType(ContentVariation.Culture | ContentVariation.Segment); @@ -1023,13 +1019,13 @@ public class ContentTypeServiceVariantsTests : UmbracoIntegrationTest } [Test] - public void Change_Variations_ComposedContentType_1() + public async Task Change_Variations_ComposedContentType_1() { // one composing content type, variant, with both variant and invariant properties // one composed content type, variant, with both variant and invariant properties // can change the composing content type to invariant and back // can change the composed content type to invariant and back - CreateFrenchAndEnglishLangs(); + await CreateFrenchAndEnglishLangs(); var composing = CreateContentType(ContentVariation.Culture, "composing"); @@ -1123,14 +1119,14 @@ public class ContentTypeServiceVariantsTests : UmbracoIntegrationTest } [Test] - public void Change_Variations_ComposedContentType_2() + public async Task Change_Variations_ComposedContentType_2() { // one composing content type, variant, with both variant and invariant properties // one composed content type, variant, with both variant and invariant properties // one composed content type, invariant // can change the composing content type to invariant and back // can change the variant composed content type to invariant and back - CreateFrenchAndEnglishLangs(); + await CreateFrenchAndEnglishLangs(); var composing = CreateContentType(ContentVariation.Culture, "composing"); @@ -1276,12 +1272,12 @@ public class ContentTypeServiceVariantsTests : UmbracoIntegrationTest "{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':"); } - private void CreateFrenchAndEnglishLangs() + private async Task CreateFrenchAndEnglishLangs() { var languageEn = new Language("en", "English") { IsDefault = true }; - LocalizationService.Save(languageEn); + await LanguageService.CreateAsync(languageEn); var languageFr = new Language("fr", "French"); - LocalizationService.Save(languageFr); + await LanguageService.CreateAsync(languageFr); } private IContentType CreateContentType(ContentVariation variance, string alias = "contentType") => diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DictionaryItemServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DictionaryItemServiceTests.cs new file mode 100644 index 0000000000..0af6220eb7 --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DictionaryItemServiceTests.cs @@ -0,0 +1,498 @@ +using System.Diagnostics; +using NUnit.Framework; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Testing; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; + +[TestFixture] +[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] +public class DictionaryItemServiceTests : UmbracoIntegrationTest +{ + private Guid _parentItemId; + private Guid _childItemId; + + private IDictionaryItemService DictionaryItemService => GetRequiredService(); + + private ILanguageService LanguageService => GetRequiredService(); + + [SetUp] + public async Task SetUp() => await CreateTestData(); + + [Test] + public async Task Can_Get_Root_Dictionary_Items() + { + var rootItems = await DictionaryItemService.GetAtRootAsync(); + + Assert.NotNull(rootItems); + Assert.IsTrue(rootItems.Any()); + } + + [Test] + public async Task Can_Determine_If_DictionaryItem_Exists() + { + var exists = await DictionaryItemService.ExistsAsync("Parent"); + Assert.IsTrue(exists); + } + + [Test] + public async Task Can_Get_Dictionary_Item_By_Id() + { + var parentItem = await DictionaryItemService.GetAsync(_parentItemId); + Assert.NotNull(parentItem); + + var childItem = await DictionaryItemService.GetAsync(_childItemId); + Assert.NotNull(childItem); + } + + [Test] + public async Task Can_Get_Dictionary_Items_By_Ids() + { + var items = await DictionaryItemService.GetManyAsync(_parentItemId, _childItemId); + Assert.AreEqual(2, items.Count()); + Assert.NotNull(items.FirstOrDefault(i => i.Key == _parentItemId)); + Assert.NotNull(items.FirstOrDefault(i => i.Key == _childItemId)); + } + + [Test] + public async Task Can_Get_Dictionary_Item_By_Key() + { + var parentItem = await DictionaryItemService.GetAsync("Parent"); + Assert.NotNull(parentItem); + + var childItem = await DictionaryItemService.GetAsync("Child"); + Assert.NotNull(childItem); + } + + [Test] + public async Task Can_Get_Dictionary_Items_By_Keys() + { + var items = await DictionaryItemService.GetManyAsync("Parent", "Child"); + Assert.AreEqual(2, items.Count()); + Assert.NotNull(items.FirstOrDefault(i => i.ItemKey == "Parent")); + Assert.NotNull(items.FirstOrDefault(i => i.ItemKey == "Child")); + } + + [Test] + public async Task Does_Not_Fail_When_DictionaryItem_Doesnt_Exist() + { + var item = await DictionaryItemService.GetAsync("RandomKey"); + Assert.Null(item); + } + + [Test] + public async Task Can_Get_Dictionary_Item_Children() + { + var item = await DictionaryItemService.GetChildrenAsync(_parentItemId); + Assert.NotNull(item); + Assert.That(item.Count(), Is.EqualTo(1)); + + foreach (var dictionaryItem in item) + { + Assert.AreEqual(_parentItemId, dictionaryItem.ParentId); + Assert.IsFalse(string.IsNullOrEmpty(dictionaryItem.ItemKey)); + } + } + + [Test] + public async Task Can_Get_Dictionary_Item_Descendants() + { + using (var scope = ScopeProvider.CreateScope()) + { + var en = await LanguageService.GetAsync("en-GB"); + var dk = await LanguageService.GetAsync("da-DK"); + + var currParentId = _childItemId; + for (var i = 0; i < 25; i++) + { + // Create 2 per level + var result = await DictionaryItemService.CreateAsync( + new DictionaryItem(currParentId, "D1" + i) + { + Translations = new List + { + new DictionaryTranslation(en, "ChildValue1 " + i), + new DictionaryTranslation(dk, "BørnVærdi1 " + i) + } + }); + + Assert.IsTrue(result.Success); + + await DictionaryItemService.CreateAsync( + new DictionaryItem(currParentId, "D2" + i) + { + Translations = new List + { + new DictionaryTranslation(en, "ChildValue2 " + i), + new DictionaryTranslation(dk, "BørnVærdi2 " + i) + } + }); + + currParentId = result.Result!.Key; + } + + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlTrace = true; + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlCount = true; + + var items = (await DictionaryItemService.GetDescendantsAsync(_parentItemId)).ToArray(); + + Debug.WriteLine("SQL CALLS: " + ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().SqlCount); + + Assert.AreEqual(51, items.Length); + + // There's a call or two to get languages, so apart from that there should only be one call per level. + Assert.Less(ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().SqlCount, 30); + } + } + + [Test] + public async Task Can_Create_DictionaryItem_At_Root() + { + var english = await LanguageService.GetAsync("en-US"); + + var result = await DictionaryItemService.CreateAsync( + new DictionaryItem("Testing123") + { + Translations = new List { new DictionaryTranslation(english, "Hello world") } + }); + Assert.True(result.Success); + + // re-get + var item = await DictionaryItemService.GetAsync(result.Result!.Key); + Assert.NotNull(item); + + Assert.Greater(item.Id, 0); + Assert.IsTrue(item.HasIdentity); + Assert.IsFalse(item.ParentId.HasValue); + Assert.AreEqual("Testing123", item.ItemKey); + Assert.AreEqual(1, item.Translations.Count()); + } + + [Test] + public async Task Can_Create_DictionaryItem_At_Root_With_All_Languages() + { + var allLangs = (await LanguageService.GetAllAsync()).ToArray(); + Assert.Greater(allLangs.Length, 0); + + var translations = allLangs.Select(language => new DictionaryTranslation(language, $"Translation for: {language.IsoCode}")).ToArray(); + var result = await DictionaryItemService.CreateAsync( + new DictionaryItem("Testing12345") { Translations = translations } + ); + + Assert.IsTrue(result.Success); + Assert.AreEqual(DictionaryItemOperationStatus.Success, result.Status); + Assert.NotNull(result.Result); + + // re-get + var item = await DictionaryItemService.GetAsync(result.Result!.Key); + + Assert.IsNotNull(item); + Assert.Greater(item.Id, 0); + Assert.IsTrue(item.HasIdentity); + Assert.IsFalse(item.ParentId.HasValue); + Assert.AreEqual("Testing12345", item.ItemKey); + foreach (var language in allLangs) + { + Assert.AreEqual($"Translation for: {language.IsoCode}", + item.Translations.Single(x => x.Language.CultureName == language.CultureName).Value); + } + } + + [Test] + public async Task Can_Create_DictionaryItem_At_Root_With_Some_Languages() + { + var allLangs = (await LanguageService.GetAllAsync()).ToArray(); + Assert.Greater(allLangs.Length, 1); + + var firstLanguage = allLangs.First(); + var translations = new[] { new DictionaryTranslation(firstLanguage, $"Translation for: {firstLanguage.IsoCode}") }; + var result = await DictionaryItemService.CreateAsync( + new DictionaryItem("Testing12345") { Translations = translations } + ); + + Assert.IsTrue(result.Success); + Assert.AreEqual(DictionaryItemOperationStatus.Success, result.Status); + Assert.NotNull(result.Result); + + // re-get + var item = await DictionaryItemService.GetAsync(result.Result!.Key); + + Assert.IsNotNull(item); + Assert.Greater(item.Id, 0); + Assert.IsTrue(item.HasIdentity); + Assert.IsFalse(item.ParentId.HasValue); + Assert.AreEqual("Testing12345", item.ItemKey); + Assert.AreEqual(1, item.Translations.Count()); + Assert.AreEqual(firstLanguage.Id, item.Translations.First().LanguageId); + } + + [Test] + public async Task Can_Create_DictionaryItem_With_Explicit_Key() + { + var english = await LanguageService.GetAsync("en-US"); + // the package install needs to be able to create dictionary items with explicit keys + var key = Guid.NewGuid(); + + var result = await DictionaryItemService.CreateAsync( + new DictionaryItem("Testing123") + { + Key = key, + Translations = new List { new DictionaryTranslation(english, "Hello world") } + }); + Assert.True(result.Success); + Assert.AreEqual(key, result.Result.Key); + + // re-get + var item = await DictionaryItemService.GetAsync(result.Result!.Key); + Assert.NotNull(item); + Assert.AreEqual(key, item.Key); + } + + [Test] + public async Task Can_Add_Translation_To_Existing_Dictionary_Item() + { + var english = await LanguageService.GetAsync("en-US"); + + var result = await DictionaryItemService.CreateAsync(new DictionaryItem("Testing12345")); + Assert.True(result.Success); + + // re-get + var item = await DictionaryItemService.GetAsync(result.Result!.Key); + Assert.NotNull(item); + + item.Translations = new List { new DictionaryTranslation(english, "Hello world") }; + + result = await DictionaryItemService.UpdateAsync(item); + Assert.True(result.Success); + + Assert.AreEqual(1, item.Translations.Count()); + foreach (var translation in item.Translations) + { + Assert.AreEqual("Hello world", translation.Value); + } + + item.Translations = new List(item.Translations) + { + new DictionaryTranslation( + await LanguageService.GetAsync("en-GB"), + "My new value") + }; + + result = await DictionaryItemService.UpdateAsync(item); + Assert.True(result.Success); + + // re-get + item = await DictionaryItemService.GetAsync(item.Key); + Assert.NotNull(item); + + Assert.AreEqual(2, item.Translations.Count()); + Assert.AreEqual("Hello world", item.Translations.First().Value); + Assert.AreEqual("My new value", item.Translations.Last().Value); + } + + [Test] + public async Task Can_Delete_DictionaryItem() + { + var item = await DictionaryItemService.GetAsync("Child"); + Assert.NotNull(item); + + var result = await DictionaryItemService.DeleteAsync(item.Key); + Assert.IsTrue(result.Success); + Assert.AreEqual(DictionaryItemOperationStatus.Success, result.Status); + + var deletedItem = await DictionaryItemService.GetAsync("Child"); + Assert.Null(deletedItem); + } + + [Test] + public async Task Can_Update_Existing_DictionaryItem() + { + var item = await DictionaryItemService.GetAsync("Child"); + foreach (var translation in item.Translations) + { + translation.Value += "UPDATED"; + } + + var result = await DictionaryItemService.UpdateAsync(item); + Assert.True(result.Success); + + var updatedItem = await DictionaryItemService.GetAsync("Child"); + Assert.NotNull(updatedItem); + + foreach (var translation in updatedItem.Translations) + { + Assert.That(translation.Value.EndsWith("UPDATED"), Is.True); + } + } + + [Test] + public async Task Cannot_Add_Duplicate_DictionaryItem_ItemKey() + { + var item = await DictionaryItemService.GetAsync("Child"); + Assert.IsNotNull(item); + + item.ItemKey = "Parent"; + + var result = await DictionaryItemService.UpdateAsync(item); + Assert.IsFalse(result.Success); + Assert.AreEqual(DictionaryItemOperationStatus.DuplicateItemKey, result.Status); + + var item2 = await DictionaryItemService.GetAsync("Child"); + Assert.IsNotNull(item2); + Assert.AreEqual(item.Key, item2.Key); + } + + [Test] + public async Task Cannot_Create_Child_DictionaryItem_Under_Missing_Parent() + { + var itemKey = Guid.NewGuid().ToString("N"); + + var result = await DictionaryItemService.CreateAsync(new DictionaryItem(Guid.NewGuid(), itemKey)); + Assert.IsFalse(result.Success); + Assert.AreEqual(DictionaryItemOperationStatus.ParentNotFound, result.Status); + + var item = await DictionaryItemService.GetAsync(itemKey); + Assert.IsNull(item); + } + + [Test] + public async Task Cannot_Create_Multiple_DictionaryItems_With_Same_ItemKey() + { + var itemKey = Guid.NewGuid().ToString("N"); + var result = await DictionaryItemService.CreateAsync(new DictionaryItem(itemKey)); + + Assert.IsTrue(result.Success); + + result = await DictionaryItemService.CreateAsync(new DictionaryItem(itemKey)); + Assert.IsFalse(result.Success); + Assert.AreEqual(DictionaryItemOperationStatus.DuplicateItemKey, result.Status); + } + + [Test] + public async Task Cannot_Update_Non_Existant_DictionaryItem() + { + var result = await DictionaryItemService.UpdateAsync(new DictionaryItem("NoSuchItemKey")); + Assert.False(result.Success); + Assert.AreEqual(DictionaryItemOperationStatus.ItemNotFound, result.Status); + } + + [Test] + public async Task Cannot_Update_DictionaryItem_With_Empty_Id() + { + var item = await DictionaryItemService.GetAsync("Child"); + Assert.IsNotNull(item); + + item = new DictionaryItem(item.ParentId, item.ItemKey) { Key = item.Key, Translations = item.Translations }; + + var result = await DictionaryItemService.UpdateAsync(item); + Assert.False(result.Success); + Assert.AreEqual(DictionaryItemOperationStatus.ItemNotFound, result.Status); + } + + [Test] + public async Task Cannot_Delete_Non_Existant_DictionaryItem() + { + var result = await DictionaryItemService.DeleteAsync(Guid.NewGuid()); + Assert.IsFalse(result.Success); + Assert.AreEqual(DictionaryItemOperationStatus.ItemNotFound, result.Status); + } + + [Test] + public async Task Cannot_Create_DictionaryItem_With_Duplicate_Key() + { + var english = await LanguageService.GetAsync("en-US"); + var key = Guid.NewGuid(); + + var result = await DictionaryItemService.CreateAsync( + new DictionaryItem("Testing123") + { + Key = key, + Translations = new List { new DictionaryTranslation(english, "Hello world") } + }); + Assert.True(result.Success); + + result = await DictionaryItemService.CreateAsync( + new DictionaryItem("Testing456") + { + Key = key, + Translations = new List { new DictionaryTranslation(english, "Hello world") } + }); + Assert.False(result.Success); + Assert.AreEqual(DictionaryItemOperationStatus.DuplicateKey, result.Status); + + // re-get + var item = await DictionaryItemService.GetAsync("Testing123"); + Assert.NotNull(item); + Assert.AreEqual(key, item.Key); + + item = await DictionaryItemService.GetAsync("Testing456"); + Assert.Null(item); + } + + [Test] + public async Task Cannot_Create_DictionaryItem_With_Reused_DictionaryItem_Model() + { + var childItem = await DictionaryItemService.GetAsync("Child"); + Assert.NotNull(childItem); + + childItem.ItemKey = "Something"; + childItem.Translations.First().Value = "Something Edited"; + childItem.Translations.Last().Value = "Something Also Edited"; + + var result = await DictionaryItemService.CreateAsync(childItem); + Assert.IsFalse(result.Success); + Assert.AreEqual(DictionaryItemOperationStatus.InvalidId, result.Status); + } + + private async Task CreateTestData() + { + var languageDaDk = new LanguageBuilder() + .WithCultureInfo("da-DK") + .Build(); + var languageEnGb = new LanguageBuilder() + .WithCultureInfo("en-GB") + .Build(); + + var languageResult = await LanguageService.CreateAsync(languageDaDk, 0); + Assert.IsTrue(languageResult.Success); + languageResult = await LanguageService.CreateAsync(languageEnGb, 0); + Assert.IsTrue(languageResult.Success); + + var dictionaryResult = await DictionaryItemService.CreateAsync( + new DictionaryItem("Parent") + { + Translations = new List + { + new DictionaryTranslation(languageEnGb, "ParentValue"), + new DictionaryTranslation(languageDaDk, "ForældreVærdi") + } + }); + Assert.True(dictionaryResult.Success); + IDictionaryItem parentItem = dictionaryResult.Result!; + + _parentItemId = parentItem.Key; + + dictionaryResult = await DictionaryItemService.CreateAsync( + new DictionaryItem( + parentItem.Key, + "Child" + ) + { + Translations = new List + { + new DictionaryTranslation(languageEnGb, "ChildValue"), + new DictionaryTranslation(languageDaDk, "BørnVærdi") + } + }); + Assert.True(dictionaryResult.Success); + IDictionaryItem childItem = dictionaryResult.Result!; + + _childItemId = childItem.Key; + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs index 3003d28f4c..038c5d3349 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs @@ -25,23 +25,23 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; public class EntityServiceTests : UmbracoIntegrationTest { [SetUp] - public void SetupTestData() + public async Task SetupTestData() { if (_langFr == null && _langEs == null) { _langFr = new Language("fr-FR", "French (France)"); _langEs = new Language("es-ES", "Spanish (Spain)"); - LocalizationService.Save(_langFr); - LocalizationService.Save(_langEs); + await LanguageService.CreateAsync(_langFr); + await LanguageService.CreateAsync(_langEs); } CreateTestData(); } - private Language _langFr; - private Language _langEs; + private Language? _langFr; + private Language? _langEs; - private ILocalizationService LocalizationService => GetRequiredService(); + private ILanguageService LanguageService => GetRequiredService(); private IContentTypeService ContentTypeService => GetRequiredService(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityXmlSerializerTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityXmlSerializerTests.cs index 6ba1356acd..3c6a9da17d 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityXmlSerializerTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityXmlSerializerTests.cs @@ -1,10 +1,7 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Xml.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -18,6 +15,7 @@ using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Media; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; @@ -26,7 +24,6 @@ using Umbraco.Cms.Tests.Common.Builders.Extensions; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services.Importing; -using Umbraco.Extensions; namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; @@ -66,10 +63,10 @@ public class EntityXmlSerializerTests : UmbracoIntegrationTest } [Test] - public void Can_Export_DictionaryItems() + public async Task Can_Export_DictionaryItems() { // Arrange - CreateDictionaryData(); + await CreateDictionaryData(); var localizationService = GetRequiredService(); var dictionaryItem = localizationService.GetDictionaryItemByKey("Parent"); @@ -84,21 +81,21 @@ public class EntityXmlSerializerTests : UmbracoIntegrationTest } [Test] - public void Can_Export_Languages() + public async Task Can_Export_Languages() { // Arrange - var localizationService = GetRequiredService(); + var languageService = GetRequiredService(); var languageNbNo = new LanguageBuilder() .WithCultureInfo("nb-NO") .WithCultureName("Norwegian Bokmål (Norway)") .Build(); - localizationService.Save(languageNbNo); + await languageService.CreateAsync(languageNbNo); var languageEnGb = new LanguageBuilder() .WithCultureInfo("en-GB") .Build(); - localizationService.Save(languageEnGb); + await languageService.CreateAsync(languageEnGb); var newPackageXml = XElement.Parse(ImportResources.Dictionary_Package); var languageItemsElement = newPackageXml.Elements("Languages").First(); @@ -291,38 +288,45 @@ public class EntityXmlSerializerTests : UmbracoIntegrationTest }); } - private void CreateDictionaryData() + private async Task CreateDictionaryData() { - var localizationService = GetRequiredService(); + var languageService = GetRequiredService(); + var dictionaryItemService = GetRequiredService(); var languageNbNo = new LanguageBuilder() .WithCultureInfo("nb-NO") .WithCultureName("Norwegian") .Build(); - localizationService.Save(languageNbNo); + await languageService.CreateAsync(languageNbNo); var languageEnGb = new LanguageBuilder() .WithCultureInfo("en-GB") .Build(); - localizationService.Save(languageEnGb); + await languageService.CreateAsync(languageEnGb); - var parentItem = new DictionaryItem("Parent") { Key = Guid.Parse("28f2e02a-8c66-4fcd-85e3-8524d551c0d3") }; + var parentKey = Guid.Parse("28f2e02a-8c66-4fcd-85e3-8524d551c0d3"); + var parentItem = new DictionaryItem("Parent") { Key = parentKey }; var parentTranslations = new List { new DictionaryTranslation(languageNbNo, "ForelderVerdi"), new DictionaryTranslation(languageEnGb, "ParentValue") }; parentItem.Translations = parentTranslations; - localizationService.Save(parentItem); + var result = await dictionaryItemService.CreateAsync(parentItem); + Assert.IsTrue(result.Success); + Assert.AreEqual(parentKey, result.Result.Key); + var childKey = Guid.Parse("e7dba0a9-d517-4ba4-8e18-2764d392c611"); var childItem = - new DictionaryItem(parentItem.Key, "Child") { Key = Guid.Parse("e7dba0a9-d517-4ba4-8e18-2764d392c611") }; + new DictionaryItem(parentItem.Key, "Child") { Key = childKey }; var childTranslations = new List { new DictionaryTranslation(languageNbNo, "BarnVerdi"), new DictionaryTranslation(languageEnGb, "ChildValue") }; childItem.Translations = childTranslations; - localizationService.Save(childItem); + result = await dictionaryItemService.CreateAsync(childItem); + Assert.IsTrue(result.Success); + Assert.AreEqual(childKey, result.Result.Key); } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LanguageServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LanguageServiceTests.cs new file mode 100644 index 0000000000..dca0f82535 --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LanguageServiceTests.cs @@ -0,0 +1,399 @@ +using NUnit.Framework; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Testing; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; + +[TestFixture] +[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] +public class LanguageServiceTests : UmbracoIntegrationTest +{ + private ILanguageService LanguageService => GetRequiredService(); + + [SetUp] + public async Task SetUp() => await CreateTestData(); + + [Test] + public async Task Can_Get_All_Languages() + { + var languages = await LanguageService.GetAllAsync(); + Assert.NotNull(languages); + Assert.IsTrue(languages.Any()); + Assert.That(languages.Count(), Is.EqualTo(3)); + } + + [Test] + public async Task Can_GetLanguageByIsoCode() + { + var danish = await LanguageService.GetAsync("da-DK"); + var english = await LanguageService.GetAsync("en-GB"); + Assert.NotNull(danish); + Assert.NotNull(english); + } + + [Test] + public async Task Does_Not_Fail_When_Language_Doesnt_Exist() + { + var language = await LanguageService.GetAsync("sv-SE"); + Assert.Null(language); + } + + [Test] + public async Task Can_Delete_Language() + { + var languageNbNo = new LanguageBuilder() + .WithCultureInfo("nb-NO") + .Build(); + await LanguageService.CreateAsync(languageNbNo, 0); + Assert.That(languageNbNo.HasIdentity, Is.True); + + var language = await LanguageService.GetAsync("nb-NO"); + Assert.NotNull(language); + + var result = await LanguageService.DeleteAsync("nb-NO"); + Assert.IsTrue(result.Success); + + language = await LanguageService.GetAsync("nb-NO"); + Assert.Null(language); + } + + [Test] + public async Task Can_Delete_Language_Used_As_Fallback() + { + var languageDaDk = await LanguageService.GetAsync("da-DK"); + var languageNbNo = new LanguageBuilder() + .WithCultureInfo("nb-NO") + .WithFallbackLanguageId(languageDaDk.Id) + .Build(); + await LanguageService.CreateAsync(languageNbNo, 0); + + var result = await LanguageService.DeleteAsync("da-DK"); + Assert.IsTrue(result.Success); + + var language = await LanguageService.GetAsync("da-DK"); + Assert.Null(language); + } + + [Test] + public async Task Find_BaseData_Language() + { + // Act + var languages = await LanguageService.GetAllAsync(); + + // Assert + Assert.That(3, Is.EqualTo(languages.Count())); + } + + [Test] + public async Task Save_Language_And_GetLanguageByIsoCode() + { + // Arrange + var isoCode = "en-AU"; + var languageEnAu = new LanguageBuilder() + .WithCultureInfo(isoCode) + .Build(); + + // Act + await LanguageService.CreateAsync(languageEnAu); + var result = await LanguageService.GetAsync(isoCode); + + // Assert + Assert.NotNull(result); + } + + [Test] + public async Task Set_Default_Language() + { + var languageEnAu = new LanguageBuilder() + .WithCultureInfo("en-AU") + .WithIsDefault(true) + .Build(); + await LanguageService.CreateAsync(languageEnAu); + var result = await LanguageService.GetAsync(languageEnAu.IsoCode); + + Assert.IsTrue(result.IsDefault); + + var languageEnNz = new LanguageBuilder() + .WithCultureInfo("en-NZ") + .WithIsDefault(true) + .Build(); + await LanguageService.CreateAsync(languageEnNz); + var result2 = await LanguageService.GetAsync(languageEnNz.IsoCode); + + // re-get + result = await LanguageService.GetAsync(languageEnAu.IsoCode); + + Assert.IsTrue(result2.IsDefault); + Assert.IsFalse(result.IsDefault); + } + + [Test] + public async Task Deleted_Language_Should_Not_Exist() + { + var isoCode = "en-AU"; + var languageEnAu = new LanguageBuilder() + .WithCultureInfo(isoCode) + .Build(); + await LanguageService.CreateAsync(languageEnAu); + + // Act + var result = await LanguageService.DeleteAsync(languageEnAu.IsoCode); + Assert.IsTrue(result.Success); + + // Assert + Assert.IsNull(await LanguageService.GetAsync(isoCode)); + } + + [Test] + public async Task Can_Update_Existing_Language() + { + ILanguage languageDaDk = await LanguageService.GetAsync("da-DK"); + Assert.NotNull(languageDaDk); + Assert.IsFalse(languageDaDk.IsMandatory); + languageDaDk.IsMandatory = true; + languageDaDk.IsoCode = "da"; + languageDaDk.CultureName = "New Culture Name For da-DK"; + + var result = await LanguageService.UpdateAsync(languageDaDk); + Assert.IsTrue(result.Success); + Assert.AreEqual(LanguageOperationStatus.Success, result.Status); + + // re-get + languageDaDk = await LanguageService.GetAsync(languageDaDk.IsoCode); + Assert.NotNull(languageDaDk); + Assert.IsTrue(languageDaDk.IsMandatory); + Assert.AreEqual("da", languageDaDk.IsoCode); + Assert.AreEqual("New Culture Name For da-DK", languageDaDk.CultureName); + } + + [Test] + public async Task Can_Change_Default_Language_By_Update() + { + var defaultLanguageIsoCode = await LanguageService.GetDefaultIsoCodeAsync(); + Assert.AreEqual("en-US", defaultLanguageIsoCode); + + ILanguage languageDaDk = await LanguageService.GetAsync("da-DK"); + Assert.NotNull(languageDaDk); + Assert.IsFalse(languageDaDk.IsDefault); + Assert.AreNotEqual(defaultLanguageIsoCode, languageDaDk.IsoCode); + + languageDaDk.IsDefault = true; + var result = await LanguageService.UpdateAsync(languageDaDk); + Assert.IsTrue(result.Success); + + // re-get + var previousDefaultLanguage = await LanguageService.GetAsync(defaultLanguageIsoCode); + Assert.NotNull(previousDefaultLanguage); + Assert.IsFalse(previousDefaultLanguage.IsDefault); + languageDaDk = await LanguageService.GetAsync(languageDaDk.IsoCode); + Assert.NotNull(languageDaDk); + Assert.IsTrue(languageDaDk.IsDefault); + } + + [Test] + public async Task Cannot_Create_Language_With_Invalid_IsoCode() + { + var invalidLanguage = new Language("no-such-iso-code", "Invalid ISO code"); + var result = await LanguageService.CreateAsync(invalidLanguage); + Assert.IsFalse(result.Success); + Assert.AreEqual(LanguageOperationStatus.InvalidIsoCode, result.Status); + } + + [Test] + public async Task Cannot_Create_Duplicate_Languages() + { + var isoCode = "en-AU"; + var languageEnAu = new LanguageBuilder() + .WithCultureInfo(isoCode) + .Build(); + var result = await LanguageService.CreateAsync(languageEnAu); + Assert.IsTrue(result.Success); + + var duplicateLanguageEnAu = new LanguageBuilder() + .WithCultureInfo(isoCode) + .Build(); + result = await LanguageService.CreateAsync(duplicateLanguageEnAu); + Assert.IsFalse(result.Success); + Assert.AreEqual(LanguageOperationStatus.DuplicateIsoCode, result.Status); + } + + [Test] + public async Task Cannot_Create_Language_With_NonExisting_Fallback_Language() + { + var isoCode = "en-AU"; + var languageEnAu = new LanguageBuilder() + .WithCultureInfo(isoCode) + .WithFallbackLanguageId(123456789) + .Build(); + var result = await LanguageService.CreateAsync(languageEnAu); + Assert.IsFalse(result.Success); + Assert.AreEqual(LanguageOperationStatus.InvalidFallback, result.Status); + } + + [Test] + public async Task Cannot_Update_Non_Existing_Language() + { + ILanguage language = new Language("da", "Danish"); + var result = await LanguageService.UpdateAsync(language); + Assert.IsFalse(result.Success); + Assert.AreEqual(LanguageOperationStatus.NotFound, result.Status); + } + + [Test] + public async Task Cannot_Undefault_Default_Language_By_Update() + { + var defaultLanguageIsoCode = await LanguageService.GetDefaultIsoCodeAsync(); + Assert.IsNotNull(defaultLanguageIsoCode); + var defaultLanguage = await LanguageService.GetAsync(defaultLanguageIsoCode); + Assert.IsNotNull(defaultLanguage); + Assert.IsTrue(defaultLanguage.IsDefault); + + defaultLanguage.IsDefault = false; + var result = await LanguageService.UpdateAsync(defaultLanguage); + Assert.IsFalse(result.Success); + Assert.AreEqual(LanguageOperationStatus.MissingDefault, result.Status); + + // re-get + defaultLanguage = await LanguageService.GetAsync(defaultLanguageIsoCode); + Assert.IsNotNull(defaultLanguage); + Assert.IsTrue(defaultLanguage.IsDefault); + defaultLanguageIsoCode = await LanguageService.GetDefaultIsoCodeAsync(); + Assert.AreEqual(defaultLanguage.IsoCode, defaultLanguageIsoCode); + } + + [Test] + public async Task Cannot_Update_Language_With_NonExisting_Fallback_Language() + { + ILanguage languageDaDk = await LanguageService.GetAsync("da-DK"); + Assert.NotNull(languageDaDk); + Assert.IsNull(languageDaDk.FallbackLanguageId); + + languageDaDk.FallbackLanguageId = 123456789; + var result = await LanguageService.UpdateAsync(languageDaDk); + Assert.IsFalse(result.Success); + Assert.AreEqual(LanguageOperationStatus.InvalidFallback, result.Status); + } + + [Test] + public async Task Cannot_Create_Direct_Cyclic_Fallback_Language() + { + ILanguage languageDaDk = await LanguageService.GetAsync("da-DK"); + ILanguage languageEnGb = await LanguageService.GetAsync("en-GB"); + Assert.NotNull(languageDaDk); + Assert.NotNull(languageEnGb); + Assert.IsNull(languageDaDk.FallbackLanguageId); + Assert.IsNull(languageEnGb.FallbackLanguageId); + + languageDaDk.FallbackLanguageId = languageEnGb.Id; + var result = await LanguageService.UpdateAsync(languageDaDk); + Assert.IsTrue(result.Success); + + languageEnGb.FallbackLanguageId = languageDaDk.Id; + result = await LanguageService.UpdateAsync(languageEnGb); + Assert.IsFalse(result.Success); + Assert.AreEqual(LanguageOperationStatus.InvalidFallback, result.Status); + } + + [Test] + public async Task Cannot_Create_Implicit_Cyclic_Fallback_Language() + { + ILanguage languageEnUs = await LanguageService.GetAsync("en-US"); + ILanguage languageEnGb = await LanguageService.GetAsync("en-GB"); + ILanguage languageDaDk = await LanguageService.GetAsync("da-DK"); + Assert.IsNotNull(languageEnUs); + Assert.IsNotNull(languageEnGb); + Assert.IsNotNull(languageDaDk); + Assert.IsNull(languageEnUs.FallbackLanguageId); + Assert.IsNull(languageEnGb.FallbackLanguageId); + Assert.IsNull(languageDaDk.FallbackLanguageId); + + languageEnGb.FallbackLanguageId = languageEnUs.Id; + var result = await LanguageService.UpdateAsync(languageEnGb); + Assert.IsTrue(result.Success); + + languageDaDk.FallbackLanguageId = languageEnGb.Id; + result = await LanguageService.UpdateAsync(languageDaDk); + Assert.IsTrue(result.Success); + + languageEnUs.FallbackLanguageId = languageDaDk.Id; + result = await LanguageService.UpdateAsync(languageEnUs); + Assert.IsFalse(result.Success); + Assert.AreEqual(LanguageOperationStatus.InvalidFallback, result.Status); + + // re-get + languageEnUs = await LanguageService.GetAsync("en-US"); + languageEnGb = await LanguageService.GetAsync("en-GB"); + languageDaDk = await LanguageService.GetAsync("da-DK"); + Assert.IsNotNull(languageEnUs); + Assert.IsNotNull(languageEnGb); + Assert.IsNotNull(languageDaDk); + + Assert.AreEqual(languageEnUs.Id, languageEnGb.FallbackLanguageId); + Assert.AreEqual(languageEnGb.Id, languageDaDk.FallbackLanguageId); + Assert.IsNull(languageEnUs.FallbackLanguageId); + } + + [Test] + public async Task Cannot_Delete_Default_Language() + { + var languageNbNo = new LanguageBuilder() + .WithCultureInfo("nb-NO") + .WithIsDefault(true) + .Build(); + var result = await LanguageService.CreateAsync(languageNbNo); + Assert.IsTrue(result.Success); + + result = await LanguageService.DeleteAsync(languageNbNo.IsoCode); + Assert.IsFalse(result.Success); + Assert.AreEqual(LanguageOperationStatus.MissingDefault, result.Status); + + // re-get + languageNbNo = await LanguageService.GetAsync("nb-NO"); + Assert.NotNull(languageNbNo); + Assert.IsTrue(languageNbNo.IsDefault); + } + + [Test] + public async Task Cannot_Delete_NonExisting_Language() + { + var languageNbNo = new LanguageBuilder() + .WithCultureInfo("nb-NO") + .Build(); + + var result = await LanguageService.DeleteAsync(languageNbNo.IsoCode); + Assert.IsFalse(result.Success); + Assert.AreEqual(LanguageOperationStatus.NotFound, result.Status); + } + + [Test] + public async Task Cannot_Create_Language_With_Reused_Language_Model() + { + var languageDaDk = await LanguageService.GetAsync("da-DK"); + Assert.NotNull(languageDaDk); + languageDaDk.IsoCode = "nb-NO"; + + var result = await LanguageService.CreateAsync(languageDaDk); + Assert.IsFalse(result.Success); + Assert.AreEqual(LanguageOperationStatus.InvalidId, result.Status); + } + + private async Task CreateTestData() + { + var languageDaDk = new LanguageBuilder() + .WithCultureInfo("da-DK") + .Build(); + var languageEnGb = new LanguageBuilder() + .WithCultureInfo("en-GB") + .Build(); + + var languageResult = await LanguageService.CreateAsync(languageDaDk, 0); + Assert.IsTrue(languageResult.Success); + languageResult = await LanguageService.CreateAsync(languageEnGb, 0); + Assert.IsTrue(languageResult.Success); + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LocalizationServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LocalizationServiceTests.cs index b93e7ea77d..5b3eee4965 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LocalizationServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/LocalizationServiceTests.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using NUnit.Framework; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Services.OperationStatus; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Builders.Extensions; @@ -134,27 +133,26 @@ public class LocalizationServiceTests : UmbracoIntegrationTest for (var i = 0; i < 25; i++) { // Create 2 per level - var result = LocalizationService.Create( - "D1" + i, - currParentId, - new List + var desc1 = new DictionaryItem(currParentId, "D1" + i) + { + Translations = new List { new DictionaryTranslation(en, "ChildValue1 " + i), new DictionaryTranslation(dk, "BørnVærdi1 " + i) - }); - - Assert.IsTrue(result.Success); - - LocalizationService.Create( - "D2" + i, - currParentId, - new List + } + }; + var desc2 = new DictionaryItem(currParentId, "D2" + i) + { + Translations = new List { new DictionaryTranslation(en, "ChildValue2 " + i), new DictionaryTranslation(dk, "BørnVærdi2 " + i) - }); + } + }; + LocalizationService.Save(desc1); + LocalizationService.Save(desc2); - currParentId = result.Result!.Key; + currParentId = desc1.Key; } ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlTrace = true; @@ -241,12 +239,14 @@ public class LocalizationServiceTests : UmbracoIntegrationTest { var english = LocalizationService.GetLanguageByIsoCode("en-US"); - var result = LocalizationService.Create("Testing123", null, new List { new DictionaryTranslation(english, "Hello world") }); - Assert.True(result.Success); + var item = (IDictionaryItem)new DictionaryItem("Testing123") + { + Translations = new List { new DictionaryTranslation(english, "Hello world") } + }; + LocalizationService.Save(item); // re-get - var item = LocalizationService.GetDictionaryItemById(result.Result!.Id); - Assert.NotNull(item); + item = LocalizationService.GetDictionaryItemById(item.Id); Assert.Greater(item.Id, 0); Assert.IsTrue(item.HasIdentity); @@ -256,75 +256,42 @@ public class LocalizationServiceTests : UmbracoIntegrationTest } [Test] - public void Can_Create_DictionaryItem_At_Root_With_All_Languages() + public void Can_Create_DictionaryItem_At_Root_With_Identity() { - var allLangs = LocalizationService.GetAllLanguages().ToArray(); - Assert.Greater(allLangs.Length, 0); - - var translations = allLangs.Select(language => new DictionaryTranslation(language, $"Translation for: {language.IsoCode}")).ToArray(); - var result = LocalizationService.Create("Testing12345", null, translations); - - Assert.IsTrue(result.Success); - Assert.AreEqual(DictionaryItemOperationStatus.Success, result.Status); - Assert.NotNull(result.Result); + var item = LocalizationService.CreateDictionaryItemWithIdentity( + "Testing12345", null, "Hellooooo"); // re-get - var item = LocalizationService.GetDictionaryItemById(result.Result!.Id); + item = LocalizationService.GetDictionaryItemById(item.Id); Assert.IsNotNull(item); Assert.Greater(item.Id, 0); Assert.IsTrue(item.HasIdentity); Assert.IsFalse(item.ParentId.HasValue); Assert.AreEqual("Testing12345", item.ItemKey); + var allLangs = LocalizationService.GetAllLanguages(); + Assert.Greater(allLangs.Count(), 0); foreach (var language in allLangs) { - Assert.AreEqual($"Translation for: {language.IsoCode}", + Assert.AreEqual("Hellooooo", item.Translations.Single(x => x.Language.CultureName == language.CultureName).Value); } } - [Test] - public void Can_Create_DictionaryItem_At_Root_With_Some_Languages() - { - var allLangs = LocalizationService.GetAllLanguages().ToArray(); - Assert.Greater(allLangs.Length, 1); - - var firstLanguage = allLangs.First(); - var translations = new[] { new DictionaryTranslation(firstLanguage, $"Translation for: {firstLanguage.IsoCode}") }; - var result = LocalizationService.Create("Testing12345", null, translations); - - Assert.IsTrue(result.Success); - Assert.AreEqual(DictionaryItemOperationStatus.Success, result.Status); - Assert.NotNull(result.Result); - - // re-get - var item = LocalizationService.GetDictionaryItemById(result.Result!.Id); - - Assert.IsNotNull(item); - Assert.Greater(item.Id, 0); - Assert.IsTrue(item.HasIdentity); - Assert.IsFalse(item.ParentId.HasValue); - Assert.AreEqual("Testing12345", item.ItemKey); - Assert.AreEqual(1, item.Translations.Count()); - Assert.AreEqual(firstLanguage.Id, item.Translations.First().LanguageId); - } - [Test] public void Can_Add_Translation_To_Existing_Dictionary_Item() { var english = LocalizationService.GetLanguageByIsoCode("en-US"); - var result = LocalizationService.Create("Testing123", null); - Assert.True(result.Success); + var item = (IDictionaryItem)new DictionaryItem("Testing123"); + LocalizationService.Save(item); // re-get - var item = LocalizationService.GetDictionaryItemById(result.Result!.Id); - Assert.NotNull(item); + item = LocalizationService.GetDictionaryItemById(item.Id); item.Translations = new List { new DictionaryTranslation(english, "Hello world") }; - result = LocalizationService.Update(item); - Assert.True(result.Success); + LocalizationService.Save(item); Assert.AreEqual(1, item.Translations.Count()); foreach (var translation in item.Translations) @@ -339,12 +306,10 @@ public class LocalizationServiceTests : UmbracoIntegrationTest "My new value") }; - result = LocalizationService.Update(item); - Assert.True(result.Success); + LocalizationService.Save(item); // re-get item = LocalizationService.GetDictionaryItemById(item.Id); - Assert.NotNull(item); Assert.AreEqual(2, item.Translations.Count()); Assert.AreEqual("Hello world", item.Translations.First().Value); @@ -357,9 +322,7 @@ public class LocalizationServiceTests : UmbracoIntegrationTest var item = LocalizationService.GetDictionaryItemByKey("Child"); Assert.NotNull(item); - var result = LocalizationService.Delete(item.Key); - Assert.IsTrue(result.Success); - Assert.AreEqual(DictionaryItemOperationStatus.Success, result.Status); + LocalizationService.Delete(item); var deletedItem = LocalizationService.GetDictionaryItemByKey("Child"); Assert.Null(deletedItem); @@ -374,8 +337,7 @@ public class LocalizationServiceTests : UmbracoIntegrationTest translation.Value += "UPDATED"; } - var result = LocalizationService.Update(item); - Assert.True(result.Success); + LocalizationService.Save(item); var updatedItem = LocalizationService.GetDictionaryItemByKey("Child"); Assert.NotNull(updatedItem); @@ -472,78 +434,6 @@ public class LocalizationServiceTests : UmbracoIntegrationTest Assert.Null(result); } - [Test] - public void Cannot_Add_Duplicate_DictionaryItem_Key() - { - var item = LocalizationService.GetDictionaryItemByKey("Child"); - Assert.IsNotNull(item); - - item.ItemKey = "Parent"; - - var result = LocalizationService.Update(item); - Assert.IsFalse(result.Success); - Assert.AreEqual(DictionaryItemOperationStatus.DuplicateItemKey, result.Status); - - var item2 = LocalizationService.GetDictionaryItemByKey("Child"); - Assert.IsNotNull(item2); - Assert.AreEqual(item.Key, item2.Key); - } - - [Test] - public void Cannot_Create_Child_DictionaryItem_Under_Missing_Parent() - { - var itemKey = Guid.NewGuid().ToString("N"); - - var result = LocalizationService.Create(itemKey, Guid.NewGuid(), Array.Empty()); - Assert.IsFalse(result.Success); - Assert.AreEqual(DictionaryItemOperationStatus.ParentNotFound, result.Status); - - var item = LocalizationService.GetDictionaryItemByKey(itemKey); - Assert.IsNull(item); - } - - [Test] - public void Cannot_Create_Multiple_DictionaryItems_With_Same_ItemKey() - { - var itemKey = Guid.NewGuid().ToString("N"); - var result = LocalizationService.Create(itemKey, null, Array.Empty()); - - Assert.IsTrue(result.Success); - - result = LocalizationService.Create(itemKey, null, Array.Empty()); - Assert.IsFalse(result.Success); - Assert.AreEqual(DictionaryItemOperationStatus.DuplicateItemKey, result.Status); - } - - [Test] - public void Cannot_Update_Non_Existant_DictionaryItem() - { - var result = LocalizationService.Update(new DictionaryItem("NoSuchItemKey")); - Assert.False(result.Success); - Assert.AreEqual(DictionaryItemOperationStatus.ItemNotFound, result.Status); - } - - [Test] - public void Cannot_Update_DictionaryItem_With_Empty_Id() - { - var item = LocalizationService.GetDictionaryItemByKey("Child"); - Assert.IsNotNull(item); - - item = new DictionaryItem(item.ParentId, item.ItemKey) { Key = item.Key, Translations = item.Translations }; - - var result = LocalizationService.Update(item); - Assert.False(result.Success); - Assert.AreEqual(DictionaryItemOperationStatus.ItemNotFound, result.Status); - } - - [Test] - public void Cannot_Delete_Non_Existant_DictionaryItem() - { - var result = LocalizationService.Delete(Guid.NewGuid()); - Assert.IsFalse(result.Success); - Assert.AreEqual(DictionaryItemOperationStatus.ItemNotFound, result.Status); - } - public void CreateTestData() { var languageDaDk = new LanguageBuilder() @@ -558,32 +448,28 @@ public class LocalizationServiceTests : UmbracoIntegrationTest _danishLangId = languageDaDk.Id; _englishLangId = languageEnGb.Id; - var result = LocalizationService.Create( - "Parent", - null, - new List + var parentItem = new DictionaryItem("Parent") + { + Translations = new List { new DictionaryTranslation(languageEnGb, "ParentValue"), new DictionaryTranslation(languageDaDk, "ForældreVærdi") - }); - Assert.True(result.Success); - IDictionaryItem parentItem = result.Result!; - + } + }; + LocalizationService.Save(parentItem); _parentItemGuidId = parentItem.Key; _parentItemIntId = parentItem.Id; - result = LocalizationService.Create( - "Child", - parentItem.Key, - new List + var childItem = new DictionaryItem(parentItem.Key, "Child") + { + Translations = new List { new DictionaryTranslation(languageEnGb, "ChildValue"), new DictionaryTranslation(languageDaDk, "BørnVærdi") - }); - Assert.True(result.Success); - IDictionaryItem childItem = result.Result!; - + } + }; + LocalizationService.Save(childItem); _childItemGuidId = childItem.Key; _childItemIntId = childItem.Id; } -} +} \ No newline at end of file diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs index 70db8b3ce2..a04ecefab8 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs @@ -1,10 +1,7 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Linq; using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Cms.Core; @@ -16,7 +13,6 @@ using Umbraco.Cms.Tests.Common.Builders.Extensions; using Umbraco.Cms.Tests.Integration.TestServerTest; using Umbraco.Cms.Web.BackOffice.Controllers; using Umbraco.Cms.Web.Common.Formatters; -using Umbraco.Extensions; namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.BackOffice.Controllers; @@ -32,10 +28,10 @@ public class ContentControllerTests : UmbracoTestServerTestBase [Test] public async Task PostSave_Validate_Existing_Content() { - var localizationService = GetRequiredService(); + var languageService = GetRequiredService(); // Add another language - localizationService.Save(new LanguageBuilder() + await languageService.CreateAsync(new LanguageBuilder() .WithCultureInfo(DkIso) .WithIsDefault(false) .Build()); @@ -88,10 +84,10 @@ public class ContentControllerTests : UmbracoTestServerTestBase [Test] public async Task PostSave_Validate_At_Least_One_Variant_Flagged_For_Saving() { - var localizationService = GetRequiredService(); + var languageService = GetRequiredService(); // Add another language - localizationService.Save(new LanguageBuilder() + await languageService.CreateAsync(new LanguageBuilder() .WithCultureInfo(DkIso) .WithIsDefault(false) .Build()); @@ -156,10 +152,10 @@ public class ContentControllerTests : UmbracoTestServerTestBase [Test] public async Task PostSave_Validate_Properties_Exist() { - var localizationService = GetRequiredService(); + var languageService = GetRequiredService(); // Add another language - localizationService.Save(new LanguageBuilder() + await languageService.CreateAsync(new LanguageBuilder() .WithCultureInfo(DkIso) .WithIsDefault(false) .Build()); @@ -219,10 +215,10 @@ public class ContentControllerTests : UmbracoTestServerTestBase [Test] public async Task PostSave_Simple_Invariant() { - var localizationService = GetRequiredService(); + var languageService = GetRequiredService(); // Add another language - localizationService.Save(new LanguageBuilder() + await languageService.CreateAsync(new LanguageBuilder() .WithCultureInfo(DkIso) .WithIsDefault(false) .Build()); @@ -278,10 +274,10 @@ public class ContentControllerTests : UmbracoTestServerTestBase [Test] public async Task PostSave_Validate_Empty_Name() { - var localizationService = GetRequiredService(); + var languageService = GetRequiredService(); // Add another language - localizationService.Save(new LanguageBuilder() + await languageService.CreateAsync(new LanguageBuilder() .WithCultureInfo(DkIso) .WithIsDefault(false) .Build()); @@ -340,10 +336,10 @@ public class ContentControllerTests : UmbracoTestServerTestBase [Test] public async Task PostSave_Validate_Variants_Empty_Name() { - var localizationService = GetRequiredService(); + var languageService = GetRequiredService(); // Add another language - localizationService.Save(new LanguageBuilder() + await languageService.CreateAsync(new LanguageBuilder() .WithCultureInfo(DkIso) .WithIsDefault(false) .Build()); @@ -402,8 +398,8 @@ public class ContentControllerTests : UmbracoTestServerTestBase [Test] public async Task PostSave_Validates_Domains_Exist() { - var localizationService = GetRequiredService(); - localizationService.Save(new LanguageBuilder() + var languageService = GetRequiredService(); + await languageService.CreateAsync(new LanguageBuilder() .WithCultureInfo(DkIso) .WithIsDefault(false) .Build()); @@ -450,14 +446,14 @@ public class ContentControllerTests : UmbracoTestServerTestBase public async Task PostSave_Validates_All_Ancestor_Cultures_Are_Considered() { var sweIso = "sv-SE"; - var localizationService = GetRequiredService(); + var languageService = GetRequiredService(); //Create 2 new languages - localizationService.Save(new LanguageBuilder() + await languageService.CreateAsync(new LanguageBuilder() .WithCultureInfo(DkIso) .WithIsDefault(false) .Build()); - localizationService.Save(new LanguageBuilder() + await languageService.CreateAsync(new LanguageBuilder() .WithCultureInfo(sweIso) .WithIsDefault(false) .Build()); @@ -499,12 +495,12 @@ public class ContentControllerTests : UmbracoTestServerTestBase .WithAction(ContentSaveAction.PublishNew) .Build(); - var enLanguage = localizationService.GetLanguageByIsoCode(UsIso); + var enLanguage = await languageService.GetAsync(UsIso); var domainService = GetRequiredService(); var enDomain = new UmbracoDomain("/en") {RootContentId = content.Id, LanguageId = enLanguage.Id}; domainService.Save(enDomain); - var dkLanguage = localizationService.GetLanguageByIsoCode(DkIso); + var dkLanguage = await languageService.GetAsync(DkIso); var dkDomain = new UmbracoDomain("/dk") {RootContentId = childContent.Id, LanguageId = dkLanguage.Id}; domainService.Save(dkDomain); @@ -536,8 +532,8 @@ public class ContentControllerTests : UmbracoTestServerTestBase [Test] public async Task PostSave_Validates_All_Cultures_Has_Domains() { - var localizationService = GetRequiredService(); - localizationService.Save(new LanguageBuilder() + var languageService = GetRequiredService(); + await languageService.CreateAsync(new LanguageBuilder() .WithCultureInfo(DkIso) .WithIsDefault(false) .Build()); @@ -561,7 +557,7 @@ public class ContentControllerTests : UmbracoTestServerTestBase .WithAction(ContentSaveAction.Publish) .Build(); - var dkLanguage = localizationService.GetLanguageByIsoCode(DkIso); + var dkLanguage = await languageService.GetAsync(DkIso); var domainService = GetRequiredService(); var dkDomain = new UmbracoDomain("/") {RootContentId = content.Id, LanguageId = dkLanguage.Id}; domainService.Save(dkDomain); @@ -592,8 +588,8 @@ public class ContentControllerTests : UmbracoTestServerTestBase [Test] public async Task PostSave_Checks_Ancestors_For_Domains() { - var localizationService = GetRequiredService(); - localizationService.Save(new LanguageBuilder() + var languageService = GetRequiredService(); + await languageService.CreateAsync(new LanguageBuilder() .WithCultureInfo(DkIso) .WithIsDefault(false) .Build()); @@ -632,8 +628,8 @@ public class ContentControllerTests : UmbracoTestServerTestBase contentService.Save(grandChild); - var dkLanguage = localizationService.GetLanguageByIsoCode(DkIso); - var usLanguage = localizationService.GetLanguageByIsoCode(UsIso); + var dkLanguage = await languageService.GetAsync(DkIso); + var usLanguage = await languageService.GetAsync(UsIso); var domainService = GetRequiredService(); var dkDomain = new UmbracoDomain("/") {RootContentId = rootNode.Id, LanguageId = dkLanguage.Id}; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Filters/OutgoingEditorModelEventFilterTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Filters/OutgoingEditorModelEventFilterTests.cs index e627a3300f..1d6042d980 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Filters/OutgoingEditorModelEventFilterTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Filters/OutgoingEditorModelEventFilterTests.cs @@ -1,10 +1,5 @@ -using System; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; +using System.Net; using NUnit.Framework; -using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; @@ -16,7 +11,6 @@ using Umbraco.Cms.Tests.Common.Builders.Extensions; using Umbraco.Cms.Tests.Integration.TestServerTest; using Umbraco.Cms.Web.BackOffice.Controllers; using Umbraco.Cms.Web.Common.Formatters; -using Umbraco.Extensions; namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.BackOffice.Filters; @@ -76,7 +70,7 @@ public class OutgoingEditorModelEventFilterTests : UmbracoTestServerTestBase const string SweIso = "sv-SE"; var contentTypeService = GetRequiredService(); var contentService = GetRequiredService(); - var localizationService = GetRequiredService(); + var languageService = GetRequiredService(); IJsonSerializer serializer = GetRequiredService(); var contentType = new ContentTypeBuilder() @@ -94,8 +88,8 @@ public class OutgoingEditorModelEventFilterTests : UmbracoTestServerTestBase .WithIsDefault(false) .Build(); - localizationService.Save(dkLang); - localizationService.Save(sweLang); + await languageService.CreateAsync(dkLang); + await languageService.CreateAsync(sweLang); var content = new ContentBuilder() .WithoutIdentity()