diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index 09846ed6e2..0933c0cba7 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -118,6 +118,7 @@ namespace Umbraco.Core.Migrations.Upgrade Chain("{517CE9EA-36D7-472A-BF4B-A0D6FB1B8F89}"); // from 7.12.0 Chain("{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}"); // from 7.12.0 Chain("{2C87AA47-D1BC-4ECB-8A73-2D8D1046C27F}"); + Chain("{B19BF0F2-E1C6-4AEB-A146-BC559D97A2C6}"); //FINAL diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorVariantsModel.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorVariantsModel.cs new file mode 100644 index 0000000000..aa498583ff --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorVariantsModel.cs @@ -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("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("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(x => x.NodeId) + .AndSelect(x => x.LanguageId) + .From() + .InnerJoin().On((node, cv) => node.NodeId == cv.NodeId) + .InnerJoin().On((cv, dv) => cv.Id == dv.Id && dv.Published) + .InnerJoin().On((cv, ccv) => cv.Id == ccv.VersionId); + + foreach (var dto in Database.Fetch(getPublished)) + Database.Execute(Sql() + .Update(u => u.Set(x => x.Published, true)) + .Where(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("name"); + + // so far, every record in the table mapped to an available culture + var getNames = Sql() + .Select(x => x.NodeId) + .AndSelect(x => x.LanguageId, x => x.Name) + .From() + .InnerJoin().On((node, cv) => node.NodeId == cv.NodeId && cv.Current) + .InnerJoin().On((cv, ccv) => cv.Id == ccv.VersionId); + + foreach (var dto in Database.Fetch(getNames)) + Database.Execute(Sql() + .Update(u => u.Set(x => x.Name, dto.Name)) + .Where(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 + } +} diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index dd379e02f8..238d87b186 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -269,7 +269,7 @@ namespace Umbraco.Core.Models if (_publishInfos == null) _publishInfos = new Dictionary(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(StringComparer.OrdinalIgnoreCase); - _editedCultures.Add(culture); + _editedCultures.Add(culture.ToLowerInvariant()); } // sets all publish edited diff --git a/src/Umbraco.Core/Persistence/Dtos/ContentVersionCultureVariationDto.cs b/src/Umbraco.Core/Persistence/Dtos/ContentVersionCultureVariationDto.cs index 27107b0afc..cd6c66d5f0 100644 --- a/src/Umbraco.Core/Persistence/Dtos/ContentVersionCultureVariationDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/ContentVersionCultureVariationDto.cs @@ -41,9 +41,5 @@ namespace Umbraco.Core.Persistence.Dtos [ForeignKey(typeof(UserDto))] [NullSetting(NullSetting = NullSettings.Null)] public int? PublishedUserId { get => _publishedUserId == 0 ? null : _publishedUserId; set => _publishedUserId = value; } //return null if zero - - // fixme: I've commented this out, it's never used, need to review - //[Column("edited")] - //public bool Edited { get; set; } } } diff --git a/src/Umbraco.Core/Persistence/Dtos/DocumentCultureVariationDto.cs b/src/Umbraco.Core/Persistence/Dtos/DocumentCultureVariationDto.cs index 78e819e714..ed61ea5622 100644 --- a/src/Umbraco.Core/Persistence/Dtos/DocumentCultureVariationDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/DocumentCultureVariationDto.cs @@ -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; } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 7ce4482fb4..033301a8e8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -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 { @@ -1099,16 +1097,22 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private IEnumerable GetDocumentVariationDtos(IContent content, bool publishing, HashSet 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))) }; } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index e6f1798f50..fb8c2732e6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -313,17 +313,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected Sql GetVariantInfos(IEnumerable ids) { - // this ... is an interesting query - could we make it simpler? probably, by having DocumentCultureVariationDto - // handle 'available' and 'published' in addition to 'edited' - would take (a bit) more time to save a document, - // but would make querying way faster - return Sql() .Select(x => x.NodeId) .AndSelect(x => x.IsoCode) .AndSelect("doc", x => Alias(x.Published, "DocumentPublished"), x => Alias(x.Edited, "DocumentEdited")) - .AndSelect("ccv", x => Alias(x.Id, "CultureAvailableData"), x => Alias(x.Name, "Name")) - .AndSelect("pcv", x => Alias(x.Id, "CulturePublishedData")) - .AndSelect("dcv", x => Alias(x.Edited, "CultureEditedData")) + .AndSelect("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() @@ -337,22 +333,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .LeftJoin("dcv") .On((node, dcv, lang) => node.NodeId == dcv.NodeId && lang.Id == dcv.LanguageId, aliasRight: "dcv") - // join to current version - always exists - // left-join to current version variations - indicates which cultures are *available* - .InnerJoin("cv") - .On((node, ev) => node.NodeId == ev.NodeId && ev.Current, aliasRight: "cv") - .LeftJoin("ccv") - .On((cv, ccv, lang) => cv.Id == ccv.VersionId && lang.Id == ccv.LanguageId, "cv", "ccv") - - // left-join to published version - exists when whole node is published - // left-join to published version variations - matches cultures that are *published* - .LeftJoin(nested => nested.InnerJoin("dv") - .On((pv, dv) => pv.Id == dv.Id && dv.Published, "pv", "dv"), - "pv") - .On((node, pv) => node.NodeId == pv.NodeId, aliasRight: "pv") - .LeftJoin("pcv") - .On((pv, pcv, lang) => pv.Id == pcv.VersionId && lang.Id == pcv.LanguageId, "pv", "pcv") - // for selected nodes .WhereIn(x => x.NodeId, ids); } @@ -566,16 +546,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public bool DocumentPublished { get; set; } public bool DocumentEdited { get; set; } - public int? CultureAvailableData { get; set; } - public int? CulturePublishedData { get; set; } - public bool? CultureEditedData { get; set; } - - [ResultColumn] - public bool CultureAvailable => CultureAvailableData.HasValue; - [ResultColumn] - public bool CulturePublished => CulturePublishedData.HasValue; - [ResultColumn] - public bool CultureEdited => CultureEditedData ?? false; + public bool CultureAvailable { get; set; } + public bool CulturePublished { get; set; } + public bool CultureEdited { get; set; } } // ReSharper disable once ClassNeverInstantiated.Local diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 76ed089dec..d1303c37e5 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -359,6 +359,7 @@ +