From a39e77d1cfc480de8ccde45176adca653903b5a4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 21 Sep 2018 15:49:37 +1000 Subject: [PATCH] Gets culture publishing info in IDocumentEntitySlim and has the tree showing the correct publish state, but missing 'edited' flag since i think that requires yet-another-query --- .../Dtos/ContentVersionCultureVariationDto.cs | 5 +- .../Persistence/NPocoSqlExtensions.cs | 21 +- .../Implement/EntityRepository.cs | 478 +++++------------- .../Trees/ContentTreeController.cs | 24 +- 4 files changed, 140 insertions(+), 388 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Dtos/ContentVersionCultureVariationDto.cs b/src/Umbraco.Core/Persistence/Dtos/ContentVersionCultureVariationDto.cs index a4e51b913e..27107b0afc 100644 --- a/src/Umbraco.Core/Persistence/Dtos/ContentVersionCultureVariationDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/ContentVersionCultureVariationDto.cs @@ -42,7 +42,8 @@ namespace Umbraco.Core.Persistence.Dtos [NullSetting(NullSetting = NullSettings.Null)] public int? PublishedUserId { get => _publishedUserId == 0 ? null : _publishedUserId; set => _publishedUserId = value; } //return null if zero - [Column("edited")] - public bool Edited { get; set; } + // fixme: I've commented this out, it's never used, need to review + //[Column("edited")] + //public bool Edited { get; set; } } } diff --git a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs index 0d3faa0d68..61f46683d8 100644 --- a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs +++ b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs @@ -76,26 +76,7 @@ namespace Umbraco.Core.Persistence var (s, a) = sql.SqlContext.Visit(predicate, alias); return sql.Where(s, a); } - - /// - /// Appends an AND clause to a WHERE Sql statement. - /// - /// The type of the Dto. - /// The Sql statement. - /// A predicate to transform and append to the Sql statement. - /// An optional alias for the table. - /// The Sql statement. - /// - /// Chaining .Where(...).Where(...) in NPoco works because it merges the two WHERE statements, - /// however if the first statement is not an explicit WHERE statement, chaining fails and two WHERE - /// statements appear in the resulting Sql. This allows for adding an AND clause without problems. - /// - public static Sql AndWhere(this Sql sql, Expression> predicate, string alias = null) - { - var (s, a) = sql.SqlContext.Visit(predicate, alias); - return sql.Append("AND (" + s + ")", a); - } - + /// /// Appends a WHERE clause to the Sql statement. /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index 70fca3fd34..74ecb23071 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; +using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -34,6 +35,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected IUmbracoDatabase Database => _scopeAccessor.AmbientScope.Database; protected Sql Sql() => _scopeAccessor.AmbientScope.SqlContext.Sql(); + protected ISqlSyntaxProvider SqlSyntax => _scopeAccessor.AmbientScope.SqlContext.SqlSyntax; #region Repository @@ -57,83 +59,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement //fixme - we should be able to do sql = sql.OrderBy(x => Alias(x.NodeId, "NodeId")); but we can't because the OrderBy extension don't support Alias currently sql = sql.OrderBy("NodeId"); - //IEnumerable result; - // - //if (isMedia) - //{ - // //Treat media differently for now, as an Entity it will be returned with ALL of it's properties in the AdditionalData bag! - // var pagedResult = UnitOfWork.Database.Page(pageIndex + 1, pageSize, pagedSql); - - // var ids = pagedResult.Items.Select(x => (int)x.id).InGroupsOf(2000); - // var entities = pagedResult.Items.Select(BuildEntityFromDynamic).Cast().ToList(); - - // //Now we need to merge in the property data since we need paging and we can't do this the way that the big media query was working before - // foreach (var idGroup in ids) - // { - // var propSql = GetPropertySql(Constants.ObjectTypes.Media) - // .WhereIn(x => x.NodeId, idGroup) - // .OrderBy(x => x.NodeId); - - // //This does NOT fetch all data into memory in a list, this will read - // // over the records as a data reader, this is much better for performance and memory, - // // but it means that during the reading of this data set, nothing else can be read - // // from SQL server otherwise we'll get an exception. - // var allPropertyData = UnitOfWork.Database.Query(propSql); - - // //keep track of the current property data item being enumerated - // var propertyDataSetEnumerator = allPropertyData.GetEnumerator(); - // var hasCurrent = false; // initially there is no enumerator.Current - - // try - // { - // //This must be sorted by node id (which is done by SQL) because this is how we are sorting the query to lookup property types above, - // // which allows us to more efficiently iterate over the large data set of property values. - // foreach (var entity in entities) - // { - // // assemble the dtos for this def - // // use the available enumerator.Current if any else move to next - // while (hasCurrent || propertyDataSetEnumerator.MoveNext()) - // { - // if (propertyDataSetEnumerator.Current.nodeId == entity.Id) - // { - // hasCurrent = false; // enumerator.Current is not available - - // //the property data goes into the additional data - // entity.AdditionalData[propertyDataSetEnumerator.Current.propertyTypeAlias] = new UmbracoEntity.EntityProperty - // { - // PropertyEditorAlias = propertyDataSetEnumerator.Current.propertyEditorAlias, - // Value = StringExtensions.IsNullOrWhiteSpace(propertyDataSetEnumerator.Current.textValue) - // ? propertyDataSetEnumerator.Current.varcharValue - // : StringExtensions.ConvertToJsonIfPossible(propertyDataSetEnumerator.Current.textValue) - // }; - // } - // else - // { - // hasCurrent = true; // enumerator.Current is available for another def - // break; // no more propertyDataDto for this def - // } - // } - // } - // } - // finally - // { - // propertyDataSetEnumerator.Dispose(); - // } - // } - - // result = entities; - //} - //else - //{ - // var pagedResult = UnitOfWork.Database.Page(pageIndex + 1, pageSize, pagedSql); - // result = pagedResult.Items.Select(BuildEntityFromDynamic).Cast().ToList(); - //} var page = Database.Page(pageIndex + 1, pageSize, sql); 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 (isContent) + BuildVariantInfo(entities); if (isMedia) BuildProperties(entities, dtos); @@ -158,7 +90,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement ddto => ddto.VariationInfo, ddto => ddto.VersionId, sql); - return dtos.Count == 0 ? null : BuildDocumentEntity(dtos[0]); + + return dtos.Count == 0 ? null : BuildVariantInfo(BuildDocumentEntity(dtos[0]))[0]; } var dto = Database.FirstOrDefault(sql); @@ -220,9 +153,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement dto => dto.VariationInfo, dto => dto.VersionId, sql); + return cdtos.Count == 0 ? Enumerable.Empty() - : cdtos.Select(BuildDocumentEntity).ToArray(); + : BuildVariantInfo(cdtos.Select(BuildDocumentEntity).ToArray()).ToList(); } var dtos = Database.Fetch(sql); @@ -323,7 +257,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private void BuildProperties(EntitySlim[] entities, List dtos) { - var versionIds = dtos.Select(x => x.VersionId).Distinct().ToArray(); + var versionIds = dtos.Select(x => x.VersionId).Distinct().ToList(); var pdtos = Database.FetchByGroups(versionIds, 2000, GetPropertyData); var xentity = entities.ToDictionary(x => x.Id, x => x); // nodeId -> entity @@ -346,10 +280,54 @@ namespace Umbraco.Core.Persistence.Repositories.Implement entity.AdditionalData[pdto.PropertyTypeDto.Alias] = new EntitySlim.PropertySlim(pdto.PropertyTypeDto.DataTypeDto.EditorAlias, value); } + private void BuildVariantInfo(EntitySlim[] entities) + { + BuildVariantInfo((DocumentEntitySlim[])entities); + } + + private DocumentEntitySlim[] BuildVariantInfo(params DocumentEntitySlim[] entities) + { + if (entities.Any(x => x.Variations.VariesByCulture())) + { + //each EntitySlim at this stage is an DocumentEntitySlim + + var dtos = Database.FetchByGroups(entities.Select(x => x.Id), 2000, GetVariantPublishedInfo) + .GroupBy(x => x.NodeId) + .ToDictionary(x => x.Key, x => (IEnumerable)x); + + foreach (var e in entities.OfType().Where(x => x.Variations.VariesByCulture()).OrderBy(x => x.Id)) + { + //fixme: how do i get this info? Seems that requires another query since that is how I think it's done in the DocumentRepository + //e.EditedCultures = + e.PublishedCultures = dtos[e.Id].Where(x => x.VersionPublished).Select(x => x.IsoCode).Distinct().ToList(); + } + } + + return entities; + } + #endregion #region Sql + private Sql GetVariantPublishedInfo(IEnumerable ids) + { + var sql = Sql(); + sql + .Select(x => x.NodeId, x => Alias(x.Id, "versionId"), x => Alias(x.Current, "versionCurrent")) + .AndSelect(x => Alias(x.Id, "versionCultureId")) + .AndSelect(x => x.IsoCode) + .AndSelect(x => Alias(x.Published, "versionPublished")) + .From() + .InnerJoin().On(x => x.NodeId, x => x.NodeId) + .InnerJoin().On(x => x.Id, x => x.VersionId) + .InnerJoin().On(x => x.Id, x => x.Id) + .InnerJoin().On(x => x.LanguageId, x => x.Id) + .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document) + .WhereIn(x => x.NodeId, ids) + .Where($"{SqlSyntax.GetFieldName(x => x.Current)} = 1 OR {SqlSyntax.GetFieldName(x => x.Published)} = 1"); + return sql; + } // gets the full sql for a given object type and a given unique id protected Sql GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectType, Guid uniqueId) { @@ -371,24 +349,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return AddGroupBy(isContent, isMedia, sql); } - // fixme kill this nonsense - //// gets the SELECT + FROM + WHERE sql - //// to get all property data for all items of the specified object type - //private Sql GetPropertySql(Guid objectType) - //{ - // return Sql() - // .Select(x => x.VersionId, x => x.TextValue, x => x.VarcharValue) - // .AndSelect(x => x.NodeId) - // .AndSelect(x => x.PropertyEditorAlias) - // .AndSelect(x => Alias(x.Alias, "propertyTypeAlias")) - // .From() - // .InnerJoin().On((left, right) => left.VersionId == right.Id) - // .InnerJoin().On((left, right) => left.NodeId == right.NodeId) - // .InnerJoin().On(dto => dto.PropertyTypeId, dto => dto.Id) - // .InnerJoin().On(dto => dto.DataTypeId, dto => dto.DataTypeId) - // .Where(x => x.NodeObjectType == objectType); - //} - private Sql GetPropertyData(int versionId) { return Sql() @@ -410,89 +370,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .OrderBy(x => x.VersionId); } - // fixme - wtf is this? - //private Sql GetFullSqlForMedia(Sql entitySql, Action> filter = null) - //{ - // //this will add any varcharValue property to the output which can be added to the additional properties - - // var sql = GetPropertySql(Constants.ObjectTypes.Media); - - // filter?.Invoke(sql); - - // // We're going to create a query to query against the entity SQL - // // because we cannot group by nText columns and we have a COUNT in the entitySql we cannot simply left join - // // the entitySql query, we have to join the wrapped query to get the ntext in the result - - // var wrappedSql = Sql() - // .Append("SELECT * FROM (") - // .Append(entitySql) - // .Append(") tmpTbl LEFT JOIN (") - // .Append(sql) - // .Append(") as property ON id = property.nodeId") - // .OrderBy("sortOrder, id"); - - // return wrappedSql; - //} - - - /// - /// The DTO used to fetch results for a content item with its variation info - /// - private class ContentEntityDto : BaseDto - { - public ContentVariation Variations { get; set; } - - [ResultColumn, Reference(ReferenceType.Many)] - public List VariationInfo { get; set; } - - public bool Published { get; set; } - public bool Edited { 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; } - [Column("versionCultureEdited")] - public bool Edited { get; set; } - } - - // ReSharper disable once ClassNeverInstantiated.Local - /// - /// the DTO corresponding to fields selected by GetBase - /// - private class BaseDto - { - // ReSharper disable UnusedAutoPropertyAccessor.Local - // ReSharper disable UnusedMember.Local - public int NodeId { get; set; } - public bool Trashed { get; set; } - public int ParentId { get; set; } - public int? UserId { get; set; } - public int Level { get; set; } - public string Path { get; set; } - public int SortOrder { get; set; } - public Guid UniqueId { get; set; } - public string Text { get; set; } - public Guid NodeObjectType { get; set; } - public DateTime CreateDate { get; set; } - public int Children { get; set; } - public int VersionId { get; set; } - public string Alias { get; set; } - public string Icon { get; set; } - public string Thumbnail { get; set; } - public bool IsContainer { get; set; } - - // ReSharper restore UnusedAutoPropertyAccessor.Local - // ReSharper restore UnusedMember.Local - } + // gets the base SELECT + FROM [+ filter] sql // always from the 'current' content version @@ -524,8 +402,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .AndSelect( x => Alias(x.Id, "versionCultureId"), x => Alias(x.LanguageId, "versionCultureLangId"), - x => Alias(x.Name, "versionCultureName"), - x => Alias(x.Edited, "versionCultureEdited")); + x => Alias(x.Name, "versionCultureName")); } } @@ -537,7 +414,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement sql .InnerJoin().On((left, right) => left.NodeId == right.NodeId && right.Current) .InnerJoin().On((left, right) => left.NodeId == right.NodeId) - .LeftJoin().On((left, right) => left.ContentTypeId == right.NodeId); + .InnerJoin().On((left, right) => left.ContentTypeId == right.NodeId); } if (isContent) @@ -652,184 +529,73 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public string TextValue { get; set; } } - // fixme kill + /// - /// This is a special relator in that it is not returning a DTO but a real resolved entity and that it accepts - /// a dynamic instance. + /// The DTO used to fetch results for a content item with its variation info /// - /// - /// We're doing this because when we query the db, we want to use dynamic so that it returns all available fields not just the ones - /// defined on the entity so we can them to additional data - /// - //internal class UmbracoEntityRelator - //{ - // internal UmbracoEntity Current; + private class ContentEntityDto : BaseDto + { + public ContentVariation Variations { get; set; } - // public IEnumerable MapAll(IEnumerable input) - // { - // UmbracoEntity entity; + [ResultColumn, Reference(ReferenceType.Many)] + public List VariationInfo { get; set; } - // foreach (var x in input) - // { - // entity = Map(x); - // if (entity != null) yield return entity; - // } + public bool Published { get; set; } + public bool Edited { get; set; } + } - // entity = Map((dynamic) null); - // if (entity != null) yield return entity; - // } + private class VariationPublishInfoDto + { + public int NodeId { get; set; } + public int VersionId { get; set; } + public bool VersionCurrent { get; set; } + public string IsoCode { get; set; } + public int VersionCultureId { get; set; } + public bool VersionPublished { get; set; } + } - // // must be called one last time with null in order to return the last one! - // public UmbracoEntity Map(dynamic a) - // { - // // Terminating call. Since we can return null from this function - // // we need to be ready for NPoco to callback later with null - // // parameters - // if (a == null) - // return Current; + /// + /// 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; } + } - // string pPropertyEditorAlias = a.propertyEditorAlias; - // var pExists = pPropertyEditorAlias != null; - // string pPropertyAlias = a.propertyTypeAlias; - // string pTextValue = a.textValue; - // string pNVarcharValue = a.varcharValue; - - // // Is this the same UmbracoEntity as the current one we're processing - // if (Current != null && Current.Key == a.uniqueID) - // { - // if (pExists && pPropertyAlias.IsNullOrWhiteSpace() == false) - // { - // // Add this UmbracoProperty to the current additional data - // Current.AdditionalData[pPropertyAlias] = new UmbracoEntity.EntityProperty - // { - // PropertyEditorAlias = pPropertyEditorAlias, - // Value = pTextValue.IsNullOrWhiteSpace() - // ? pNVarcharValue - // : pTextValue.ConvertToJsonIfPossible() - // }; - // } - - // // Return null to indicate we're not done with this UmbracoEntity yet - // return null; - // } - - // // This is a different UmbracoEntity to the current one, or this is the - // // first time through and we don't have a Tab yet - - // // Save the current UmbracoEntityDto - // var prev = Current; - - // // Setup the new current UmbracoEntity - - // Current = BuildEntityFromDynamic(a); - - // if (pExists && pPropertyAlias.IsNullOrWhiteSpace() == false) - // { - // //add the property/create the prop list if null - // Current.AdditionalData[pPropertyAlias] = new UmbracoEntity.EntityProperty - // { - // PropertyEditorAlias = pPropertyEditorAlias, - // Value = pTextValue.IsNullOrWhiteSpace() - // ? pNVarcharValue - // : pTextValue.ConvertToJsonIfPossible() - // }; - // } - - // // Return the now populated previous UmbracoEntity (or null if first time through) - // return prev; - // } - //} - - // fixme need to review what's below - // comes from 7.6, similar to what's in VersionableRepositoryBase - // not sure it really makes sense... - - //private class EntityDefinitionCollection : KeyedCollection - //{ - // protected override int GetKeyForItem(EntityDefinition item) - // { - // return item.Id; - // } - - // /// - // /// if this key already exists if it does then we need to check - // /// if the existing item is 'older' than the new item and if that is the case we'll replace the older one - // /// - // /// - // /// - // public bool AddOrUpdate(EntityDefinition item) - // { - // if (Dictionary == null) - // { - // Add(item); - // return true; - // } - - // var key = GetKeyForItem(item); - // if (TryGetValue(key, out EntityDefinition found)) - // { - // //it already exists and it's older so we need to replace it - // if (item.VersionId > found.VersionId) - // { - // var currIndex = Items.IndexOf(found); - // if (currIndex == -1) - // throw new IndexOutOfRangeException("Could not find the item in the list: " + found.Id); - - // //replace the current one with the newer one - // SetItem(currIndex, item); - // return true; - // } - // //could not add or update - // return false; - // } - - // Add(item); - // return true; - // } - - // private bool TryGetValue(int key, out EntityDefinition val) - // { - // if (Dictionary != null) return Dictionary.TryGetValue(key, out val); - - // val = null; - // return false; - // } - //} - - // fixme wtf is this, why dynamics here, this is horrible !! - //private class EntityDefinition - //{ - // private readonly dynamic _entity; - // private readonly bool _isContent; - // private readonly bool _isMedia; - - // public EntityDefinition(dynamic entity, bool isContent, bool isMedia) - // { - // _entity = entity; - // _isContent = isContent; - // _isMedia = isMedia; - // } - - // public IUmbracoEntity BuildFromDynamic() - // { - // return BuildEntityFromDynamic(_entity); - // } - - // public int Id => _entity.id; - - // public int VersionId - // { - // get - // { - // if (_isContent || _isMedia) - // { - // return _entity.versionId; - // } - // return _entity.id; - // } - // } - //} + // ReSharper disable once ClassNeverInstantiated.Local + /// + /// the DTO corresponding to fields selected by GetBase + /// + private class BaseDto + { + // ReSharper disable UnusedAutoPropertyAccessor.Local + // ReSharper disable UnusedMember.Local + public int NodeId { get; set; } + public bool Trashed { get; set; } + public int ParentId { get; set; } + public int? UserId { get; set; } + public int Level { get; set; } + public string Path { get; set; } + public int SortOrder { get; set; } + public Guid UniqueId { get; set; } + public string Text { get; set; } + public Guid NodeObjectType { get; set; } + public DateTime CreateDate { get; set; } + public int Children { get; set; } + public int VersionId { get; set; } + public string Alias { get; set; } + public string Icon { get; set; } + public string Thumbnail { get; set; } + public bool IsContainer { get; set; } + // ReSharper restore UnusedAutoPropertyAccessor.Local + // ReSharper restore UnusedMember.Local + } #endregion #region Factory @@ -914,21 +680,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement //fill in the variant info if (dto.Variations.VariesByCulture() && dto.VariationInfo != null && dto.VariationInfo.Count > 0) { + //fixme: Currently require making a 2nd query to fill in publish status information for variants + var variantInfo = new Dictionary(StringComparer.InvariantCultureIgnoreCase); foreach (var info in dto.VariationInfo) { var isoCode = _langRepository.GetIsoCodeById(info.LanguageId); if (isoCode != null) variantInfo[isoCode] = info.Name; - - if (dto.Published) - { - publishedCultures.Add(isoCode); - if (info.Edited) - { - editedCultures.Add(isoCode); - } - } + } entity.CultureNames = variantInfo; entity.Variations = dto.Variations; diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 33a347d3d4..878a277177 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -47,10 +47,10 @@ namespace Umbraco.Web.Trees /// protected override TreeNode GetSingleTreeNode(IEntitySlim entity, string parentId, FormDataCollection queryStrings) { - var langId = queryStrings?["culture"]; + var culture = queryStrings?["culture"]; var allowedUserOptions = GetAllowedUserMenuItemsForNode(entity); - if (CanUserAccessNode(entity, allowedUserOptions, langId)) + if (CanUserAccessNode(entity, allowedUserOptions, culture)) { //Special check to see if it ia a container, if so then we'll hide children. var isContainer = entity.IsContainer; // && (queryStrings.Get("isDialog") != "true"); @@ -74,11 +74,21 @@ namespace Umbraco.Web.Trees { var documentEntity = (IDocumentEntitySlim) entity; - //fixme we need these statuses per variant but to do that we need to fix the issues listed in IDocumentEntitySlim - if (!documentEntity.Published) - node.SetNotPublishedStyle(); - //if (documentEntity.Edited) - // node.SetHasUnpublishedVersionStyle(); + if (!documentEntity.Variations.VariesByCulture()) + { + if (!documentEntity.Published) + node.SetNotPublishedStyle(); + if (documentEntity.Edited) + node.SetHasUnpublishedVersionStyle(); + } + else + { + if (!culture.IsNullOrWhiteSpace()) + { + if (!documentEntity.PublishedCultures.Contains(culture)) + node.SetNotPublishedStyle(); + } + } node.AdditionalData.Add("contentType", documentEntity.ContentTypeAlias); }