Merge pull request #3052 from umbraco/temp8-IDocumentEntity-updates

IDocumentEntity updates
This commit is contained in:
Shannon Deminick
2018-09-27 19:41:53 +10:00
committed by GitHub
21 changed files with 463 additions and 474 deletions

View File

@@ -118,6 +118,7 @@ namespace Umbraco.Core.Migrations.Upgrade
Chain<RenameTrueFalseField>("{517CE9EA-36D7-472A-BF4B-A0D6FB1B8F89}"); // from 7.12.0
Chain<SetDefaultTagsStorageType>("{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}"); // from 7.12.0
Chain<UpdateDefaultMandatoryLanguage>("{2C87AA47-D1BC-4ECB-8A73-2D8D1046C27F}");
Chain<RefactorVariantsModel>("{B19BF0F2-E1C6-4AEB-A146-BC559D97A2C6}");
//FINAL

View File

@@ -0,0 +1,79 @@
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos;
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
{
public class RefactorVariantsModel : MigrationBase
{
public RefactorVariantsModel(IMigrationContext context)
: base(context)
{ }
public override void Migrate()
{
Delete.Column("edited").FromTable(Constants.DatabaseSchema.Tables.ContentVersionCultureVariation).Do();
// add available column
AddColumn<DocumentCultureVariationDto>("available", out var sqls);
// so far, only those cultures that were available had records in the table
Update.Table(DocumentCultureVariationDto.TableName).Set(new { available = true }).AllRows().Do();
foreach (var sql in sqls) Execute.Sql(sql).Do();
// add published column
AddColumn<DocumentCultureVariationDto>("published", out sqls);
// make it false by default
Update.Table(DocumentCultureVariationDto.TableName).Set(new { published = false }).AllRows().Do();
// now figure out whether these available cultures are published, too
var getPublished = Sql()
.Select<NodeDto>(x => x.NodeId)
.AndSelect<ContentVersionCultureVariationDto>(x => x.LanguageId)
.From<NodeDto>()
.InnerJoin<ContentVersionDto>().On<NodeDto, ContentVersionDto>((node, cv) => node.NodeId == cv.NodeId)
.InnerJoin<DocumentVersionDto>().On<ContentVersionDto, DocumentVersionDto>((cv, dv) => cv.Id == dv.Id && dv.Published)
.InnerJoin<ContentVersionCultureVariationDto>().On<ContentVersionDto, ContentVersionCultureVariationDto>((cv, ccv) => cv.Id == ccv.VersionId);
foreach (var dto in Database.Fetch<TempDto>(getPublished))
Database.Execute(Sql()
.Update<DocumentCultureVariationDto>(u => u.Set(x => x.Published, true))
.Where<DocumentCultureVariationDto>(x => x.NodeId == dto.NodeId && x.LanguageId == dto.LanguageId));
foreach (var sql in sqls) Execute.Sql(sql).Do();
// so far, it was kinda impossible to make a culture unavailable again,
// so we *should* not have anything published but not available - ignore
// add name column
AddColumn<DocumentCultureVariationDto>("name");
// so far, every record in the table mapped to an available culture
var getNames = Sql()
.Select<NodeDto>(x => x.NodeId)
.AndSelect<ContentVersionCultureVariationDto>(x => x.LanguageId, x => x.Name)
.From<NodeDto>()
.InnerJoin<ContentVersionDto>().On<NodeDto, ContentVersionDto>((node, cv) => node.NodeId == cv.NodeId && cv.Current)
.InnerJoin<ContentVersionCultureVariationDto>().On<ContentVersionDto, ContentVersionCultureVariationDto>((cv, ccv) => cv.Id == ccv.VersionId);
foreach (var dto in Database.Fetch<TempDto>(getNames))
Database.Execute(Sql()
.Update<DocumentCultureVariationDto>(u => u.Set(x => x.Name, dto.Name))
.Where<DocumentCultureVariationDto>(x => x.NodeId == dto.NodeId && x.LanguageId == dto.LanguageId));
}
// ReSharper disable once ClassNeverInstantiated.Local
// ReSharper disable UnusedAutoPropertyAccessor.Local
private class TempDto
{
public int NodeId { get; set; }
public int LanguageId { get; set; }
public string Name { get; set; }
}
// ReSharper restore UnusedAutoPropertyAccessor.Local
}
}

View File

@@ -269,7 +269,7 @@ namespace Umbraco.Core.Models
if (_publishInfos == null)
_publishInfos = new Dictionary<string, (string Name, DateTime Date)>(StringComparer.OrdinalIgnoreCase);
_publishInfos[culture] = (name, date);
_publishInfos[culture.ToLowerInvariant()] = (name, date);
}
private void ClearPublishInfos()
@@ -294,7 +294,7 @@ namespace Umbraco.Core.Models
throw new ArgumentNullOrEmptyException(nameof(culture));
if (_editedCultures == null)
_editedCultures = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
_editedCultures.Add(culture);
_editedCultures.Add(culture.ToLowerInvariant());
}
// sets all publish edited

View File

@@ -167,7 +167,7 @@ namespace Umbraco.Core.Models
}
/// <inheritdoc />
public DateTime? GetCultureDate(string culture)
public DateTime? GetUpdateDate(string culture)
{
if (culture.IsNullOrWhiteSpace()) return null;
if (!ContentTypeBase.VariesByCulture()) return null;
@@ -202,6 +202,12 @@ namespace Umbraco.Core.Models
}
}
internal void TouchCulture(string culture)
{
if (ContentTypeBase.VariesByCulture() && _cultureInfos != null && _cultureInfos.TryGetValue(culture, out var infos))
_cultureInfos[culture] = (infos.Name, DateTime.Now);
}
protected void ClearCultureInfos()
{
_cultureInfos = null;

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
namespace Umbraco.Core.Models.Entities
{
@@ -8,13 +9,32 @@ namespace Umbraco.Core.Models.Entities
public class DocumentEntitySlim : ContentEntitySlim, IDocumentEntitySlim
{
private static readonly IReadOnlyDictionary<string, string> Empty = new Dictionary<string, string>();
private IReadOnlyDictionary<string, string> _cultureNames;
private IEnumerable<string> _publishedCultures;
private IEnumerable<string> _editedCultures;
/// <inheritdoc />
public IReadOnlyDictionary<string, string> CultureNames
{
get => _cultureNames ?? Empty;
set => _cultureNames = value;
}
/// <inheritdoc />
public IEnumerable<string> PublishedCultures
{
get => _publishedCultures ?? Enumerable.Empty<string>();
set => _publishedCultures = value;
}
/// <inheritdoc />
public IEnumerable<string> EditedCultures
{
get => _editedCultures ?? Enumerable.Empty<string>();
set => _editedCultures = value;
}
public ContentVariation Variations { get; set; }
/// <inheritdoc />
@@ -22,5 +42,6 @@ namespace Umbraco.Core.Models.Entities
/// <inheritdoc />
public bool Edited { get; set; }
}
}

View File

@@ -20,4 +20,4 @@
/// </summary>
string ContentTypeThumbnail { get; }
}
}
}

View File

@@ -7,26 +7,35 @@ namespace Umbraco.Core.Models.Entities
/// </summary>
public interface IDocumentEntitySlim : IContentEntitySlim
{
//fixme we need to supply more information than this and change this property name. This will need to include Published/Editor per variation since we need this information for the tree
/// <summary>
/// Gets the variant name for each culture
/// </summary>
IReadOnlyDictionary<string, string> CultureNames { get; }
/// <summary>
/// Gets the published cultures.
/// </summary>
IEnumerable<string> PublishedCultures { get; }
/// <summary>
/// Gets the edited cultures.
/// </summary>
IEnumerable<string> EditedCultures { get; }
/// <summary>
/// Gets the content variation of the content type.
/// </summary>
ContentVariation Variations { get; }
/// <summary>
/// At least one variation is published
/// Gets a value indicating whether the content is published.
/// </summary>
/// <remarks>
/// If the document is invariant, this simply means there is a published version
/// </remarks>
bool Published { get; set; }
bool Published { get; }
/// <summary>
/// At least one variation has pending changes
/// Gets a value indicating whether the content has been edited.
/// </summary>
/// <remarks>
/// If the document is invariant, this simply means there is pending changes
/// </remarks>
bool Edited { get; set; }
bool Edited { get; }
}
}

View File

@@ -80,13 +80,13 @@ namespace Umbraco.Core.Models
bool IsCultureAvailable(string culture);
/// <summary>
/// Gets the date a culture was created.
/// Gets the date a culture was updated.
/// </summary>
/// <remarks>
/// <para>When <paramref name="culture" /> is <c>null</c>, returns <c>null</c>.</para>
/// <para>If the specified culture is not available, returns <c>null</c>.</para>
/// </remarks>
DateTime? GetCultureDate(string culture);
DateTime? GetUpdateDate(string culture);
/// <summary>
/// List of properties, which make up all the data available for this Content object

View File

@@ -10,7 +10,7 @@ namespace Umbraco.Core.Persistence.Dtos
internal class ContentVersionCultureVariationDto
{
public const string TableName = Constants.DatabaseSchema.Tables.ContentVersionCultureVariation;
private int? _publishedUserId;
private int? _updateUserId;
[Column("id")]
[PrimaryKeyColumn]
@@ -33,16 +33,12 @@ namespace Umbraco.Core.Persistence.Dtos
[Column("name")]
public string Name { get; set; }
[Column("date")]
public DateTime Date { get; set; }
[Column("date")] // fixme: db rename to 'updateDate'
public DateTime UpdateDate { get; set; }
// fixme want?
[Column("availableUserId")]
[Column("availableUserId")] // fixme: db rename to 'updateDate'
[ForeignKey(typeof(UserDto))]
[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; }
public int? UpdateUserId { get => _updateUserId == 0 ? null : _updateUserId; set => _updateUserId = value; } //return null if zero
}
}

View File

@@ -21,11 +21,11 @@ namespace Umbraco.Core.Persistence.Dtos
[ForeignKey(typeof(ContentDto))]
public int NodeId { get; set; }
[Column("versionDate")]
[Column("versionDate")] // fixme: db rename to 'updateDate'
[Constraint(Default = SystemMethods.CurrentDateTime)]
public DateTime VersionDate { get; set; }
[Column("userId")]
[Column("userId")] // fixme: db rename to 'updateUserId'
[ForeignKey(typeof(UserDto))]
[NullSetting(NullSetting = NullSettings.Null)]
public int? UserId { get => _userId == 0 ? null : _userId; set => _userId = value; } //return null if zero

View File

@@ -28,7 +28,25 @@ namespace Umbraco.Core.Persistence.Dtos
[Ignore]
public string Culture { get; set; }
// authority on whether a culture has been edited
[Column("edited")]
public bool Edited { get; set; }
// de-normalized for perfs
// (means there is a current content version culture variation for the language)
[Column("available")]
public bool Available { get; set; }
// de-normalized for perfs
// (means there is a published content version culture variation for the language)
[Column("published")]
public bool Published { get; set; }
// de-normalized for perfs
// (when available, copies name from current content version culture variation for the language)
// (otherwise, it's the published one, 'cos we need to have one)
[Column("name")]
[NullSetting(NullSetting = NullSettings.Null)]
public string Name { get; set; }
}
}

View File

@@ -45,7 +45,7 @@ namespace Umbraco.Core.Persistence.Dtos
[Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_Trashed")]
public bool Trashed { get; set; }
[Column("nodeUser")] // fixme dbfix rename userId
[Column("nodeUser")] // fixme: db rename to 'createUserId'
[ForeignKey(typeof(UserDto))]
[NullSetting(NullSetting = NullSettings.Null)]
public int? UserId { get => _userId == 0 ? null : _userId; set => _userId = value; } //return null if zero
@@ -54,10 +54,10 @@ namespace Umbraco.Core.Persistence.Dtos
[NullSetting(NullSetting = NullSettings.Null)]
public string Text { get; set; }
[Column("nodeObjectType")] // fixme dbfix rename objectType
[Column("nodeObjectType")] // fixme: db rename to 'objectType'
[NullSetting(NullSetting = NullSettings.Null)]
[Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_ObjectType")]
public Guid? NodeObjectType { get; set; } // fixme dbfix rename ObjectType
public Guid? NodeObjectType { get; set; }
[Column("createDate")]
[Constraint(Default = SystemMethods.CurrentDateTime)]

View File

@@ -76,26 +76,7 @@ namespace Umbraco.Core.Persistence
var (s, a) = sql.SqlContext.Visit(predicate, alias);
return sql.Where(s, a);
}
/// <summary>
/// Appends an AND clause to a WHERE Sql statement.
/// </summary>
/// <typeparam name="TDto">The type of the Dto.</typeparam>
/// <param name="sql">The Sql statement.</param>
/// <param name="predicate">A predicate to transform and append to the Sql statement.</param>
/// <param name="alias">An optional alias for the table.</param>
/// <returns>The Sql statement.</returns>
/// <remarks>
/// <para>Chaining <c>.Where(...).Where(...)</c> 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.</para>
/// </remarks>
public static Sql<ISqlContext> AndWhere<TDto>(this Sql<ISqlContext> sql, Expression<Func<TDto, bool>> predicate, string alias = null)
{
var (s, a) = sql.SqlContext.Visit(predicate, alias);
return sql.Append("AND (" + s + ")", a);
}
/// <summary>
/// Appends a WHERE clause to the Sql statement.
/// </summary>
@@ -411,6 +392,23 @@ namespace Umbraco.Core.Persistence
#region Joins
/// <summary>
/// Appends a CROSS JOIN clause to the Sql statement.
/// </summary>
/// <typeparam name="TDto">The type of the Dto.</typeparam>
/// <param name="sql">The Sql statement.</param>
/// <param name="alias">An optional alias for the joined table.</param>
/// <returns>The Sql statement.</returns>
public static Sql<ISqlContext> CrossJoin<TDto>(this Sql<ISqlContext> sql, string alias = null)
{
var type = typeof(TDto);
var tableName = type.GetTableName();
var join = sql.SqlContext.SqlSyntax.GetQuotedTableName(tableName);
if (alias != null) join += " " + sql.SqlContext.SqlSyntax.GetQuotedTableName(alias);
return sql.Append("CROSS JOIN " + join);
}
/// <summary>
/// Appends an INNER JOIN clause to the Sql statement.
/// </summary>
@@ -552,6 +550,25 @@ namespace Umbraco.Core.Persistence
return sqlJoin.On(onExpression, expresionist.GetSqlParameters());
}
/// <summary>
/// Appends an ON clause to a SqlJoin statement.
/// </summary>
/// <typeparam name="TDto1">The type of Dto 1.</typeparam>
/// <typeparam name="TDto2">The type of Dto 2.</typeparam>
/// <typeparam name="TDto3">The type of Dto 3.</typeparam>
/// <param name="sqlJoin">The SqlJoin statement.</param>
/// <param name="predicate">A predicate to transform and use as the ON clause body.</param>
/// <param name="aliasLeft">An optional alias for Dto 1 table.</param>
/// <param name="aliasRight">An optional alias for Dto 2 table.</param>
/// <param name="aliasOther">An optional alias for Dto 3 table.</param>
/// <returns>The Sql statement.</returns>
public static Sql<ISqlContext> On<TDto1, TDto2, TDto3>(this Sql<ISqlContext>.SqlJoinClause<ISqlContext> sqlJoin, Expression<Func<TDto1, TDto2, TDto3, bool>> predicate, string aliasLeft = null, string aliasRight = null, string aliasOther = null)
{
var expresionist = new PocoToSqlExpressionVisitor<TDto1, TDto2, TDto3>(sqlJoin.SqlContext, aliasLeft, aliasRight, aliasOther);
var onExpression = expresionist.Visit(predicate);
return sqlJoin.On(onExpression, expresionist.GetSqlParameters());
}
#endregion
#region Select

View File

@@ -167,4 +167,93 @@ namespace Umbraco.Core.Persistence.Querying
}
}
/// <summary>
/// Represents an expression tree parser used to turn strongly typed expressions into SQL statements.
/// </summary>
/// <typeparam name="TDto1">The type of DTO 1.</typeparam>
/// <typeparam name="TDto2">The type of DTO 2.</typeparam>
/// <typeparam name="TDto3">The type of DTO 3.</typeparam>
/// <remarks>This visitor is stateful and cannot be reused.</remarks>
internal class PocoToSqlExpressionVisitor<TDto1, TDto2, TDto3> : ExpressionVisitorBase
{
private readonly PocoData _pocoData1, _pocoData2, _pocoData3;
private readonly string _alias1, _alias2, _alias3;
private string _parameterName1, _parameterName2, _parameterName3;
public PocoToSqlExpressionVisitor(ISqlContext sqlContext, string alias1, string alias2, string alias3)
: base(sqlContext.SqlSyntax)
{
_pocoData1 = sqlContext.PocoDataFactory.ForType(typeof(TDto1));
_pocoData2 = sqlContext.PocoDataFactory.ForType(typeof(TDto2));
_pocoData3 = sqlContext.PocoDataFactory.ForType(typeof(TDto3));
_alias1 = alias1;
_alias2 = alias2;
_alias3 = alias3;
}
protected override string VisitLambda(LambdaExpression lambda)
{
if (lambda.Parameters.Count == 3)
{
_parameterName1 = lambda.Parameters[0].Name;
_parameterName2 = lambda.Parameters[1].Name;
_parameterName3 = lambda.Parameters[2].Name;
}
else if (lambda.Parameters.Count == 2)
{
_parameterName1 = lambda.Parameters[0].Name;
_parameterName2 = lambda.Parameters[1].Name;
}
else
{
_parameterName1 = _parameterName2 = null;
}
return base.VisitLambda(lambda);
}
protected override string VisitMemberAccess(MemberExpression m)
{
if (m.Expression != null)
{
if (m.Expression.NodeType == ExpressionType.Parameter)
{
var pex = (ParameterExpression)m.Expression;
if (pex.Name == _parameterName1)
return Visited ? string.Empty : GetFieldName(_pocoData1, m.Member.Name, _alias1);
if (pex.Name == _parameterName2)
return Visited ? string.Empty : GetFieldName(_pocoData2, m.Member.Name, _alias2);
if (pex.Name == _parameterName3)
return Visited ? string.Empty : GetFieldName(_pocoData3, m.Member.Name, _alias3);
}
else if (m.Expression.NodeType == ExpressionType.Convert)
{
// here: which _pd should we use?!
throw new NotSupportedException();
//return Visited ? string.Empty : GetFieldName(_pd, m.Member.Name);
}
}
var member = Expression.Convert(m, typeof(object));
var lambda = Expression.Lambda<Func<object>>(member);
var getter = lambda.Compile();
var o = getter();
SqlParameters.Add(o);
// execute if not already compiled
return Visited ? string.Empty : "@" + (SqlParameters.Count - 1);
}
protected virtual string GetFieldName(PocoData pocoData, string name, string alias)
{
var column = pocoData.Columns.FirstOrDefault(x => x.Value.MemberInfoData.Name == name);
var tableName = SqlSyntax.GetQuotedTableName(alias ?? pocoData.TableInfo.TableName);
var columnName = SqlSyntax.GetQuotedColumnName(column.Value.ColumnName);
return tableName + "." + columnName;
}
}
}

View File

@@ -8,14 +8,12 @@ using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
@@ -505,8 +503,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// names also impact 'edited'
foreach (var (culture, name) in content.CultureNames)
if (name != content.GetPublishName(culture))
{
edited = true;
(editedCultures ?? (editedCultures = new HashSet<string>(StringComparer.OrdinalIgnoreCase))).Add(culture);
// fixme - change tracking
// at the moment, we don't do any dirty tracking on property values, so we don't know whether the
// culture has just been edited or not, so we don't update its update date - that date only changes
// when the name is set, and it all works because the controller does it - but, if someone uses a
// service to change a property value and save (without setting name), the update date does not change.
}
// replace the content version variations (rather than updating)
// only need to delete for the version that existed, the new version (if any) has no property data yet
var deleteContentVariations = Sql().Delete<ContentVersionCultureVariationDto>().Where<ContentVersionCultureVariationDto>(x => x.VersionId == versionToDelete);
@@ -1033,7 +1040,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
Culture = LanguageRepository.GetIsoCodeById(dto.LanguageId),
Name = dto.Name,
Date = dto.Date
Date = dto.UpdateDate
});
}
@@ -1078,7 +1085,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
LanguageId = LanguageRepository.GetIdByIsoCode(culture) ?? throw new InvalidOperationException("Not a valid culture."),
Culture = culture,
Name = name,
Date = content.GetCultureDate(culture) ?? DateTime.MinValue // we *know* there is a value
UpdateDate = content.GetUpdateDate(culture) ?? DateTime.MinValue // we *know* there is a value
};
// if not publishing, we're just updating the 'current' (non-published) version,
@@ -1093,22 +1100,28 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
LanguageId = LanguageRepository.GetIdByIsoCode(culture) ?? throw new InvalidOperationException("Not a valid culture."),
Culture = culture,
Name = name,
Date = content.GetPublishDate(culture) ?? DateTime.MinValue // we *know* there is a value
UpdateDate = content.GetPublishDate(culture) ?? DateTime.MinValue // we *know* there is a value
};
}
private IEnumerable<DocumentCultureVariationDto> GetDocumentVariationDtos(IContent content, bool publishing, HashSet<string> editedCultures)
{
foreach (var (culture, name) in content.CultureNames)
var allCultures = content.AvailableCultures.Union(content.PublishedCultures); // union = distinct
foreach (var culture in allCultures)
yield return new DocumentCultureVariationDto
{
NodeId = content.Id,
LanguageId = LanguageRepository.GetIdByIsoCode(culture) ?? throw new InvalidOperationException("Not a valid culture."),
Culture = culture,
// if not published, always edited
// no need to check for availability: it *is* available since it is in content.CultureNames
Edited = !content.IsCulturePublished(culture) || (editedCultures != null && editedCultures.Contains(culture))
Name = content.GetCultureName(culture) ?? content.GetPublishName(culture),
// note: can't use IsCultureEdited at that point - hasn't been updated yet - see PersistUpdatedItem
Available = content.IsCultureAvailable(culture),
Published = content.IsCulturePublished(culture),
Edited = content.IsCultureAvailable(culture) &&
(!content.IsCulturePublished(culture) || (editedCultures != null && editedCultures.Contains(culture)))
};
}

View File

@@ -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<ISqlContext> Sql() => _scopeAccessor.AmbientScope.SqlContext.Sql();
protected ISqlSyntaxProvider SqlSyntax => _scopeAccessor.AmbientScope.SqlContext.SqlSyntax;
#region Repository
@@ -57,83 +59,12 @@ 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<IUmbracoEntity> 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<dynamic>(pageIndex + 1, pageSize, pagedSql);
// var ids = pagedResult.Items.Select(x => (int)x.id).InGroupsOf(2000);
// var entities = pagedResult.Items.Select(BuildEntityFromDynamic).Cast<IUmbracoEntity>().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<NodeDto>(x => x.NodeId, idGroup)
// .OrderBy<NodeDto>(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<dynamic>(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<dynamic>(pageIndex + 1, pageSize, pagedSql);
// result = pagedResult.Items.Select(BuildEntityFromDynamic).Cast<IUmbracoEntity>().ToList();
//}
var page = Database.Page<BaseDto>(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)
BuildVariants(entities.Cast<DocumentEntitySlim>());
if (isMedia)
BuildProperties(entities, dtos);
@@ -154,11 +85,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
//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<ContentEntityDto>(
ddto => ddto.VariationInfo,
ddto => ddto.VersionId,
sql);
return dtos.Count == 0 ? null : BuildDocumentEntity(dtos[0]);
var cdtos = Database.Fetch<ContentEntityDto>(sql);
return cdtos.Count == 0 ? null : BuildVariants(BuildDocumentEntity(cdtos[0]));
}
var dto = Database.FirstOrDefault<BaseDto>(sql);
@@ -216,13 +145,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
//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<ContentEntityDto>(
dto => dto.VariationInfo,
dto => dto.VersionId,
sql);
var cdtos = Database.Fetch<ContentEntityDto>(sql);
return cdtos.Count == 0
? Enumerable.Empty<IEntitySlim>()
: cdtos.Select(BuildDocumentEntity).ToArray();
: BuildVariants(cdtos.Select(BuildDocumentEntity)).ToList();
}
var dtos = Database.Fetch<BaseDto>(sql);
@@ -323,7 +250,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
private void BuildProperties(EntitySlim[] entities, List<BaseDto> dtos)
{
var versionIds = dtos.Select(x => x.VersionId).Distinct().ToArray();
var versionIds = dtos.Select(x => x.VersionId).Distinct().ToList();
var pdtos = Database.FetchByGroups<PropertyDataDto, int>(versionIds, 2000, GetPropertyData);
var xentity = entities.ToDictionary(x => x.Id, x => x); // nodeId -> entity
@@ -346,10 +273,70 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
entity.AdditionalData[pdto.PropertyTypeDto.Alias] = new EntitySlim.PropertySlim(pdto.PropertyTypeDto.DataTypeDto.EditorAlias, value);
}
private DocumentEntitySlim BuildVariants(DocumentEntitySlim entity)
=> BuildVariants(new[] { entity }).First();
private IEnumerable<DocumentEntitySlim> BuildVariants(IEnumerable<DocumentEntitySlim> entities)
{
List<DocumentEntitySlim> v = null;
var entitiesList = entities.ToList();
foreach (var e in entitiesList)
{
if (e.Variations.VariesByCulture())
(v ?? (v = new List<DocumentEntitySlim>())).Add(e);
}
if (v == null) return entitiesList;
// fetch all variant info dtos
var dtos = Database.FetchByGroups<VariantInfoDto, int>(v.Select(x => x.Id), 2000, GetVariantInfos);
// group by node id (each group contains all languages)
var xdtos = dtos.GroupBy(x => x.NodeId).ToDictionary(x => x.Key, x => x);
foreach (var e in v)
{
// since we're only iterating on entities that vary, we must have something
var edtos = xdtos[e.Id];
e.CultureNames = edtos.Where(x => x.CultureAvailable).ToDictionary(x => x.IsoCode, x => x.Name);
e.PublishedCultures = edtos.Where(x => x.CulturePublished).Select(x => x.IsoCode);
e.EditedCultures = edtos.Where(x => x.CultureAvailable && x.CultureEdited).Select(x => x.IsoCode);
}
return entitiesList;
}
#endregion
#region Sql
protected Sql<ISqlContext> GetVariantInfos(IEnumerable<int> ids)
{
return Sql()
.Select<NodeDto>(x => x.NodeId)
.AndSelect<LanguageDto>(x => x.IsoCode)
.AndSelect<DocumentDto>("doc", x => Alias(x.Published, "DocumentPublished"), x => Alias(x.Edited, "DocumentEdited"))
.AndSelect<DocumentCultureVariationDto>("dcv",
x => Alias(x.Available, "CultureAvailable"), x => Alias(x.Published, "CulturePublished"), x => Alias(x.Edited, "CultureEdited"),
x => Alias(x.Name, "Name"))
// from node x language
.From<NodeDto>()
.CrossJoin<LanguageDto>()
// join to document - always exists - indicates global document published/edited status
.InnerJoin<DocumentDto>("doc")
.On<NodeDto, DocumentDto>((node, doc) => node.NodeId == doc.NodeId, aliasRight: "doc")
// left-join do document variation - matches cultures that are *available* + indicates when *edited*
.LeftJoin<DocumentCultureVariationDto>("dcv")
.On<NodeDto, DocumentCultureVariationDto, LanguageDto>((node, dcv, lang) => node.NodeId == dcv.NodeId && lang.Id == dcv.LanguageId, aliasRight: "dcv")
// for selected nodes
.WhereIn<NodeDto>(x => x.NodeId, ids);
}
// gets the full sql for a given object type and a given unique id
protected Sql<ISqlContext> GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectType, Guid uniqueId)
{
@@ -371,24 +358,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<ISqlContext> GetPropertySql(Guid objectType)
//{
// return Sql()
// .Select<PropertyDataDto>(x => x.VersionId, x => x.TextValue, x => x.VarcharValue)
// .AndSelect<NodeDto>(x => x.NodeId)
// .AndSelect<DataTypeDto>(x => x.PropertyEditorAlias)
// .AndSelect<PropertyTypeDto>(x => Alias(x.Alias, "propertyTypeAlias"))
// .From<PropertyDataDto>()
// .InnerJoin<ContentVersionDto>().On<PropertyDataDto, ContentVersionDto>((left, right) => left.VersionId == right.Id)
// .InnerJoin<NodeDto>().On<ContentVersionDto, NodeDto>((left, right) => left.NodeId == right.NodeId)
// .InnerJoin<PropertyTypeDto>().On<PropertyDataDto, PropertyTypeDto>(dto => dto.PropertyTypeId, dto => dto.Id)
// .InnerJoin<DataTypeDto>().On<PropertyTypeDto, DataTypeDto>(dto => dto.DataTypeId, dto => dto.DataTypeId)
// .Where<NodeDto>(x => x.NodeObjectType == objectType);
//}
private Sql<ISqlContext> GetPropertyData(int versionId)
{
return Sql()
@@ -408,89 +377,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
.InnerJoin<DataTypeDto>().On<PropertyTypeDto, DataTypeDto>((left, right) => left.DataTypeId == right.NodeId)
.WhereIn<PropertyDataDto>(x => x.VersionId, versionIds)
.OrderBy<PropertyDataDto>(x => x.VersionId);
}
// fixme - wtf is this?
//private Sql<ISqlContext> GetFullSqlForMedia(Sql<ISqlContext> entitySql, Action<Sql<ISqlContext>> 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;
//}
/// <summary>
/// The DTO used to fetch results for a content item with its variation info
/// </summary>
private class ContentEntityDto : BaseDto
{
public ContentVariation Variations { get; set; }
[ResultColumn, Reference(ReferenceType.Many)]
public List<ContentEntityVariationInfoDto> VariationInfo { get; set; }
public bool Published { get; set; }
public bool Edited { get; set; }
}
/// <summary>
/// The DTO used in the 1:M result for content variation info
/// </summary>
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
/// <summary>
/// the DTO corresponding to fields selected by GetBase
/// </summary>
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
@@ -517,12 +404,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (isContent)
{
sql
.AndSelect<DocumentDto>(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<ContentVersionCultureVariationDto>(
x => Alias(x.Id, "versionCultureId"),
x => Alias(x.LanguageId, "versionCultureLangId"),
x => Alias(x.Name, "versionCultureName"));
.AndSelect<DocumentDto>(x => x.Published, x => x.Edited);
}
}
@@ -534,7 +416,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
sql
.InnerJoin<ContentVersionDto>().On<NodeDto, ContentVersionDto>((left, right) => left.NodeId == right.NodeId && right.Current)
.InnerJoin<ContentDto>().On<NodeDto, ContentDto>((left, right) => left.NodeId == right.NodeId)
.LeftJoin<ContentTypeDto>().On<ContentDto, ContentTypeDto>((left, right) => left.ContentTypeId == right.NodeId);
.InnerJoin<ContentTypeDto>().On<ContentDto, ContentTypeDto>((left, right) => left.ContentTypeId == right.NodeId);
}
if (isContent)
@@ -548,10 +430,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
sql
.LeftJoin<NodeDto>("child").On<NodeDto, NodeDto>((left, right) => left.NodeId == right.ParentId, aliasRight: "child");
if (isContent)
sql
.LeftJoin<ContentVersionCultureVariationDto>().On<ContentVersionDto, ContentVersionCultureVariationDto>((left, right) => left.Id == right.VersionId);
}
@@ -613,8 +491,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (isContent)
{
sql
.AndBy<DocumentDto>(x => x.Published, x => x.Edited)
.AndBy<ContentVersionCultureVariationDto>(x => x.Id, x => x.LanguageId, x => x.Name);
.AndBy<DocumentDto>(x => x.Published, x => x.Edited);
}
@@ -649,184 +526,60 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
public string TextValue { get; set; }
}
// fixme kill
/// <summary>
/// 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
/// </summary>
/// <remarks>
/// 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
/// </remarks>
//internal class UmbracoEntityRelator
//{
// internal UmbracoEntity Current;
private class ContentEntityDto : BaseDto
{
public ContentVariation Variations { get; set; }
// public IEnumerable<IUmbracoEntity> MapAll(IEnumerable<dynamic> input)
// {
// UmbracoEntity entity;
public bool Published { get; set; }
public bool Edited { get; set; }
}
// foreach (var x in input)
// {
// entity = Map(x);
// if (entity != null) yield return entity;
// }
public class VariantInfoDto
{
public int NodeId { get; set; }
public string IsoCode { get; set; }
public string Name { get; set; }
public bool DocumentPublished { get; set; }
public bool DocumentEdited { get; set; }
// entity = Map((dynamic) null);
// if (entity != null) yield return entity;
// }
public bool CultureAvailable { get; set; }
public bool CulturePublished { get; set; }
public bool CultureEdited { 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;
// 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<int, EntityDefinition>
//{
// protected override int GetKeyForItem(EntityDefinition item)
// {
// return item.Id;
// }
// /// <summary>
// /// 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
// /// </summary>
// /// <param name="item"></param>
// /// <returns></returns>
// 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
/// <summary>
/// the DTO corresponding to fields selected by GetBase
/// </summary>
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
@@ -879,44 +632,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
private DocumentEntitySlim BuildDocumentEntity(BaseDto dto)
{
// EntitySlim does not track changes
var entity = new DocumentEntitySlim();
BuildContentEntity(entity, dto);
if (dto is ContentEntityDto contentDto)
{
return BuildDocumentEntity(contentDto);
// fill in the invariant info
entity.Edited = contentDto.Edited;
entity.Published = contentDto.Published;
entity.Variations = contentDto.Variations;
}
// EntitySlim does not track changes
var entity = new DocumentEntitySlim();
BuildContentEntity(entity, dto);
return entity;
}
/// <summary>
/// Builds the <see cref="EntitySlim"/> from a <see cref="ContentEntityDto"/> and ensures the AdditionalData is populated with variant info
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
private DocumentEntitySlim BuildDocumentEntity(ContentEntityDto dto)
{
// EntitySlim does not track changes
var entity = new DocumentEntitySlim();
BuildContentEntity(entity, dto);
//fixme we need to set these statuses for each variant, see notes in IDocumentEntitySlim
entity.Edited = dto.Edited;
entity.Published = dto.Published;
if (dto.Variations.VariesByCulture() && dto.VariationInfo != null && dto.VariationInfo.Count > 0)
{
var variantInfo = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
foreach (var info in dto.VariationInfo)
{
var isoCode = _langRepository.GetIsoCodeById(info.LanguageId);
if (isoCode != null)
variantInfo[isoCode] = info.Name;
}
entity.CultureNames = variantInfo;
entity.Variations = dto.Variations;
}
return entity;
}

View File

@@ -359,6 +359,7 @@
<Compile Include="Migrations\Upgrade\V_8_0_0\LanguageColumns.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\PropertyEditorsMigration.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\RefactorMacroColumns.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\RefactorVariantsModel.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\SuperZero.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\TagsMigration.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\UpdateDefaultMandatoryLanguage.cs" />

View File

@@ -108,7 +108,7 @@ namespace Umbraco.Web.Models.Mapping
// if we don't have a date for a culture, it means the culture is not available, and
// hey we should probably not be mapping it, but it's too late, return a fallback date
var date = source.GetCultureDate(culture);
var date = source.GetUpdateDate(culture);
return date ?? source.UpdateDate;
}
}

View File

@@ -54,7 +54,7 @@
/// Sets the node style to show that it is has unpublished versions (but is currently published)
/// </summary>
/// <param name="treeNode"></param>
public static void SetHasUnpublishedVersionStyle(this TreeNode treeNode)
public static void SetHasPendingVersionStyle(this TreeNode treeNode)
{
if (treeNode.CssClasses.Contains("has-unpublished-version") == false)
{

View File

@@ -1196,7 +1196,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
foreach (var (culture, name) in names)
{
cultureData[culture] = new CultureVariation { Name = name, Date = content.GetCultureDate(culture) ?? DateTime.MinValue };
cultureData[culture] = new CultureVariation { Name = name, Date = content.GetUpdateDate(culture) ?? DateTime.MinValue };
}
//the dictionary that will be serialized

View File

@@ -47,10 +47,10 @@ namespace Umbraco.Web.Trees
/// <inheritdoc />
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,23 @@ 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();
else if (documentEntity.Edited)
node.SetHasPendingVersionStyle();
}
else
{
if (!culture.IsNullOrWhiteSpace())
{
if (!documentEntity.PublishedCultures.Contains(culture))
node.SetNotPublishedStyle();
else if (documentEntity.EditedCultures.Contains(culture))
node.SetHasPendingVersionStyle();
}
}
node.AdditionalData.Add("contentType", documentEntity.ContentTypeAlias);
}