From 8ae27ecc27834c8461bcb7827999da5faafe349c Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 19 Apr 2018 22:06:02 +1000 Subject: [PATCH 1/9] Updates doc type editor to be able to change update the doc type to support being culture variant --- .../services/umbdataformatter.service.js | 2 +- .../views/permissions/permissions.html | 41 ++++++++++++------- .../Models/ContentEditing/ContentTypeSave.cs | 9 ++++ .../ContentEditing/DocumentTypeDisplay.cs | 3 ++ .../Models/ContentEditing/DocumentTypeSave.cs | 2 +- ...=> ContentItemDisplayVariationResolver.cs} | 4 +- .../Models/Mapping/ContentMapperProfile.cs | 2 +- .../Mapping/ContentTypeMapperProfile.cs | 4 +- .../Mapping/ContentTypeProfileExtensions.cs | 2 +- .../Mapping/ContentTypeVariationsResolver.cs | 26 ++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 3 +- 11 files changed, 76 insertions(+), 22 deletions(-) rename src/Umbraco.Web/Models/Mapping/{VariationResolver.cs => ContentItemDisplayVariationResolver.cs} (90%) create mode 100644 src/Umbraco.Web/Models/Mapping/ContentTypeVariationsResolver.cs diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js index 439720e461..dab6cb8eda 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js @@ -37,7 +37,7 @@ var saveModel = _.pick(displayModel, 'compositeContentTypes', 'isContainer', 'allowAsRoot', 'allowedTemplates', 'allowedContentTypes', 'alias', 'description', 'thumbnail', 'name', 'id', 'icon', 'trashed', - 'key', 'parentId', 'alias', 'path'); + 'key', 'parentId', 'alias', 'path', 'allowCultureVariant'); //TODO: Map these saveModel.allowedTemplates = _.map(displayModel.allowedTemplates, function (t) { return t.alias; }); diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.html index d9c7d185da..7df2577871 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/views/permissions/permissions.html @@ -7,7 +7,7 @@
-
- + - +
+
+ +
+
Content Type Variation
+ Define the rules for how this content type's properties can be varied +
+
+ +
+ +
+ diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs b/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs index 3f3efa15d8..c2ec70d3dc 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs @@ -57,6 +57,15 @@ namespace Umbraco.Web.Models.ContentEditing Groups = new List>(); } + /// + /// A rule for defining how a content type can be varied + /// + /// + /// This is only supported on document types right now but in the future it could be media types too + /// + [DataMember(Name = "allowCultureVariant")] + public bool AllowCultureVariant { get; set; } + //Tabs [DataMember(Name = "groups")] public IEnumerable> Groups { get; set; } diff --git a/src/Umbraco.Web/Models/ContentEditing/DocumentTypeDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/DocumentTypeDisplay.cs index 7965b90ae0..6b82f74ca7 100644 --- a/src/Umbraco.Web/Models/ContentEditing/DocumentTypeDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/DocumentTypeDisplay.cs @@ -20,6 +20,9 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "defaultTemplate")] public EntityBasic DefaultTemplate { get; set; } + + [DataMember(Name = "allowCultureVariant")] + public bool AllowCultureVariant { get; set; } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/DocumentTypeSave.cs b/src/Umbraco.Web/Models/ContentEditing/DocumentTypeSave.cs index 3f163f860f..21164b493b 100644 --- a/src/Umbraco.Web/Models/ContentEditing/DocumentTypeSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/DocumentTypeSave.cs @@ -31,7 +31,7 @@ namespace Umbraco.Web.Models.ContentEditing /// public override IEnumerable Validate(ValidationContext validationContext) { - if (AllowedTemplates.Any(x => StringExtensions.IsNullOrWhiteSpace(x))) + if (AllowedTemplates.Any(x => x.IsNullOrWhiteSpace())) yield return new ValidationResult("Template value cannot be null", new[] { "AllowedTemplates" }); foreach (var v in base.Validate(validationContext)) diff --git a/src/Umbraco.Web/Models/Mapping/VariationResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs similarity index 90% rename from src/Umbraco.Web/Models/Mapping/VariationResolver.cs rename to src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs index 9d3de07d77..3d7890a152 100644 --- a/src/Umbraco.Web/Models/Mapping/VariationResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs @@ -10,11 +10,11 @@ using Language = Umbraco.Web.Models.ContentEditing.Language; namespace Umbraco.Web.Models.Mapping { - internal class VariationResolver : IValueResolver> + internal class ContentItemDisplayVariationResolver : IValueResolver> { private readonly ILocalizationService _localizationService; - public VariationResolver(ILocalizationService localizationService) + public ContentItemDisplayVariationResolver(ILocalizationService localizationService) { _localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService)); } diff --git a/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs index 6791afb921..9aa6495492 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs @@ -25,7 +25,7 @@ namespace Umbraco.Web.Models.Mapping var contentTreeNodeUrlResolver = new ContentTreeNodeUrlResolver(); var defaultTemplateResolver = new DefaultTemplateResolver(); var contentUrlResolver = new ContentUrlResolver(); - var variantResolver = new VariationResolver(localizationService); + var variantResolver = new ContentItemDisplayVariationResolver(localizationService); //FROM IContent TO ContentItemDisplay CreateMap() diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeMapperProfile.cs index c9e9554b93..bba27ce3f1 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeMapperProfile.cs @@ -6,6 +6,7 @@ using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Web.Models.ContentEditing; using Umbraco.Core.Services; +using ContentVariation = Umbraco.Core.Models.ContentVariation; namespace Umbraco.Web.Models.Mapping { @@ -26,7 +27,7 @@ namespace Umbraco.Web.Models.Mapping { dest.AllowedTemplates = source.AllowedTemplates .Where(x => x != null) - .Select(s => fileService.GetTemplate(s)) + .Select(fileService.GetTemplate) .ToArray(); if (source.DefaultTemplate != null) @@ -111,6 +112,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dto => dto.AllowedTemplates, opt => opt.Ignore()) .ForMember(dto => dto.DefaultTemplate, opt => opt.Ignore()) .ForMember(display => display.Notifications, opt => opt.Ignore()) + .ForMember(display => display.AllowCultureVariant, opt => opt.MapFrom(type => type.Variations.HasFlag(ContentVariation.CultureNeutral))) .AfterMap((source, dest) => { //sync templates diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeProfileExtensions.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeProfileExtensions.cs index d67eaf8ea2..72842c5354 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeProfileExtensions.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeProfileExtensions.cs @@ -181,7 +181,7 @@ namespace Umbraco.Web.Models.Mapping // ignore, composition is managed in AfterMapContentTypeSaveToEntity .ForMember(dest => dest.ContentTypeComposition, opt => opt.Ignore()) - .ForMember(dto => dto.Variations, opt => opt.Ignore()) // fixme - change when UI supports it! + .ForMember(dto => dto.Variations, opt => opt.ResolveUsing>()) .ForMember( dest => dest.AllowedContentTypes, diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeVariationsResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeVariationsResolver.cs new file mode 100644 index 0000000000..fcfe9a47cc --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeVariationsResolver.cs @@ -0,0 +1,26 @@ +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; +using ContentVariation = Umbraco.Core.Models.ContentVariation; + +namespace Umbraco.Web.Models.Mapping +{ + internal class ContentTypeVariationsResolver : IValueResolver + where TSource : ContentTypeSave + where TDestination : IContentTypeComposition + where TSourcePropertyType : PropertyTypeBasic + { + public ContentVariation Resolve(TSource source, TDestination destination, ContentVariation destMember, ResolutionContext context) + { + //this will always be the case, a content type will always be allowed to be invariant + var result = ContentVariation.InvariantNeutral; + + if (source.AllowCultureVariant) + { + result |= ContentVariation.CultureNeutral; + } + + return result; + } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 923078c0b7..160fc80751 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -238,6 +238,7 @@ + @@ -259,7 +260,7 @@ - + From e9e0274ad869d7f9b4f99bb9ce62043d125f0189 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 20 Apr 2018 00:03:19 +1000 Subject: [PATCH 2/9] fixes MultipleContentPickerParameterEditor --- .../MultipleContentPickerParameterEditor.cs | 16 ++++++++++------ src/Umbraco.Web/Umbraco.Web.csproj | 4 +++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs b/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs index 27d1076352..fa0059bf73 100644 --- a/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs @@ -1,16 +1,20 @@ using Umbraco.Core; +using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors.ParameterEditors { - [ParameterEditor(Constants.PropertyEditors.MultiNodeTreePickerAlias, "Multiple Content Picker", "contentpicker")] - public class MultipleContentPickerParameterEditor : ParameterEditor + [DataEditor("multiNodeTreePicker", EditorType.MacroParameter, "Multiple Content Picker", "contentpicker")] + public class MultipleContentPickerParameterEditor : DataEditor { - public MultipleContentPickerParameterEditor() + + public MultipleContentPickerParameterEditor(ILogger logger, EditorType type = EditorType.PropertyValue) + : base(logger, type) { - Configuration.Add("multiPicker", "1"); - Configuration.Add("minNumber",0 ); - Configuration.Add("maxNumber", 0); + // configure + DefaultConfiguration.Add("multiPicker", "1"); + DefaultConfiguration.Add("minNumber",0 ); + DefaultConfiguration.Add("maxNumber", 0); } } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 0b1e4d3103..7691f294db 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -242,6 +242,7 @@ + @@ -263,7 +264,7 @@ - + @@ -314,6 +315,7 @@ + From d5ebe129832cabff5e39ff329ceddb7e7978d37e Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 20 Apr 2018 00:05:45 +1000 Subject: [PATCH 3/9] removes ununsed aliases --- src/Umbraco.Core/Constants-PropertyEditors.cs | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index 43bd381571..f6fa5a443c 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -68,12 +68,7 @@ namespace Umbraco.Core /// DropDown List. /// public const string DropDownListFlexible = "Umbraco.DropDown.Flexible"; - - /// - /// Folder Browser. - /// - public const string FolderBrowser = "Umbraco.FolderBrowser"; - + /// /// Grid. /// @@ -193,17 +188,7 @@ namespace Umbraco.Core /// Upload Field. /// public const string UploadField = "Umbraco.UploadField"; - - /// - /// XPatch Checkbox List. - /// - public const string XPathCheckBoxList = "Umbraco.XPathCheckBoxList"; - - /// - /// XPath DropDown List. - /// - public const string XPathDropDownList = "Umbraco.XPathDropDownList"; - + /// /// Email Address. /// From 04da7cfd20101100c6900727fc7ca11d669069c1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 20 Apr 2018 00:09:12 +1000 Subject: [PATCH 4/9] doh, fixes csproj merge --- src/Umbraco.Web/Umbraco.Web.csproj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 7691f294db..0f900826a4 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -242,7 +242,6 @@ - @@ -264,7 +263,7 @@ - + From c84087e96ba547ebda3d7fa4f7d616cd5e60b907 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 20 Apr 2018 00:59:23 +1000 Subject: [PATCH 5/9] Gets the variant names working in the editor --- src/Umbraco.Web/Editors/ContentController.cs | 16 ++++++++++-- .../Editors/ContentControllerBase.cs | 26 ------------------- src/Umbraco.Web/Editors/MediaController.cs | 7 ++++- src/Umbraco.Web/Editors/MemberController.cs | 9 +++++++ .../Mapping/ContentItemDisplayNameResolver.cs | 25 ++++++++++++++++++ .../ContentItemDisplayVariationResolver.cs | 3 +++ .../Models/Mapping/ContentMapperProfile.cs | 1 + src/Umbraco.Web/Umbraco.Web.csproj | 1 + 8 files changed, 59 insertions(+), 29 deletions(-) create mode 100644 src/Umbraco.Web/Models/Mapping/ContentItemDisplayNameResolver.cs diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index dce9ca531d..6b1601fac2 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -943,14 +943,26 @@ namespace Umbraco.Web.Editors return content; } } - + /// /// Maps the dto property values to the persisted model /// /// private void MapPropertyValues(ContentItemSave contentItem) { - UpdateName(contentItem); + //Don't update the name if it is empty + if (contentItem.Name.IsNullOrWhiteSpace() == false) + { + //set the name according to the culture settings + if (contentItem.LanguageId.HasValue && contentItem.PersistedContent.ContentType.Variations.HasFlag(ContentVariation.CultureNeutral)) + { + contentItem.PersistedContent.SetName(contentItem.LanguageId, contentItem.Name); + } + else + { + contentItem.PersistedContent.Name = contentItem.Name; + } + } //TODO: We need to support 'send to publish' diff --git a/src/Umbraco.Web/Editors/ContentControllerBase.cs b/src/Umbraco.Web/Editors/ContentControllerBase.cs index 70baabecf5..fdfefa6133 100644 --- a/src/Umbraco.Web/Editors/ContentControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentControllerBase.cs @@ -36,32 +36,6 @@ namespace Umbraco.Web.Editors return errorResponse; } - protected void UpdateName(ContentBaseItemSave contentItem) - where TPersisted : IContentBase - { - //Don't update the name if it is empty - if (contentItem.Name.IsNullOrWhiteSpace() == false) - { - contentItem.PersistedContent.Name = contentItem.Name; - } - } - - protected HttpResponseMessage PerformSort(ContentSortOrder sorted) - { - if (sorted == null) - { - return Request.CreateResponse(HttpStatusCode.NotFound); - } - - //if there's nothing to sort just return ok - if (sorted.IdSortOrder.Length == 0) - { - return Request.CreateResponse(HttpStatusCode.OK); - } - - return null; - } - /// /// Maps the dto property values to the persisted model /// diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 2c7a5f30c6..fb09cefd03 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -470,7 +470,12 @@ namespace Umbraco.Web.Editors // * we have a reference to the DTO object and the persisted object // * Permissions are valid - UpdateName(contentItem); + //Don't update the name if it is empty + if (contentItem.Name.IsNullOrWhiteSpace() == false) + { + contentItem.PersistedContent.Name = contentItem.Name; + } + MapPropertyValues( contentItem, (save, property) => property.GetValue(), //get prop val diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index bf60560574..bb99030804 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -591,6 +591,15 @@ namespace Umbraco.Web.Editors contentItem.PersistedContent.Username = providedUserName; } + private static void UpdateName(MemberSave memberSave) + { + //Don't update the name if it is empty + if (memberSave.Name.IsNullOrWhiteSpace() == false) + { + memberSave.PersistedContent.Name = memberSave.Name; + } + } + /// /// This is going to create the user with the membership provider and check for validation /// diff --git a/src/Umbraco.Web/Models/Mapping/ContentItemDisplayNameResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentItemDisplayNameResolver.cs new file mode 100644 index 0000000000..125db912be --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/ContentItemDisplayNameResolver.cs @@ -0,0 +1,25 @@ +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; +using ContentVariation = Umbraco.Core.Models.ContentVariation; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Used to map the name from an depending on it's variation settings + /// + internal class ContentItemDisplayNameResolver : IValueResolver + { + public string Resolve(IContent source, ContentItemDisplay destination, string destMember, ResolutionContext context) + { + var langId = context.GetLanguageId(); + if (langId.HasValue && source.ContentType.Variations.HasFlag(ContentVariation.CultureNeutral)) + { + //return the culture name being requested + return source.GetName(langId); + } + + return source.Name; + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs index 3d7890a152..41146d7a8e 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs @@ -10,6 +10,9 @@ using Language = Umbraco.Web.Models.ContentEditing.Language; namespace Umbraco.Web.Models.Mapping { + /// + /// Used to map the variations collection from an instance + /// internal class ContentItemDisplayVariationResolver : IValueResolver> { private readonly ILocalizationService _localizationService; diff --git a/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs index 9aa6495492..627f508906 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs @@ -32,6 +32,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(src.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document, src.Key))) .ForMember(dest => dest.Owner, opt => opt.ResolveUsing(src => contentOwnerResolver.Resolve(src))) .ForMember(dest => dest.Updater, opt => opt.ResolveUsing(src => creatorResolver.Resolve(src))) + .ForMember(dest => dest.Name, opt => opt.ResolveUsing()) .ForMember(dest => dest.Variants, opt => opt.ResolveUsing(variantResolver)) .ForMember(dest => dest.Icon, opt => opt.MapFrom(src => src.ContentType.Icon)) .ForMember(dest => dest.ContentTypeAlias, opt => opt.MapFrom(src => src.ContentType.Alias)) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 7691f294db..56c04deca1 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -242,6 +242,7 @@ + From e9f0e98d2c2847f5f5e7195de7d51b5107837bc8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 20 Apr 2018 13:12:55 +1000 Subject: [PATCH 6/9] Updates EntityRepository to get the variant names for content items ... for the non-paged query which we can use in the tree --- .../Implement/EntityRepository.cs | 109 ++++++++++++++++-- .../Repositories/EntityRepositoryTests.cs | 12 -- .../Services/EntityServiceTests.cs | 56 ++++++++- src/Umbraco.Tests/Umbraco.Tests.csproj | 6 +- .../Trees/ContentTreeControllerBase.cs | 2 +- 5 files changed, 154 insertions(+), 31 deletions(-) delete mode 100644 src/Umbraco.Tests/Persistence/Repositories/EntityRepositoryTests.cs diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index 857e059942..6e34030b29 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -128,6 +128,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var dtos = page.Items; var entities = dtos.Select(x => BuildEntity(isContent, isMedia, x)).ToArray(); + //TODO: For isContent will we need to build up the variation info? + if (isMedia) BuildProperties(entities, dtos); @@ -251,18 +253,35 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var isMedia = objectType == Constants.ObjectTypes.Media; var sql = GetBaseWhere(isContent, isMedia, false, null, objectType); + var translator = new SqlTranslator(sql, query); sql = translator.Translate(); sql = AddGroupBy(isContent, isMedia, sql); - var dtos = Database.Fetch(sql); - if (dtos.Count == 0) return Enumerable.Empty(); - var entities = dtos.Select(x => BuildEntity(isContent, isMedia, x)).ToArray(); + //isContent is going to return a 1:M result now with the variants so we need to do different things + if (isContent) + { + var dtos = Database.FetchOneToMany( + dto => dto.VariationInfo, + dto => dto.VersionId, + sql); + if (dtos.Count == 0) return Enumerable.Empty(); + var entities = dtos.Select(x => BuildDocumentEntity(x)).ToArray(); + return entities; + } + else + { + var dtos = Database.Fetch(sql); - if (isMedia) - BuildProperties(entities, dtos); + if (dtos.Count == 0) return Enumerable.Empty(); - return entities; + var entities = dtos.Select(x => BuildEntity(isContent, isMedia, x)).ToArray(); + + if (isMedia) + BuildProperties(entities, dtos); + + return entities; + } } public UmbracoObjectTypes GetObjectType(int id) @@ -409,8 +428,33 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // return wrappedSql; //} - // the DTO corresponding to fields selected by GetBase + + /// + /// The DTO used to fetch results for a content item with it's variation info + /// + private class ContentEntityDto : BaseDto + { + [ResultColumn, Reference(ReferenceType.Many)] + public List VariationInfo { get; set; } + } + + /// + /// The DTO used in the 1:M result for content variation info + /// + private class ContentEntityVariationInfoDto + { + [Column("versionCultureId")] + public int VersionCultureId { get; set; } + [Column("versionCultureLangId")] + public int LanguageId { get; set; } + [Column("versionCultureName")] + public string Name { get; set; } + } + // ReSharper disable once ClassNeverInstantiated.Local + /// + /// the DTO corresponding to fields selected by GetBase + /// private class BaseDto { // ReSharper disable UnusedAutoPropertyAccessor.Local @@ -455,14 +499,21 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .AndSelect(x => x.SortOrder, x => x.UniqueId, x => x.Text, x => x.NodeObjectType, x => x.CreateDate) .Append(", COUNT(child.id) AS children"); - if (isContent) - sql - .AndSelect(x => x.Published, x => x.Edited); - if (isContent || isMedia) sql .AndSelect(x => NPocoSqlExtensions.Statics.Alias(x.Id, "versionId")) .AndSelect(x => x.Alias, x => x.Icon, x => x.Thumbnail, x => x.IsContainer); + + if (isContent) + { + sql + .AndSelect(x => x.Published, x => x.Edited) + //This MUST come last in the select statements since we will end up with a 1:M query + .AndSelect( + x => NPocoSqlExtensions.Statics.Alias(x.Id, "versionCultureId"), + x => NPocoSqlExtensions.Statics.Alias(x.LanguageId, "versionCultureLangId"), + x => NPocoSqlExtensions.Statics.Alias(x.Name, "versionCultureName")); + } } sql @@ -482,10 +533,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .InnerJoin().On((left, right) => left.NodeId == right.NodeId); } + //Any LeftJoin statements need to come last if (isCount == false) + { sql .LeftJoin("child").On((left, right) => left.NodeId == right.ParentId, aliasRight: "child"); + if (isContent) + sql + .LeftJoin().On((left, right) => left.Id == right.VersionId); + } + + filter?.Invoke(sql); return sql; @@ -542,8 +601,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .AndBy(x => x.SortOrder, x => x.UniqueId, x => x.Text, x => x.NodeObjectType, x => x.CreateDate); if (isContent) + { sql - .AndBy(x => x.Published, x => x.Edited); + .AndBy(x => x.Published, x => x.Edited) + .AndBy(x => x.Id, x => x.LanguageId, x => x.Name); + } + if (isContent || isMedia) sql @@ -819,6 +882,28 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return entity; } + /// + /// Builds the from a and ensures the AdditionalData is populated with variant info + /// + /// + /// + private static EntitySlim BuildDocumentEntity(ContentEntityDto dto) + { + // EntitySlim does not track changes + var entity = new DocumentEntitySlim(); + BuildDocumentEntity(entity, dto); + var variantInfo = new Dictionary(); + if (dto.VariationInfo != null) + { + foreach (var info in dto.VariationInfo) + { + variantInfo[info.LanguageId] = info.Name; + } + entity.AdditionalData["VariantInfo"] = variantInfo; + } + return entity; + } + #endregion } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/EntityRepositoryTests.cs b/src/Umbraco.Tests/Persistence/Repositories/EntityRepositoryTests.cs deleted file mode 100644 index 2357ba273a..0000000000 --- a/src/Umbraco.Tests/Persistence/Repositories/EntityRepositoryTests.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.Tests.Persistence.Repositories -{ - class EntityRepositoryTests - { - } -} diff --git a/src/Umbraco.Tests/Services/EntityServiceTests.cs b/src/Umbraco.Tests/Services/EntityServiceTests.cs index b8d24d940c..d7acc6e177 100644 --- a/src/Umbraco.Tests/Services/EntityServiceTests.cs +++ b/src/Umbraco.Tests/Services/EntityServiceTests.cs @@ -427,6 +427,60 @@ namespace Umbraco.Tests.Services Assert.That(entities.Any(), Is.True); Assert.That(entities.Length, Is.EqualTo(1)); Assert.That(entities.Any(x => x.Trashed), Is.False); + } + + [Test] + public void EntityService_Can_Get_Child_Content_By_ParentId_And_UmbracoObjectType_With_Variant_Names() + { + var service = ServiceContext.EntityService; + + var langFr = new Language("fr-FR"); + var langEs = new Language("es-ES"); + ServiceContext.LocalizationService.Save(langFr); + ServiceContext.LocalizationService.Save(langEs); + + var contentType = MockedContentTypes.CreateSimpleContentType("test1", "Test1", false); + contentType.Variations = ContentVariation.CultureNeutral; + ServiceContext.ContentTypeService.Save(contentType); + + var root = MockedContent.CreateSimpleContent(contentType); + ServiceContext.ContentService.Save(root); + + for (int i = 0; i < 10; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root); + if (i % 2 == 0) + { + c1.SetName(langFr.Id, "Test " + i + " - FR"); + c1.SetName(langEs.Id, "Test " + i + " - ES"); + } + ServiceContext.ContentService.Save(c1); + } + + var entities = service.GetChildren(root.Id, UmbracoObjectTypes.Document).ToArray(); + + Assert.AreEqual(10, entities.Length); + + for (int i = 0; i < entities.Length; i++) + { + if (i % 2 == 0) + { + Assert.AreEqual(1, entities[i].AdditionalData.Count); + Assert.AreEqual("VariantInfo", entities[i].AdditionalData.Keys.First()); + var variantInfo = entities[i].AdditionalData.First().Value as IDictionary; + Assert.IsNotNull(variantInfo); + var keys = variantInfo.Keys.ToList(); + var vals = variantInfo.Values.ToList(); + Assert.AreEqual(langFr.Id, keys[0]); + Assert.AreEqual("Test " + i + " - FR", vals[0]); + Assert.AreEqual(langEs.Id, keys[1]); + Assert.AreEqual("Test " + i + " - ES", vals[1]); + } + else + { + Assert.AreEqual(0, entities[i].AdditionalData.Count); + } + } } [Test] @@ -518,7 +572,7 @@ namespace Umbraco.Tests.Services } Assert.That(entities.Any(x => - x.AdditionalData.Any(y => y.Value is EntitySlim.PropertySlim && ((EntitySlim.PropertySlim) y.Value).PropertyEditorAlias == Constants.PropertyEditors.Aliases.UploadField)), Is.True); + x.AdditionalData.Any(y => y.Value is EntitySlim.PropertySlim && ((EntitySlim.PropertySlim)y.Value).PropertyEditorAlias == Constants.PropertyEditors.Aliases.UploadField)), Is.True); } [Test] diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index f630194a41..3da04b48ca 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -143,7 +143,6 @@ - @@ -606,11 +605,9 @@ $(NuGetPackageFolders.Split(';')[0]) - - - + @@ -624,5 +621,4 @@ - \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index b6ab625b78..488b1097c0 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -217,7 +217,7 @@ namespace Umbraco.Web.Trees // we'll mock using this and it will just be some mock data foreach(var e in result) { - if (e.AdditionalData.TryGetValue("VariantNames", out var variantNames)) + if (e.AdditionalData.TryGetValue("VariantInfo", out var variantNames)) { var casted = (IDictionary)variantNames; e.Name = casted[langId.Value]; From d99beb0c8b59db9743c3f9ee1bcdcd46161d1e14 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 20 Apr 2018 13:26:45 +1000 Subject: [PATCH 7/9] Gets the tree to display the culture specific name --- .../Implement/EntityRepository.cs | 2 +- .../Services/EntityServiceTests.cs | 2 +- .../Trees/ContentTreeControllerBase.cs | 30 ++++++++++--------- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index 6e34030b29..e1499b8f61 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -899,7 +899,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { variantInfo[info.LanguageId] = info.Name; } - entity.AdditionalData["VariantInfo"] = variantInfo; + entity.AdditionalData["CultureNames"] = variantInfo; } return entity; } diff --git a/src/Umbraco.Tests/Services/EntityServiceTests.cs b/src/Umbraco.Tests/Services/EntityServiceTests.cs index d7acc6e177..ebf4ea4cb3 100644 --- a/src/Umbraco.Tests/Services/EntityServiceTests.cs +++ b/src/Umbraco.Tests/Services/EntityServiceTests.cs @@ -466,7 +466,7 @@ namespace Umbraco.Tests.Services if (i % 2 == 0) { Assert.AreEqual(1, entities[i].AdditionalData.Count); - Assert.AreEqual("VariantInfo", entities[i].AdditionalData.Keys.First()); + Assert.AreEqual("CultureNames", entities[i].AdditionalData.Keys.First()); var variantInfo = entities[i].AdditionalData.First().Value as IDictionary; Assert.IsNotNull(variantInfo); var keys = variantInfo.Keys.ToList(); diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 488b1097c0..abab847151 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -209,22 +209,24 @@ namespace Umbraco.Web.Trees { result = Services.EntityService.GetChildren(entityId, UmbracoObjectType).ToArray(); } - - if (langId.HasValue) + + //This should really never be null, but we'll error check anyways + int? currLangId = langId.HasValue ? langId.Value : Services.LocalizationService.GetDefaultVariantLanguage()?.Id; + + //Try to see if there is a variant name for the current language for the item and set the name accordingly. + //If any of this fails, the tree node name will remain the default invariant culture name. + //fixme - what if there is no name found at all ? This could occur if the doc type is variant and the user fills in all language values, then creates a new lang, sets it as default and deletes all other pre-existing langs + if (currLangId.HasValue) { - //need to update all node names - //TODO: This is not currently stored, we need to wait until U4-11128 is complete for this to work, in the meantime - // we'll mock using this and it will just be some mock data - foreach(var e in result) + foreach (var e in result) { - if (e.AdditionalData.TryGetValue("VariantInfo", out var variantNames)) - { - var casted = (IDictionary)variantNames; - e.Name = casted[langId.Value]; - } - else - { - e.Name = e.Name + " (lang: " + langId.Value + ")"; + if (e.AdditionalData.TryGetValue("CultureNames", out var cultureNames) + && cultureNames is IDictionary cnd) + { + if (cnd.TryGetValue(currLangId.Value, out var name)) + { + e.Name = name; + } } } } From 9325d30e7ca5e9c1e59dc629cc75d1e9cd165809 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 20 Apr 2018 13:44:20 +1000 Subject: [PATCH 8/9] adds some notes to the setVariantStatusColor --- .../editor/umbeditorheader.directive.js | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js index 520962a084..6a15b0baba 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js @@ -225,18 +225,23 @@ Use this directive to construct a header inside the main editor window. }); } + //TODO: This doesn't really affect any UI currently, need some feedback from mads function setVariantStatusColor(variants) { angular.forEach(variants, function (variant) { - angular.forEach(variant.states, function(state){ - switch (state.name) { - case "Published": - case "Published +": - state.stateColor = "success"; - break; - default: - state.stateColor = "gray"; - } - }); + + //TODO: What about variant.exists? If we are applying colors/styles, this should be one of them + + switch (variant.state) { + case "Published": + variant.stateColor = "success"; + break; + case "Unpublished": + //TODO: Not sure if these statuses will ever bubble up to the UI? + case "Publishing": + case "Unpublishing": + default: + variant.stateColor = "gray"; + } }); } From 5dfa0e744642a75157e1b8dad158dd7afd1f95e4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 20 Apr 2018 16:07:34 +1000 Subject: [PATCH 9/9] Updates EntityRepository to support normal get operations adds another unit tests, adds notes. --- .../Implement/EntityRepository.cs | 80 ++++++++++++++----- .../Services/EntityServiceTests.cs | 61 +++++++++++--- .../Trees/ContentTreeControllerBase.cs | 5 +- 3 files changed, 114 insertions(+), 32 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index e1499b8f61..cb0d19cc2e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -150,15 +150,29 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var isMedia = objectTypeId == Constants.ObjectTypes.Media; var sql = GetFullSqlForEntityType(isContent, isMedia, objectTypeId, key); - var dto = Database.FirstOrDefault(sql); - if (dto == null) return null; - var entity = BuildEntity(isContent, isMedia, dto); + //isContent is going to return a 1:M result now with the variants so we need to do different things + if (isContent) + { + var dtos = Database.FetchOneToMany( + dto => dto.VariationInfo, + dto => dto.VersionId, + sql); + if (dtos.Count == 0) return null; + return BuildDocumentEntity(dtos[0]); + } + else + { + var dto = Database.FirstOrDefault(sql); + if (dto == null) return null; - if (isMedia) - BuildProperties(entity, dto); + var entity = BuildEntity(isContent, isMedia, dto); - return entity; + if (isMedia) + BuildProperties(entity, dto); + + return entity; + } } public virtual IEntitySlim Get(int id) @@ -174,15 +188,30 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var isMedia = objectTypeId == Constants.ObjectTypes.Media; var sql = GetFullSqlForEntityType(isContent, isMedia, objectTypeId, id); - var dto = Database.FirstOrDefault(sql); - if (dto == null) return null; - var entity = BuildEntity(isContent, isMedia, dto); + //isContent is going to return a 1:M result now with the variants so we need to do different things + if (isContent) + { + var dtos = Database.FetchOneToMany( + dto => dto.VariationInfo, + dto => dto.VersionId, + sql); + if (dtos.Count == 0) return null; + return BuildDocumentEntity(dtos[0]); + } + else + { + var dto = Database.FirstOrDefault(sql); + if (dto == null) return null; - if (isMedia) - BuildProperties(entity, dto); + var entity = BuildEntity(isContent, isMedia, dto); + + if (isMedia) + BuildProperties(entity, dto); + + return entity; + } - return entity; } public virtual IEnumerable GetAll(Guid objectType, params int[] ids) @@ -205,15 +234,30 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var isMedia = objectType == Constants.ObjectTypes.Media; var sql = GetFullSqlForEntityType(isContent, isMedia, objectType, filter); - var dtos = Database.Fetch(sql); - if (dtos.Count == 0) return Enumerable.Empty(); - var entities = dtos.Select(x => BuildEntity(isContent, isMedia, x)).ToArray(); + //isContent is going to return a 1:M result now with the variants so we need to do different things + if (isContent) + { + var dtos = Database.FetchOneToMany( + dto => dto.VariationInfo, + dto => dto.VersionId, + sql); + if (dtos.Count == 0) return Enumerable.Empty(); + var entities = dtos.Select(x => BuildDocumentEntity(x)).ToArray(); + return entities; + } + else + { + var dtos = Database.Fetch(sql); + if (dtos.Count == 0) return Enumerable.Empty(); - if (isMedia) - BuildProperties(entities, dtos); + var entities = dtos.Select(x => BuildEntity(isContent, isMedia, x)).ToArray(); - return entities; + if (isMedia) + BuildProperties(entities, dtos); + + return entities; + } } public virtual IEnumerable GetAllPaths(Guid objectType, params int[] ids) diff --git a/src/Umbraco.Tests/Services/EntityServiceTests.cs b/src/Umbraco.Tests/Services/EntityServiceTests.cs index ebf4ea4cb3..124c77846d 100644 --- a/src/Umbraco.Tests/Services/EntityServiceTests.cs +++ b/src/Umbraco.Tests/Services/EntityServiceTests.cs @@ -19,7 +19,23 @@ namespace Umbraco.Tests.Services [Apartment(ApartmentState.STA)] [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] public class EntityServiceTests : TestWithSomeContentBase - { + { + private Language _langFr; + private Language _langEs; + + public override void SetUp() + { + base.SetUp(); + + if (_langFr == null && _langEs == null) + { + _langFr = new Language("fr-FR"); + _langEs = new Language("es-ES"); + ServiceContext.LocalizationService.Save(_langFr); + ServiceContext.LocalizationService.Save(_langEs); + } + } + [Test] public void EntityService_Can_Get_Paged_Content_Children() { @@ -429,30 +445,49 @@ namespace Umbraco.Tests.Services Assert.That(entities.Any(x => x.Trashed), Is.False); } + [Test] + public void EntityService_Can_Get_Content_By_UmbracoObjectType_With_Variant_Names() + { + var service = ServiceContext.EntityService; + + + var alias = "test" + Guid.NewGuid(); + var contentType = MockedContentTypes.CreateSimpleContentType(alias, alias, false); + contentType.Variations = ContentVariation.CultureNeutral; + ServiceContext.ContentTypeService.Save(contentType); + + var c1 = MockedContent.CreateSimpleContent(contentType, "Test", -1); + c1.SetName(_langFr.Id, "Test - FR"); + c1.SetName(_langEs.Id, "Test - ES"); + ServiceContext.ContentService.Save(c1); + + var result = service.Get(c1.Id, UmbracoObjectTypes.Document); + Assert.AreEqual("Test", result.Name); + Assert.IsTrue(result.AdditionalData.ContainsKey("CultureNames")); + var cultureNames = (IDictionary)result.AdditionalData["CultureNames"]; + Assert.AreEqual("Test - FR", cultureNames[_langFr.Id]); + Assert.AreEqual("Test - ES", cultureNames[_langEs.Id]); + } + [Test] public void EntityService_Can_Get_Child_Content_By_ParentId_And_UmbracoObjectType_With_Variant_Names() { - var service = ServiceContext.EntityService; + var service = ServiceContext.EntityService; - var langFr = new Language("fr-FR"); - var langEs = new Language("es-ES"); - ServiceContext.LocalizationService.Save(langFr); - ServiceContext.LocalizationService.Save(langEs); - var contentType = MockedContentTypes.CreateSimpleContentType("test1", "Test1", false); contentType.Variations = ContentVariation.CultureNeutral; ServiceContext.ContentTypeService.Save(contentType); var root = MockedContent.CreateSimpleContent(contentType); - ServiceContext.ContentService.Save(root); - + ServiceContext.ContentService.Save(root); + for (int i = 0; i < 10; i++) { var c1 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root); if (i % 2 == 0) { - c1.SetName(langFr.Id, "Test " + i + " - FR"); - c1.SetName(langEs.Id, "Test " + i + " - ES"); + c1.SetName(_langFr.Id, "Test " + i + " - FR"); + c1.SetName(_langEs.Id, "Test " + i + " - ES"); } ServiceContext.ContentService.Save(c1); } @@ -471,9 +506,9 @@ namespace Umbraco.Tests.Services Assert.IsNotNull(variantInfo); var keys = variantInfo.Keys.ToList(); var vals = variantInfo.Values.ToList(); - Assert.AreEqual(langFr.Id, keys[0]); + Assert.AreEqual(_langFr.Id, keys[0]); Assert.AreEqual("Test " + i + " - FR", vals[0]); - Assert.AreEqual(langEs.Id, keys[1]); + Assert.AreEqual(_langEs.Id, keys[1]); Assert.AreEqual("Test " + i + " - ES", vals[1]); } else diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index abab847151..96a1029f3c 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -215,7 +215,10 @@ namespace Umbraco.Web.Trees //Try to see if there is a variant name for the current language for the item and set the name accordingly. //If any of this fails, the tree node name will remain the default invariant culture name. - //fixme - what if there is no name found at all ? This could occur if the doc type is variant and the user fills in all language values, then creates a new lang, sets it as default and deletes all other pre-existing langs + + //fixme - what if there is no name found at all ? This could occur if the doc type is variant and the user fills in all language values, then creates a new lang and sets it as the default + //fixme - what if the user changes this document type to not allow culture variants after it's already been created with culture variants, this means we'll be displaying the culture variant name when in fact we should be displaying the invariant name... but that would be null + if (currLangId.HasValue) { foreach (var e in result)