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. /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index 857e059942..cb0d19cc2e 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); @@ -148,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) @@ -172,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) @@ -203,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) @@ -251,18 +297,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 +472,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 +543,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 +577,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 +645,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 +926,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["CultureNames"] = 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..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() { @@ -427,6 +443,79 @@ 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_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 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("CultureNames", 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 +607,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.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"; + } }); } 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/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 6eb8db3c61..467562428d 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/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/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/VariationResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs similarity index 85% rename from src/Umbraco.Web/Models/Mapping/VariationResolver.cs rename to src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs index ece6f87a33..07b64ac309 100644 --- a/src/Umbraco.Web/Models/Mapping/VariationResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs @@ -11,11 +11,14 @@ using Language = Umbraco.Web.Models.ContentEditing.Language; namespace Umbraco.Web.Models.Mapping { - internal class VariationResolver : IValueResolver> + /// + /// Used to map the variations collection from an instance + /// + 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..627f508906 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs @@ -25,13 +25,14 @@ 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() .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/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/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index b6ab625b78..96a1029f3c 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -209,22 +209,27 @@ 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 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) { - //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("VariantNames", 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; + } } } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 0f900826a4..56c04deca1 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -242,6 +242,8 @@ + + @@ -263,7 +265,7 @@ - +