diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs index f0937a3781..4e7dc1e982 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs @@ -105,9 +105,14 @@ namespace Umbraco.Core.Persistence.Factories /// /// out parameter indicating that one or more properties have been edited /// out parameter containing a collection of edited cultures when the contentVariation varies by culture + /// + /// out parameter containing a collection of edited cultures that are currently persisted in the database which is used to maintain the edited state + /// of each culture value (in cases where variance is being switched) + /// /// public static IEnumerable BuildDtos(ContentVariation contentVariation, int currentVersionId, int publishedVersionId, IEnumerable properties, - ILanguageRepository languageRepository, out bool edited, out HashSet editedCultures) + ILanguageRepository languageRepository, out bool edited, + out HashSet editedCultures) { var propertyDataDtos = new List(); edited = false; @@ -141,14 +146,14 @@ namespace Umbraco.Core.Persistence.Factories if (propertyValue.EditedValue != null) propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue)); - // property.Values will contain ALL of it's values, both variant and invariant which will be populated if the administrator has previously - // changed the property type to be variant vs invariant. - // We need to check for this scenario here because otherwise the editedCultures and edited flags - // will end up incorrectly so here we need to only process edited cultures based on the - // current value type and how the property varies. + //// property.Values will contain ALL of it's values, both variant and invariant which will be populated if the administrator has previously + //// changed the property type to be variant vs invariant. + //// We need to check for this scenario here because otherwise the editedCultures and edited flags + //// will end up incorrectly so here we need to only process edited cultures based on the + //// current value type and how the property varies. - if (property.PropertyType.VariesByCulture() && isInvariantValue) continue; - if (!property.PropertyType.VariesByCulture() && isCultureValue) continue; + //if (property.PropertyType.VariesByCulture() && isInvariantValue) continue; + //if (!property.PropertyType.VariesByCulture() && isCultureValue) continue; // use explicit equals here, else object comparison fails at comparing eg strings var sameValues = propertyValue.PublishedValue == null ? propertyValue.EditedValue == null : propertyValue.PublishedValue.Equals(propertyValue.EditedValue); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs index 9d77eb0990..8b84145027 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs @@ -18,8 +18,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// internal class ContentTypeRepository : ContentTypeRepositoryBase, IContentTypeRepository { - public ContentTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository) - : base(scopeAccessor, cache, logger, commonRepository) + public ContentTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository) + : base(scopeAccessor, cache, logger, commonRepository, languageRepository) { } protected override bool SupportsPublishing => ContentType.SupportsPublishingConst; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index 22c9244d8f..756435692a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -15,6 +15,7 @@ using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; using Umbraco.Core.Services; +using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -26,14 +27,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement internal abstract class ContentTypeRepositoryBase : NPocoRepositoryBase, IReadRepository where TEntity : class, IContentTypeComposition { - protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository) + protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository) : base(scopeAccessor, cache, logger) { CommonRepository = commonRepository; + LanguageRepository = languageRepository; } protected IContentTypeCommonRepository CommonRepository { get; } - + protected ILanguageRepository LanguageRepository { get; } protected abstract bool SupportsPublishing { get; } public IEnumerable> Move(TEntity moving, EntityContainer container) @@ -646,10 +648,16 @@ AND umbracoNode.id <> @id", case ContentVariation.Culture: CopyPropertyData(null, defaultLanguageId, propertyTypeIds, impactedL); CopyTagData(null, defaultLanguageId, propertyTypeIds, impactedL); + RenormalizeDocumentCultureVariations(propertyTypeIds, impactedL); + //TODO: Here we need to normalize the umbracoDocumentCultureVariation table for it's edited flags which are calculated based + //on changed property or name values break; case ContentVariation.Nothing: CopyPropertyData(defaultLanguageId, null, propertyTypeIds, impactedL); CopyTagData(defaultLanguageId, null, propertyTypeIds, impactedL); + RenormalizeDocumentCultureVariations(propertyTypeIds, impactedL); + //TODO: Here we need to normalize the umbracoDocumentCultureVariation table for it's edited flags which are calculated based + //on changed property or name values break; case ContentVariation.CultureAndSegment: case ContentVariation.Segment: @@ -659,6 +667,55 @@ AND umbracoNode.id <> @id", } } + //private HashSet GetEditedCultures(ContentVariation contentVariation, int currentVersionId, int publishedVersionId, IEnumerable properties) + //{ + // HashSet editedCultures = null; // don't allocate unless necessary + // string defaultCulture = null; //don't allocate unless necessary + + // var entityVariesByCulture = contentVariation.VariesByCulture(); + + // // create dtos for each property values, but only for values that do actually exist + // // ie have a non-null value, everything else is just ignored and won't have a db row + + // foreach (var property in properties) + // { + // if (property.PropertyType.SupportsPublishing) + // { + // //create the resulting hashset if it's not created and the entity varies by culture + // if (entityVariesByCulture && editedCultures == null) + // editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase); + + // // publishing = deal with edit and published values + // foreach (var propertyValue in property.Values) + // { + // var isInvariantValue = propertyValue.Culture == null; + // var isCultureValue = propertyValue.Culture != null && propertyValue.Segment == null; + + // // use explicit equals here, else object comparison fails at comparing eg strings + // var sameValues = propertyValue.PublishedValue == null ? propertyValue.EditedValue == null : propertyValue.PublishedValue.Equals(propertyValue.EditedValue); + + // if (entityVariesByCulture && !sameValues) + // { + // if (isCultureValue) + // { + // editedCultures.Add(propertyValue.Culture); // report culture as edited + // } + // else if (isInvariantValue) + // { + // // flag culture as edited if it contains an edited invariant property + // if (defaultCulture == null) + // defaultCulture = languageRepository.GetDefaultIsoCode(); + + // editedCultures.Add(defaultCulture); + // } + // } + // } + // } + // } + + // return editedCultures; + //} + /// /// Moves variant data for a content type variation change. /// @@ -963,6 +1020,208 @@ AND umbracoNode.id <> @id", Database.Execute(sqlDelete); } + + } + + /// + /// Re-normalizes the edited value in the umbracoDocumentCultureVariation table when property variations are changed + /// + /// + /// + /// + /// If this is not done, then in some cases the "edited" value for a particular culture for a document will remain true when it should be false + /// if the property was changed to invariant. In order to do this we need to recalculate this value based on the values stored for each + /// property, culture and current/published version. The end result is to update the edited value in the umbracoDocumentCultureVariation table so we + /// make sure to join this table with the lookups so that only relevant data is returned. + /// + private void RenormalizeDocumentCultureVariations(IReadOnlyCollection propertyTypeIds, IReadOnlyCollection contentTypeIds = null) + { + + var defaultLang = LanguageRepository.GetDefaultId(); + + //This will build up a query to get the property values of both the current and the published version so that we can check + //based on the current variance of each item to see if it's 'edited' value should be true/false. + + var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0); + if (whereInArgsCount > 2000) + throw new NotSupportedException("Too many property/content types."); + + var propertySql = Sql() + .Select() + .AndSelect(x => x.NodeId, x => x.Current) + .AndSelect(x => x.Published) + .AndSelect(x => x.Variations) + .From() + .InnerJoin().On((left, right) => left.Id == right.VersionId) + .InnerJoin().On((left, right) => left.Id == right.PropertyTypeId); + + if (contentTypeIds != null) + { + propertySql.InnerJoin().On((c, cversion) => c.NodeId == cversion.NodeId); + } + + propertySql.LeftJoin().On((docversion, cversion) => cversion.Id == docversion.Id) + .Where((docversion, cversion) => cversion.Current || docversion.Published) + .WhereIn(x => x.PropertyTypeId, propertyTypeIds); + + if (contentTypeIds != null) + { + propertySql.WhereIn(x => x.ContentTypeId, contentTypeIds); + } + + propertySql + .OrderBy(x => x.NodeId) + .OrderBy(x => x.PropertyTypeId, x => x.LanguageId, x => x.VersionId); + + //keep track of this node/lang to mark or unmark as edited + var editedVersions = new Dictionary<(int nodeId, int? langId), bool>(); + + var nodeId = -1; + var propertyTypeId = -1; + PropertyValueVersionDto pubRow = null; + + //This is a QUERY we are not fetching this all into memory so we cannot make any changes during this iteration, we are just collecting data. + //Published data will always come before Current data based on the version id sort. + //There will only be one published row (max) and one current row per property. + foreach (var row in Database.Query(propertySql)) + { + //make sure to reset on each node/property change + if (nodeId != row.NodeId || propertyTypeId != row.PropertyTypeId) + { + nodeId = row.NodeId; + propertyTypeId = row.PropertyTypeId; + pubRow = null; + } + + if (row.Published) + pubRow = row; + + if (row.Current) + { + var propVariations = (ContentVariation)row.Variations; + + //if this prop doesn't vary but the row has a lang assigned or vice versa, flag this as not edited + if (!propVariations.VariesByCulture() && row.LanguageId.HasValue + || propVariations.VariesByCulture() && !row.LanguageId.HasValue) + { + //Flag this as not edited for this node/lang if the key doesn't exist + if (!editedVersions.TryGetValue((row.NodeId, row.LanguageId), out _)) + editedVersions.Add((row.NodeId, row.LanguageId), false); + } + else if (pubRow == null) + { + //this would mean that that this property is 'edited' since there is no published version + editedVersions.Add((row.NodeId, row.LanguageId), true); + } + //compare the property values, if they differ from versions then flag the current version as edited + else if (IsPropertyValueChanged(pubRow, row)) + { + //Here we would check if the property is invariant, in which case the edited language should be indicated by the default lang + editedVersions[(row.NodeId, !propVariations.VariesByCulture() ? defaultLang : row.LanguageId)] = true; + } + + //reset + pubRow = null; + } + } + + //lookup all matching rows in umbracoDocumentCultureVariation + var docCultureVariationsToUpdate = Database.Fetch( + Sql().Select().From() + .WhereIn(x => x.LanguageId, editedVersions.Keys.Select(x => x.langId).ToList()) + .WhereIn(x => x.NodeId, editedVersions.Keys.Select(x => x.nodeId))) + //convert to dictionary with the same key type + .ToDictionary(x => (x.NodeId, (int?)x.LanguageId), x => x); + + foreach (var ev in editedVersions) + { + if (docCultureVariationsToUpdate.TryGetValue(ev.Key, out var docVariations)) + { + //check if it needs updating + if (docVariations.Edited != ev.Value) + { + docVariations.Edited = ev.Value; + Database.Update(docVariations); + } + } + else + { + //the row doesn't exist but needs creating + //TODO: Does this ever happen?? Need to see if we can test this + } + } + + ////Generate SQL to lookup the current name vs the publish name for each language + //var nameSql = Sql() + // .Select("cv1", x => x.NodeId, x => Alias(x.Id, "currentVersion")) + // .AndSelect("cvcv1", x => x.LanguageId, x => Alias(x.Name, "currentName")) + // .AndSelect("cvcv2", x => Alias(x.Name, "publishedName")) + // .AndSelect("dv", x => Alias(x.Id, "publishedVersion")) + // .AndSelect("dcv", x => x.Id, x => x.Edited) + // .From("cvcv1") + // .InnerJoin("cv1") + // .On((left, right) => left.Id == right.VersionId, "cv1", "cvcv1") + // .InnerJoin("dcv") + // .On((left, right, other) => left.NodeId == right.NodeId && left.LanguageId == other.LanguageId, "dcv", "cv1", "cvcv1") + // .LeftJoin(nested => + // nested.InnerJoin("dv") + // .On((left, right) => left.Id == right.Id && right.Published, "cv2", "dv"), "cv2") + // .On((left, right) => left.NodeId == right.NodeId, "cv1", "cv2") + // .LeftJoin("cvcv2") + // .On((left, right, other) => left.VersionId == right.Id && left.LanguageId == other.LanguageId, "cvcv2", "cv2", "cvcv1") + // .Where(x => x.Current, "cv1") + // .OrderBy("cv1.nodeId, cvcv1.versionId, cvcv1.languageId"); + + //var names = Database.Fetch(nameSql); + + } + + private static bool IsPropertyValueChanged(PropertyValueVersionDto pubRow, PropertyValueVersionDto row) + { + return !pubRow.TextValue.IsNullOrWhiteSpace() && pubRow.TextValue != row.TextValue + || !pubRow.VarcharValue.IsNullOrWhiteSpace() && pubRow.VarcharValue != row.VarcharValue + || pubRow.DateValue.HasValue && pubRow.DateValue != row.DateValue + || pubRow.DecimalValue.HasValue && pubRow.DecimalValue != row.DecimalValue + || pubRow.IntValue.HasValue && pubRow.IntValue != row.IntValue; + } + + private class NameCompareDto + { + public int NodeId { get; set; } + public int CurrentVersion { get; set; } + public int LanguageId { get; set; } + public string CurrentName { get; set; } + public string PublishedName { get; set; } + public int? PublishedVersion { get; set; } + public int Id { get; set; } // the Id of the DocumentCultureVariationDto + public bool Edited { get; set; } + } + + private class PropertyValueVersionDto + { + public int VersionId { get; set; } + public int PropertyTypeId { get; set; } + public int? LanguageId { get; set; } + public string Segment { get; set; } + public int? IntValue { get; set; } + + private decimal? _decimalValue; + [Column("decimalValue")] + public decimal? DecimalValue + { + get => _decimalValue; + set => _decimalValue = value?.Normalize(); + } + + public DateTime? DateValue { get; set; } + public string VarcharValue { get; set; } + public string TextValue { get; set; } + + public int NodeId { get; set; } + public bool Current { get; set; } + public bool Published { get; set; } + + public byte Variations { get; set; } } private void DeletePropertyType(int contentTypeId, int propertyTypeId) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 30a2927cc8..0dbfc61b8c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -386,7 +386,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing)); // insert document variations - Database.BulkInsertRecords(GetDocumentVariationDtos(entity, publishing, editedCultures)); + Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures)); } // refresh content @@ -571,7 +571,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing)); // insert document variations - Database.BulkInsertRecords(GetDocumentVariationDtos(entity, publishing, editedCultures)); + Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures)); } // refresh content @@ -1297,25 +1297,30 @@ namespace Umbraco.Core.Persistence.Repositories.Implement }; } - private IEnumerable GetDocumentVariationDtos(IContent content, bool publishing, HashSet editedCultures) + private IEnumerable GetDocumentVariationDtos(IContent content, HashSet editedCultures) { var allCultures = content.AvailableCultures.Union(content.PublishedCultures); // union = distinct foreach (var culture in allCultures) - yield return new DocumentCultureVariationDto + { + var dto = new DocumentCultureVariationDto { NodeId = content.Id, LanguageId = LanguageRepository.GetIdByIsoCode(culture) ?? throw new InvalidOperationException("Not a valid culture."), Culture = 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))) + Published = content.IsCulturePublished(culture) }; + + // note: can't use IsCultureEdited at that point - hasn't been updated yet - see PersistUpdatedItem + + dto.Edited = content.IsCultureAvailable(culture) && + (!content.IsCulturePublished(culture) || (editedCultures != null && editedCultures.Contains(culture))); + + yield return dto; + } + } private class ContentVariation diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs index 1abc75cf3a..a2c7cc3f44 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs @@ -17,8 +17,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// internal class MediaTypeRepository : ContentTypeRepositoryBase, IMediaTypeRepository { - public MediaTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository) - : base(scopeAccessor, cache, logger, commonRepository) + public MediaTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository) + : base(scopeAccessor, cache, logger, commonRepository, languageRepository) { } protected override bool SupportsPublishing => MediaType.SupportsPublishingConst; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs index d96854743e..b4d6033b9b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs @@ -18,8 +18,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// internal class MemberTypeRepository : ContentTypeRepositoryBase, IMemberTypeRepository { - public MemberTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository) - : base(scopeAccessor, cache, logger, commonRepository) + public MemberTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository) + : base(scopeAccessor, cache, logger, commonRepository, languageRepository) { } protected override bool SupportsPublishing => MemberType.SupportsPublishingConst; diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 53f150f140..f953b9cce6 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -29,10 +29,11 @@ namespace Umbraco.Tests.Persistence.Repositories private DocumentRepository CreateRepository(IScopeAccessor scopeAccessor, out ContentTypeRepository contentTypeRepository) { + var langRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger); var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(scopeAccessor, AppCaches.Disabled, Logger); var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled); - contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository); + contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository); var languageRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger); var repository = new DocumentRepository(scopeAccessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); return repository; @@ -40,9 +41,10 @@ namespace Umbraco.Tests.Persistence.Repositories private ContentTypeRepository CreateRepository(IScopeAccessor scopeAccessor) { + var langRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger); var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled); - var contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository); + var contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository); return contentTypeRepository; } @@ -50,7 +52,8 @@ namespace Umbraco.Tests.Persistence.Repositories { var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled); - var contentTypeRepository = new MediaTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository); + var langRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger); + var contentTypeRepository = new MediaTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository); return contentTypeRepository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs index fd797662c0..4d62ec8301 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs @@ -67,8 +67,8 @@ namespace Umbraco.Tests.Persistence.Repositories templateRepository = new TemplateRepository(scopeAccessor, appCaches, Logger, TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(scopeAccessor, appCaches, Logger); var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches); - contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, Logger, commonRepository); var languageRepository = new LanguageRepository(scopeAccessor, appCaches, Logger); + contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, Logger, commonRepository, languageRepository); var repository = new DocumentRepository(scopeAccessor, appCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs index f00b2fd046..628f8d75a7 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs @@ -23,8 +23,8 @@ namespace Umbraco.Tests.Persistence.Repositories var templateRepository = new TemplateRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(accessor, Core.Cache.AppCaches.Disabled, Logger); var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches); - contentTypeRepository = new ContentTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, commonRepository); languageRepository = new LanguageRepository(accessor, Core.Cache.AppCaches.Disabled, Logger); + contentTypeRepository = new ContentTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, commonRepository, languageRepository); documentRepository = new DocumentRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); var domainRepository = new DomainRepository(accessor, Core.Cache.AppCaches.Disabled, Logger); return domainRepository; diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index 1d9cf6d022..e2123df9e3 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -38,7 +38,8 @@ namespace Umbraco.Tests.Persistence.Repositories var templateRepository = new TemplateRepository(scopeAccessor, appCaches, Logger, TestObjects.GetFileSystemsMock()); var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches); - mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, Logger, commonRepository); + var languageRepository = new LanguageRepository(scopeAccessor, appCaches, Logger); + mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, Logger, commonRepository, languageRepository); var tagRepository = new TagRepository(scopeAccessor, appCaches, Logger); var repository = new MediaRepository(scopeAccessor, appCaches, Logger, mediaTypeRepository, tagRepository, Mock.Of()); return repository; diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs index f302d1d992..bb3286daed 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs @@ -23,7 +23,8 @@ namespace Umbraco.Tests.Persistence.Repositories var cacheHelper = AppCaches.Disabled; var templateRepository = new TemplateRepository((IScopeAccessor)provider, cacheHelper, Logger, TestObjects.GetFileSystemsMock()); var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, templateRepository, AppCaches); - return new MediaTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository); + var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches, Logger); + return new MediaTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository); } private EntityContainerRepository CreateContainerRepository(IScopeProvider provider) diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index a5f7f08f22..17b16ad7ab 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -31,7 +31,8 @@ namespace Umbraco.Tests.Persistence.Repositories var accessor = (IScopeAccessor) provider; var templateRepository = Mock.Of(); var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches); - memberTypeRepository = new MemberTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository); + var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger); + memberTypeRepository = new MemberTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository); memberGroupRepository = new MemberGroupRepository(accessor, AppCaches.Disabled, Logger); var tagRepo = new TagRepository(accessor, AppCaches.Disabled, Logger); var repository = new MemberRepository(accessor, AppCaches.Disabled, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of()); diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs index 79e8e43804..4b9f3096ce 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs @@ -24,7 +24,8 @@ namespace Umbraco.Tests.Persistence.Repositories { var templateRepository = Mock.Of(); var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, templateRepository, AppCaches); - return new MemberTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of(), commonRepository); + var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Mock.Of()); + return new MemberTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of(), commonRepository, languageRepository); } [Test] diff --git a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs index 803eff25af..56041c24aa 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs @@ -308,8 +308,8 @@ namespace Umbraco.Tests.Persistence.Repositories var templateRepository = new TemplateRepository(accessor, AppCaches, Logger, TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(accessor, AppCaches, Logger); var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches); - contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository); var languageRepository = new LanguageRepository(accessor, AppCaches, Logger); + contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository, languageRepository); var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs index b6cc4dc50d..e3de2c2892 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs @@ -956,8 +956,8 @@ namespace Umbraco.Tests.Persistence.Repositories var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger); var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches.Disabled); - contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository); var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger); + contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository); var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); return repository; } @@ -968,7 +968,8 @@ namespace Umbraco.Tests.Persistence.Repositories var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger); var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches.Disabled); - mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository); + var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger); + mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository); var repository = new MediaRepository(accessor, AppCaches.Disabled, Logger, mediaTypeRepository, tagRepository, Mock.Of()); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs index 13cbd463fb..b0f9a5335b 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs @@ -239,8 +239,8 @@ namespace Umbraco.Tests.Persistence.Repositories var tagRepository = new TagRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger); var commonRepository = new ContentTypeCommonRepository(ScopeProvider, templateRepository, AppCaches); - var contentTypeRepository = new ContentTypeRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, commonRepository); var languageRepository = new LanguageRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger); + var contentTypeRepository = new ContentTypeRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, commonRepository, languageRepository); var contentRepo = new DocumentRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage"); diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index b550091591..3e5919d7f3 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -26,7 +26,8 @@ namespace Umbraco.Tests.Persistence.Repositories var accessor = (IScopeAccessor) provider; var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches); - mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches, Mock.Of(), commonRepository); + var languageRepository = new LanguageRepository(accessor, AppCaches, Logger); + mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches, Mock.Of(), commonRepository, languageRepository); var tagRepository = new TagRepository(accessor, AppCaches, Mock.Of()); var repository = new MediaRepository(accessor, AppCaches, Mock.Of(), mediaTypeRepository, tagRepository, Mock.Of()); return repository; @@ -44,8 +45,8 @@ namespace Umbraco.Tests.Persistence.Repositories templateRepository = new TemplateRepository(accessor, AppCaches, Logger, TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(accessor, AppCaches, Logger); var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches); - contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository); var languageRepository = new LanguageRepository(accessor, AppCaches, Logger); + contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository, languageRepository); var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); return repository; } diff --git a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs index ed5e6073ac..ef80672baf 100644 --- a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs +++ b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs @@ -166,8 +166,8 @@ namespace Umbraco.Tests.Services var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches); - var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository); var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); + var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository); var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository); // Act @@ -200,8 +200,8 @@ namespace Umbraco.Tests.Services var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches); - var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository); var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); + var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository); var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository); // Act @@ -232,8 +232,8 @@ namespace Umbraco.Tests.Services var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); var commonRepository = new ContentTypeCommonRepository((IScopeAccessor) provider, tRepository, AppCaches); - var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository); - var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); + var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger); + var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository); var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository); // Act @@ -267,8 +267,8 @@ namespace Umbraco.Tests.Services var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches); - var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository); - var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); + var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger); + var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository); var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository); // Act diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 222f40aeed..7acfd994d3 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -3008,8 +3008,8 @@ namespace Umbraco.Tests.Services var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger); var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches); - contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository); var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger); + contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository); var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); return repository; } diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index f5fa4e8795..53a39ffe40 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -810,12 +810,18 @@ namespace Umbraco.Tests.Services var document = (IContent)new Content("document", -1, contentType); document.SetCultureName("doc1en", "en"); document.SetCultureName("doc1fr", "fr"); - document.SetValue("value1", "v1en", "en"); - document.SetValue("value1", "v1fr", "fr"); - ServiceContext.ContentService.Save(document); + document.SetValue("value1", "v1en-init", "en"); + document.SetValue("value1", "v1fr-init", "fr"); + ServiceContext.ContentService.SaveAndPublish(document); //all values are published which means the document is not 'edited' - //at this stage there will be 4 property values stored in the DB for "value1" against "en" and "fr" for both edited/published versions, - //for the published values, these will be null + document = ServiceContext.ContentService.GetById(document.Id); + Assert.IsFalse(document.IsCultureEdited("en")); + Assert.IsFalse(document.IsCultureEdited("fr")); + Assert.IsFalse(document.Edited); + + document.SetValue("value1", "v1en", "en"); //change the property culture value, so now this culture will be edited + document.SetValue("value1", "v1fr", "fr"); //change the property culture value, so now this culture will be edited + ServiceContext.ContentService.Save(document); document = ServiceContext.ContentService.GetById(document.Id); Assert.AreEqual("doc1en", document.Name); @@ -829,18 +835,17 @@ namespace Umbraco.Tests.Services // switch property type to Nothing contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Nothing; - ServiceContext.ContentTypeService.Save(contentType); + ServiceContext.ContentTypeService.Save(contentType); //This is going to have to re-normalize the "Edited" flag document = ServiceContext.ContentService.GetById(document.Id); + Assert.IsTrue(document.IsCultureEdited("en")); //This will remain true because there is now a pending change for the invariant property data which is flagged under the default lang + Assert.IsFalse(document.IsCultureEdited("fr")); //This will be false because nothing has changed for this culture and the property no longer reflects variant changes Assert.IsTrue(document.Edited); - // publish the document - document.SetValue("value1", "v1inv"); //update the invariant value + //update the invariant value and publish + document.SetValue("value1", "v1inv"); ServiceContext.ContentService.SaveAndPublish(document); - //at this stage there will be 6 property values stored in the DB for "value1" against "en", "fr" and null for both edited/published versions, - //for the published values for the cultures, these will still be null but the invariant edited/published values will both be "v1inv". - document = ServiceContext.ContentService.GetById(document.Id); Assert.AreEqual("doc1en", document.Name); Assert.AreEqual("doc1en", document.GetCultureName("en")); @@ -848,8 +853,8 @@ namespace Umbraco.Tests.Services Assert.IsNull(document.GetValue("value1", "en")); //The values are there but the business logic returns null Assert.IsNull(document.GetValue("value1", "fr")); //The values are there but the business logic returns null Assert.AreEqual("v1inv", document.GetValue("value1")); - Assert.IsFalse(document.IsCultureEdited("en")); //This returns false, the culture shouldn't be marked as edited because the invariant property value is published - Assert.IsFalse(document.IsCultureEdited("fr")); //This returns false, the culture shouldn't be marked as edited because the invariant property value is published + Assert.IsFalse(document.IsCultureEdited("en")); //This returns false, everything is published - however in the DB this is not the case for the "en" version of the property + Assert.IsFalse(document.IsCultureEdited("fr")); //This returns false, everything is published Assert.IsFalse(document.Edited); // switch property back to Culture