diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 142d7540c2..f5a38a96cb 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -275,6 +275,19 @@ namespace Umbraco.Core.Models if (ValidateAll().Any()) return false; + // Name and PublishName are managed by the repository, but Names and PublishNames + // must be managed here as they depend on the existing / supported variations. + if (string.IsNullOrWhiteSpace(Name)) + throw new InvalidOperationException($"Cannot publish invariant culture without a name."); + PublishName = Name; + foreach (var (languageId, name) in Names) + { + if (string.IsNullOrWhiteSpace(name)) + throw new InvalidOperationException($"Cannot publish {languageId} culture without a name."); + SetPublishName(languageId, name); + } + + // property.PublishAllValues only deals with supported variations (if any) foreach (var property in Properties) property.PublishAllValues(); @@ -298,15 +311,21 @@ namespace Umbraco.Core.Models // the values we want to publish should be valid if (Validate(languageId, segment).Any()) return false; + + // Name and PublishName are managed by the repository, but Names and PublishNames + // must be managed here as they depend on the existing / supported variations. + if (segment == null) + { + var name = GetName(languageId); + if (string.IsNullOrWhiteSpace(name)) + throw new InvalidOperationException($"Cannot publish {languageId?.ToString() ?? "invariant"} culture without a name."); + SetPublishName(languageId, name); + } // property.PublishValue throws on invalid variation, so filter them out foreach (var property in Properties.Where(x => x.PropertyType.ValidateVariation(languageId, segment, throwIfInvalid: false))) property.PublishValue(languageId, segment); - // Name and PublishName are managed by the repository, but Names and PublishNames - // must be managed here as they depend on the existing / supported variations. - SetPublishName(languageId, GetName(languageId)); - _publishedState = PublishedState.Publishing; return true; } @@ -317,15 +336,18 @@ namespace Umbraco.Core.Models // the values we want to publish should be valid if (ValidateCulture(languageId).Any()) return false; + + // Name and PublishName are managed by the repository, but Names and PublishNames + // must be managed here as they depend on the existing / supported variations. + var name = GetName(languageId); + if (string.IsNullOrWhiteSpace(name)) + throw new InvalidOperationException($"Cannot publish {languageId?.ToString() ?? "invariant"} culture without a name."); + SetPublishName(languageId, name); // property.PublishCultureValues only deals with supported variations (if any) foreach (var property in Properties) property.PublishCultureValues(languageId); - // Name and PublishName are managed by the repository, but Names and PublishNames - // must be managed here as they depend on the existing / supported variations. - SetPublishName(languageId, GetName(languageId)); - _publishedState = PublishedState.Publishing; return true; } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 84ff426e91..270e406a24 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -20,6 +20,7 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; +using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -852,7 +853,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected virtual string EnsureUniqueNodeName(int parentId, string nodeName, int id = 0) { var template = SqlContext.Templates.Get("Umbraco.Core.VersionableRepository.EnsureUniqueNodeName", tsql => tsql - .Select(x => NPocoSqlExtensions.Statics.Alias(x.NodeId, "id"), x => NPocoSqlExtensions.Statics.Alias(x.Text, "name")) + .Select(x => Alias(x.NodeId, "id"), x => Alias(x.Text, "name")) .From() .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType") && x.ParentId == SqlTemplate.Arg("parentId"))); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs index 86444a4cc3..4556c78fe6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs @@ -15,6 +15,7 @@ using Umbraco.Core.Persistence.Querying; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; using Umbraco.Core.Services; +using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -280,7 +281,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private string EnsureUniqueNodeName(string nodeName, int id = 0) { var template = SqlContext.Templates.Get("Umbraco.Core.DataTypeDefinitionRepository.EnsureUniqueNodeName", tsql => tsql - .Select(x => NPocoSqlExtensions.Statics.Alias(x.NodeId, "id"), x => NPocoSqlExtensions.Statics.Alias(x.Text, "name")) + .Select(x => Alias(x.NodeId, "id"), x => Alias(x.Text, "name")) .From() .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType"))); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index cb0d19cc2e..09711cf9e1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -8,6 +8,7 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; +using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -128,7 +129,7 @@ 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? + //TODO: For isContent will we need to build up the variation info? if (isMedia) BuildProperties(entities, dtos); @@ -144,35 +145,36 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return dto == null ? null : BuildEntity(false, false, dto); } + private IEntitySlim GetEntity(Sql sql, bool isContent, bool isMedia) + { + //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( + ddto => ddto.VariationInfo, + ddto => ddto.VersionId, + sql); + return dtos.Count == 0 ? null : BuildDocumentEntity(dtos[0]); + } + + var dto = Database.FirstOrDefault(sql); + if (dto == null) return null; + + var entity = BuildEntity(false, isMedia, dto); + + if (isMedia) + BuildProperties(entity, dto); + + return entity; + } + public IEntitySlim Get(Guid key, Guid objectTypeId) { var isContent = objectTypeId == Constants.ObjectTypes.Document || objectTypeId == Constants.ObjectTypes.DocumentBlueprint; var isMedia = objectTypeId == Constants.ObjectTypes.Media; var sql = GetFullSqlForEntityType(isContent, isMedia, objectTypeId, key); - - //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; - - var entity = BuildEntity(isContent, isMedia, dto); - - if (isMedia) - BuildProperties(entity, dto); - - return entity; - } + return GetEntity(sql, isContent, isMedia); } public virtual IEntitySlim Get(int id) @@ -188,30 +190,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var isMedia = objectTypeId == Constants.ObjectTypes.Media; var sql = GetFullSqlForEntityType(isContent, isMedia, objectTypeId, id); - - //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; - - var entity = BuildEntity(isContent, isMedia, dto); - - if (isMedia) - BuildProperties(entity, dto); - - return entity; - } - + return GetEntity(sql, isContent, isMedia); } public virtual IEnumerable GetAll(Guid objectType, params int[] ids) @@ -228,36 +207,38 @@ namespace Umbraco.Core.Persistence.Repositories.Implement : PerformGetAll(objectType); } + private IEnumerable GetEntities(Sql sql, bool isContent, bool isMedia) + { + //isContent is going to return a 1:M result now with the variants so we need to do different things + if (isContent) + { + var cdtos = Database.FetchOneToMany( + dto => dto.VariationInfo, + dto => dto.VersionId, + sql); + return cdtos.Count == 0 + ? Enumerable.Empty() + : cdtos.Select(BuildDocumentEntity).ToArray(); + } + + var dtos = Database.Fetch(sql); + if (dtos.Count == 0) return Enumerable.Empty(); + + var entities = dtos.Select(x => BuildEntity(false, isMedia, x)).ToArray(); + + if (isMedia) + BuildProperties(entities, dtos); + + return entities; + } + private IEnumerable PerformGetAll(Guid objectType, Action> filter = null) { var isContent = objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint; var isMedia = objectType == Constants.ObjectTypes.Media; var sql = GetFullSqlForEntityType(isContent, isMedia, objectType, filter); - - //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(); - - var entities = dtos.Select(x => BuildEntity(isContent, isMedia, x)).ToArray(); - - if (isMedia) - BuildProperties(entities, dtos); - - return entities; - } + return GetEntities(sql, isContent, isMedia); } public virtual IEnumerable GetAllPaths(Guid objectType, params int[] ids) @@ -302,30 +283,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement sql = translator.Translate(); sql = AddGroupBy(isContent, isMedia, sql); - //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(); - - var entities = dtos.Select(x => BuildEntity(isContent, isMedia, x)).ToArray(); - - if (isMedia) - BuildProperties(entities, dtos); - - return entities; - } + return GetEntities(sql, isContent, isMedia); } public UmbracoObjectTypes GetObjectType(int id) @@ -554,9 +512,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .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")); + x => Alias(x.Id, "versionCultureId"), + x => Alias(x.LanguageId, "versionCultureLangId"), + x => Alias(x.Name, "versionCultureName")); } } diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index 8965d60018..6fba071709 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -286,7 +286,7 @@ AnotherContentFinder public void GetDataEditors() { var types = _typeLoader.GetDataEditors(); - Assert.AreEqual(42, types.Count()); + Assert.AreEqual(43, types.Count()); } [Test] diff --git a/src/Umbraco.Tests/Models/VariationTests.cs b/src/Umbraco.Tests/Models/VariationTests.cs index 3b293621d5..d0af5ee2a4 100644 --- a/src/Umbraco.Tests/Models/VariationTests.cs +++ b/src/Umbraco.Tests/Models/VariationTests.cs @@ -236,6 +236,8 @@ namespace Umbraco.Tests.Models // can publish value // and get edited and published values + Assert.Throws(() => content.PublishValues(1)); // no name + content.SetName(1, "name-fr"); content.PublishValues(1); Assert.AreEqual("b", content.GetValue("prop")); Assert.IsNull(content.GetValue("prop", published: true)); diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeProfileExtensions.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeProfileExtensions.cs index 72842c5354..83345988be 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeProfileExtensions.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeProfileExtensions.cs @@ -163,7 +163,7 @@ namespace Umbraco.Web.Models.Mapping // fixme not so clean really var isPublishing = typeof(IContentType).IsAssignableFrom(typeof(TDestination)); - return mapping + mapping = mapping //only map id if set to something higher then zero .ForMember(dest => dest.Id, opt => opt.Condition(src => (Convert.ToInt32(src.Id) > 0))) .ForMember(dest => dest.Id, opt => opt.MapFrom(src => Convert.ToInt32(src.Id))) @@ -179,10 +179,15 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dest => dest.PropertyGroups, opt => opt.Ignore()) .ForMember(dest => dest.NoGroupPropertyTypes, opt => opt.Ignore()) // ignore, composition is managed in AfterMapContentTypeSaveToEntity - .ForMember(dest => dest.ContentTypeComposition, opt => opt.Ignore()) + .ForMember(dest => dest.ContentTypeComposition, opt => opt.Ignore()); - .ForMember(dto => dto.Variations, opt => opt.ResolveUsing>()) + // ignore for members + mapping = typeof(TDestination) == typeof(IMemberType) + ? mapping.ForMember(dto => dto.Variations, opt => opt.Ignore()) + : mapping.ForMember(dto => dto.Variations, opt => opt.ResolveUsing>()); + + mapping = mapping .ForMember( dest => dest.AllowedContentTypes, opt => opt.MapFrom(src => src.AllowedContentTypes.Select((t, i) => new ContentTypeSort(t, i)))) @@ -257,6 +262,8 @@ namespace Umbraco.Web.Models.Mapping // because all property collections were rebuilt, there is no need to remove // some old properties, they are just gone and will be cleared by the repository }); + + return mapping; } private static PropertyGroup MapSaveGroup(PropertyGroupBasic sourceGroup, IEnumerable destOrigGroups)