Merge pull request #3052 from umbraco/temp8-IDocumentEntity-updates
IDocumentEntity updates
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,4 +20,4 @@
|
||||
/// </summary>
|
||||
string ContentTypeThumbnail { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)))
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user