diff --git a/src/Umbraco.Core/ContentExtensions.cs b/src/Umbraco.Core/ContentExtensions.cs index 44e9907210..b2bf29cc5f 100644 --- a/src/Umbraco.Core/ContentExtensions.cs +++ b/src/Umbraco.Core/ContentExtensions.cs @@ -67,7 +67,7 @@ namespace Umbraco.Core if (!content.Published || !content.ContentType.VariesByCulture() || !content.IsPropertyDirty("PublishCultureInfos")) return Array.Empty(); - var culturesChanging = content.CultureInfos.Where(x => x.Value.IsDirty()).Select(x => x.Key); + var culturesChanging = content.CultureInfos.Values.Where(x => x.IsDirty()).Select(x => x.Culture); return culturesChanging .Where(x => !content.IsCulturePublished(x) && // is not published anymore persisted != null && persisted.IsCulturePublished(x)) // but was published before diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 63ab65b8fe..09b0b41839 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -128,15 +128,16 @@ namespace Umbraco.Core.Models /// /// Gets or sets a value indicating whether this content item is published or not. /// + /// + /// the setter is should only be invoked from + /// - the ContentFactory when creating a content entity from a dto + /// - the ContentRepository when updating a content entity + /// [DataMember] public bool Published { get => _published; - - // the setter is internal and should only be invoked from - // - the ContentFactory when creating a content entity from a dto - // - the ContentRepository when updating a content entity - internal set + set { SetPropertyValueAndDetectChanges(value, ref _published, nameof(Published)); _publishedState = _published ? PublishedState.Published : PublishedState.Unpublished; @@ -162,7 +163,7 @@ namespace Umbraco.Core.Models } [IgnoreDataMember] - public bool Edited { get; internal set; } + public bool Edited { get; set; } /// /// Gets the ContentType used by this content object @@ -172,23 +173,27 @@ namespace Umbraco.Core.Models /// [IgnoreDataMember] - public DateTime? PublishDate { get; internal set; } // set by persistence + public DateTime? PublishDate { get; set; } // set by persistence /// [IgnoreDataMember] - public int? PublisherId { get; internal set; } // set by persistence + public int? PublisherId { get; set; } // set by persistence /// [IgnoreDataMember] - public int? PublishTemplateId { get; internal set; } // set by persistence + public int? PublishTemplateId { get; set; } // set by persistence /// [IgnoreDataMember] - public string PublishName { get; internal set; } // set by persistence + public string PublishName { get; set; } // set by persistence /// [IgnoreDataMember] - public IEnumerable EditedCultures => CultureInfos.Keys.Where(IsCultureEdited); + public IEnumerable EditedCultures + { + get => CultureInfos.Keys.Where(IsCultureEdited); + set => _editedCultures = value == null ? null : new HashSet(value, StringComparer.OrdinalIgnoreCase); + } /// [IgnoreDataMember] @@ -199,27 +204,7 @@ namespace Umbraco.Core.Models // just check _publishInfos // a non-available culture could not become published anyways => _publishInfos != null && _publishInfos.ContainsKey(culture); - - - // adjust dates to sync between version, cultures etc - // used by the repo when persisting - internal void AdjustDates(DateTime date) - { - foreach (var culture in PublishedCultures.ToList()) - { - if (_publishInfos == null || !_publishInfos.TryGetValue(culture, out var publishInfos)) - continue; - - //fixme: Removing the logic here for the old WasCulturePublished and the _publishInfosOrig has broken - // the test Can_Rollback_Version_On_Multilingual, but we need to understand what it's doing since I don't - - _publishInfos.AddOrUpdate(culture, publishInfos.Name, date); - - if (CultureInfos.TryGetValue(culture, out var infos)) - SetCultureInfo(culture, infos.Name, date); - } - } - + /// public bool IsCultureEdited(string culture) => IsCultureAvailable(culture) && // is available, and @@ -228,7 +213,23 @@ namespace Umbraco.Core.Models /// [IgnoreDataMember] - public IReadOnlyDictionary PublishCultureInfos => _publishInfos ?? NoInfos; + public ContentCultureInfosCollection PublishCultureInfos + { + get + { + if (_publishInfos != null) return _publishInfos; + _publishInfos = new ContentCultureInfosCollection(); + _publishInfos.CollectionChanged += PublishNamesCollectionChanged; + return _publishInfos; + } + set + { + if (_publishInfos != null) _publishInfos.CollectionChanged -= PublishNamesCollectionChanged; + _publishInfos = value; + if (_publishInfos != null) + _publishInfos.CollectionChanged += PublishNamesCollectionChanged; + } + } /// public string GetPublishName(string culture) @@ -247,67 +248,7 @@ namespace Umbraco.Core.Models if (_publishInfos == null) return null; return _publishInfos.TryGetValue(culture, out var infos) ? infos.Date : (DateTime?) null; } - - // internal for repository - internal void SetPublishInfo(string culture, string name, DateTime date) - { - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentNullOrEmptyException(nameof(name)); - - if (culture.IsNullOrWhiteSpace()) - throw new ArgumentNullOrEmptyException(nameof(culture)); - - if (_publishInfos == null) - { - _publishInfos = new ContentCultureInfosCollection(); - _publishInfos.CollectionChanged += PublishNamesCollectionChanged; - } - - _publishInfos.AddOrUpdate(culture, name, date); - } - - private void ClearPublishInfos() - { - _publishInfos = null; - } - - private void ClearPublishInfo(string culture) - { - if (culture.IsNullOrWhiteSpace()) - throw new ArgumentNullOrEmptyException(nameof(culture)); - - if (_publishInfos == null) return; - _publishInfos.Remove(culture); - if (_publishInfos.Count == 0) _publishInfos = null; - - // set the culture to be dirty - it's been modified - TouchCultureInfo(culture); - } - - // sets a publish edited - internal void SetCultureEdited(string culture) - { - if (culture.IsNullOrWhiteSpace()) - throw new ArgumentNullOrEmptyException(nameof(culture)); - if (_editedCultures == null) - _editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase); - _editedCultures.Add(culture.ToLowerInvariant()); - } - - // sets all publish edited - internal void SetCultureEdited(IEnumerable cultures) - { - if (cultures == null) - { - _editedCultures = null; - } - else - { - var editedCultures = new HashSet(cultures.Where(x => !x.IsNullOrWhiteSpace()), StringComparer.OrdinalIgnoreCase); - _editedCultures = editedCultures.Count > 0 ? editedCultures : null; - } - } - + /// /// Handles culture infos collection changes. /// @@ -317,84 +258,10 @@ namespace Umbraco.Core.Models } [IgnoreDataMember] - public int PublishedVersionId { get; internal set; } + public int PublishedVersionId { get; set; } [DataMember] - public bool Blueprint { get; internal set; } - - /// - public bool PublishCulture(string culture = "*") - { - culture = culture.NullOrWhiteSpaceAsNull(); - - // the variation should be supported by the content type properties - // if the content type is invariant, only '*' and 'null' is ok - // if the content type varies, everything is ok because some properties may be invariant - if (!ContentType.SupportsPropertyVariation(culture, "*", true)) - throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{ContentType.Alias}\" with variation \"{ContentType.Variations}\"."); - - // the values we want to publish should be valid - if (ValidateProperties(culture).Any()) - return false; - - var alsoInvariant = false; - if (culture == "*") // all cultures - { - foreach (var c in AvailableCultures) - { - var name = GetCultureName(c); - if (string.IsNullOrWhiteSpace(name)) - return false; - SetPublishInfo(c, name, DateTime.Now); - } - } - else if (culture == null) // invariant culture - { - if (string.IsNullOrWhiteSpace(Name)) - return false; - // PublishName set by repository - nothing to do here - } - else // one single culture - { - var name = GetCultureName(culture); - if (string.IsNullOrWhiteSpace(name)) - return false; - SetPublishInfo(culture, name, DateTime.Now); - alsoInvariant = true; // we also want to publish invariant values - } - - // property.PublishValues only publishes what is valid, variation-wise - foreach (var property in Properties) - { - property.PublishValues(culture); - if (alsoInvariant) - property.PublishValues(null); - } - - _publishedState = PublishedState.Publishing; - return true; - } - - /// - public void UnpublishCulture(string culture = "*") - { - culture = culture.NullOrWhiteSpaceAsNull(); - - // the variation should be supported by the content type properties - if (!ContentType.SupportsPropertyVariation(culture, "*", true)) - throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{ContentType.Alias}\" with variation \"{ContentType.Variations}\"."); - - if (culture == "*") // all cultures - ClearPublishInfos(); - else // one single culture - ClearPublishInfo(culture); - - // property.PublishValues only publishes what is valid, variation-wise - foreach (var property in Properties) - property.UnpublishValues(culture); - - _publishedState = PublishedState.Publishing; - } + public bool Blueprint { get; set; } /// /// Changes the for the current content object diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index ad8e179e7a..b9301504c4 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -17,7 +17,6 @@ namespace Umbraco.Core.Models [DebuggerDisplay("Id: {Id}, Name: {Name}, ContentType: {ContentTypeBase.Alias}")] public abstract class ContentBase : TreeEntityBase, IContentBase { - protected static readonly ContentCultureInfosCollection NoInfos = new ContentCultureInfosCollection(); private int _contentTypeId; protected IContentTypeComposition ContentTypeBase; @@ -69,20 +68,20 @@ namespace Umbraco.Core.Models /// Id of the user who wrote/updated this entity /// [DataMember] - public virtual int WriterId + public int WriterId { get => _writerId; set => SetPropertyValueAndDetectChanges(value, ref _writerId, nameof(WriterId)); } [IgnoreDataMember] - public int VersionId { get; internal set; } + public int VersionId { get; set; } /// /// Integer Id of the default ContentType /// [DataMember] - public virtual int ContentTypeId + public int ContentTypeId { get { @@ -105,11 +104,12 @@ namespace Umbraco.Core.Models /// [DataMember] [DoNotClone] - public virtual PropertyCollection Properties + public PropertyCollection Properties { get => _properties; set { + if (_properties != null) _properties.CollectionChanged -= PropertiesChanged; _properties = value; _properties.CollectionChanged += PropertiesChanged; } @@ -147,7 +147,23 @@ namespace Umbraco.Core.Models /// [DataMember] - public virtual IReadOnlyDictionary CultureInfos => _cultureInfos ?? NoInfos; + public ContentCultureInfosCollection CultureInfos + { + get + { + if (_cultureInfos != null) return _cultureInfos; + _cultureInfos = new ContentCultureInfosCollection(); + _cultureInfos.CollectionChanged += CultureInfosCollectionChanged; + return _cultureInfos; + } + set + { + if (_cultureInfos != null) _cultureInfos.CollectionChanged -= CultureInfosCollectionChanged; + _cultureInfos = value; + if (_cultureInfos != null) + _cultureInfos.CollectionChanged += CultureInfosCollectionChanged; + } + } /// public string GetCultureName(string culture) @@ -182,7 +198,7 @@ namespace Umbraco.Core.Models } else // set { - SetCultureInfo(culture, name, DateTime.Now); + this.SetCultureInfo(culture, name, DateTime.Now); } } else // set on invariant content type @@ -194,13 +210,13 @@ namespace Umbraco.Core.Models } } - protected void ClearCultureInfos() + private void ClearCultureInfos() { _cultureInfos?.Clear(); _cultureInfos = null; } - protected void ClearCultureInfo(string culture) + private void ClearCultureInfo(string culture) { if (culture.IsNullOrWhiteSpace()) throw new ArgumentNullOrEmptyException(nameof(culture)); @@ -211,30 +227,6 @@ namespace Umbraco.Core.Models _cultureInfos = null; } - protected void TouchCultureInfo(string culture) - { - if (_cultureInfos == null || !_cultureInfos.TryGetValue(culture, out var infos)) return; - _cultureInfos.AddOrUpdate(culture, infos.Name, DateTime.Now); - } - - // internal for repository - internal void SetCultureInfo(string culture, string name, DateTime date) - { - if (name.IsNullOrWhiteSpace()) - throw new ArgumentNullOrEmptyException(nameof(name)); - - if (culture.IsNullOrWhiteSpace()) - throw new ArgumentNullOrEmptyException(nameof(culture)); - - if (_cultureInfos == null) - { - _cultureInfos = new ContentCultureInfosCollection(); - _cultureInfos.CollectionChanged += CultureInfosCollectionChanged; - } - - _cultureInfos.AddOrUpdate(culture, name, date); - } - /// /// Handles culture infos collection changes. /// @@ -248,11 +240,11 @@ namespace Umbraco.Core.Models #region Has, Get, Set, Publish Property Value /// - public virtual bool HasProperty(string propertyTypeAlias) + public bool HasProperty(string propertyTypeAlias) => Properties.Contains(propertyTypeAlias); /// - public virtual object GetValue(string propertyTypeAlias, string culture = null, string segment = null, bool published = false) + public object GetValue(string propertyTypeAlias, string culture = null, string segment = null, bool published = false) { return Properties.TryGetValue(propertyTypeAlias, out var property) ? property.GetValue(culture, segment, published) @@ -260,7 +252,7 @@ namespace Umbraco.Core.Models } /// - public virtual TValue GetValue(string propertyTypeAlias, string culture = null, string segment = null, bool published = false) + public TValue GetValue(string propertyTypeAlias, string culture = null, string segment = null, bool published = false) { if (!Properties.TryGetValue(propertyTypeAlias, out var property)) return default; @@ -270,7 +262,7 @@ namespace Umbraco.Core.Models } /// - public virtual void SetValue(string propertyTypeAlias, object value, string culture = null, string segment = null) + public void SetValue(string propertyTypeAlias, object value, string culture = null, string segment = null) { if (Properties.Contains(propertyTypeAlias)) { @@ -292,7 +284,7 @@ namespace Umbraco.Core.Models #region Copy /// - public virtual void CopyFrom(IContent other, string culture = "*") + public void CopyFrom(IContent other, string culture = "*") { if (other.ContentTypeId != ContentTypeId) throw new InvalidOperationException("Cannot copy values from a different content type."); @@ -353,10 +345,11 @@ namespace Umbraco.Core.Models if (culture == null || culture == "*") Name = other.Name; - foreach (var (otherCulture, otherInfos) in other.CultureInfos) + // ReSharper disable once UseDeconstruction + foreach (var cultureInfo in other.CultureInfos) { - if (culture == "*" || culture == otherCulture) - SetCultureName(otherInfos.Name, otherCulture); + if (culture == "*" || culture == cultureInfo.Culture) + SetCultureName(cultureInfo.Name, cultureInfo.Culture); } } diff --git a/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs new file mode 100644 index 0000000000..79c3dc3f63 --- /dev/null +++ b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Exceptions; + +namespace Umbraco.Core.Models +{ + /// + /// Extension methods used to manipulate content variations by the document repository + /// + internal static class ContentRepositoryExtensions + { + public static void SetPublishInfo(this IContent content, string culture, string name, DateTime date) + { + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentNullOrEmptyException(nameof(name)); + + if (culture.IsNullOrWhiteSpace()) + throw new ArgumentNullOrEmptyException(nameof(culture)); + + content.PublishCultureInfos.AddOrUpdate(culture, name, date); + } + + // adjust dates to sync between version, cultures etc used by the repo when persisting + public static void AdjustDates(this IContent content, DateTime date) + { + foreach (var culture in content.PublishedCultures.ToList()) + { + if (!content.PublishCultureInfos.TryGetValue(culture, out var publishInfos)) + continue; + + //fixme: Removing the logic here for the old WasCulturePublished and the _publishInfosOrig has broken + // the test Can_Rollback_Version_On_Multilingual, but we need to understand what it's doing since I don't + + content.PublishCultureInfos.AddOrUpdate(culture, publishInfos.Name, date); + + if (content.CultureInfos.TryGetValue(culture, out var infos)) + SetCultureInfo(content, culture, infos.Name, date); + } + } + + // sets the edited cultures on the content + public static void SetCultureEdited(this IContent content, IEnumerable cultures) + { + if (cultures == null) + content.EditedCultures = null; + else + { + var editedCultures = new HashSet(cultures.Where(x => !x.IsNullOrWhiteSpace()), StringComparer.OrdinalIgnoreCase); + content.EditedCultures = editedCultures.Count > 0 ? editedCultures : null; + } + } + + public static void SetCultureInfo(this IContentBase content, string culture, string name, DateTime date) + { + if (name.IsNullOrWhiteSpace()) + throw new ArgumentNullOrEmptyException(nameof(name)); + + if (culture.IsNullOrWhiteSpace()) + throw new ArgumentNullOrEmptyException(nameof(culture)); + + content.CultureInfos.AddOrUpdate(culture, name, date); + } + + public static bool PublishCulture(this IContent content, string culture = "*") + { + culture = culture.NullOrWhiteSpaceAsNull(); + + // the variation should be supported by the content type properties + // if the content type is invariant, only '*' and 'null' is ok + // if the content type varies, everything is ok because some properties may be invariant + if (!content.ContentType.SupportsPropertyVariation(culture, "*", true)) + throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\"."); + + // the values we want to publish should be valid + if (content.ValidateProperties(culture).Any()) + return false; + + var alsoInvariant = false; + if (culture == "*") // all cultures + { + foreach (var c in content.AvailableCultures) + { + var name = content.GetCultureName(c); + if (string.IsNullOrWhiteSpace(name)) + return false; + content.SetPublishInfo(c, name, DateTime.Now); + } + } + else if (culture == null) // invariant culture + { + if (string.IsNullOrWhiteSpace(content.Name)) + return false; + // PublishName set by repository - nothing to do here + } + else // one single culture + { + var name = content.GetCultureName(culture); + if (string.IsNullOrWhiteSpace(name)) + return false; + content.SetPublishInfo(culture, name, DateTime.Now); + alsoInvariant = true; // we also want to publish invariant values + } + + // property.PublishValues only publishes what is valid, variation-wise + foreach (var property in content.Properties) + { + property.PublishValues(culture); + if (alsoInvariant) + property.PublishValues(null); + } + + content.PublishedState = PublishedState.Publishing; + return true; + } + + public static void UnpublishCulture(this IContent content, string culture = "*") + { + culture = culture.NullOrWhiteSpaceAsNull(); + + // the variation should be supported by the content type properties + if (!content.ContentType.SupportsPropertyVariation(culture, "*", true)) + throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\"."); + + if (culture == "*") // all cultures + content.ClearPublishInfos(); + else // one single culture + content.ClearPublishInfo(culture); + + // property.PublishValues only publishes what is valid, variation-wise + foreach (var property in content.Properties) + property.UnpublishValues(culture); + + content.PublishedState = PublishedState.Publishing; + } + + public static void ClearPublishInfos(this IContent content) + { + content.PublishCultureInfos = null; + } + + public static void ClearPublishInfo(this IContent content, string culture) + { + if (culture.IsNullOrWhiteSpace()) + throw new ArgumentNullOrEmptyException(nameof(culture)); + + content.PublishCultureInfos.Remove(culture); + + // set the culture to be dirty - it's been modified + content.TouchCultureInfo(culture); + } + + public static void TouchCultureInfo(this IContent content, string culture) + { + if (!content.CultureInfos.TryGetValue(culture, out var infos)) return; + content.CultureInfos.AddOrUpdate(culture, infos.Name, DateTime.Now); + } + } +} diff --git a/src/Umbraco.Core/Models/IContent.cs b/src/Umbraco.Core/Models/IContent.cs index f468df59ab..e953bef1eb 100644 --- a/src/Umbraco.Core/Models/IContent.cs +++ b/src/Umbraco.Core/Models/IContent.cs @@ -25,46 +25,46 @@ namespace Umbraco.Core.Models /// /// Gets a value indicating whether the content is published. /// - bool Published { get; } + bool Published { get; set; } - PublishedState PublishedState { get; } + PublishedState PublishedState { get; set; } /// /// Gets a value indicating whether the content has been edited. /// - bool Edited { get; } + bool Edited { get; set; } /// /// Gets the published version identifier. /// - int PublishedVersionId { get; } + int PublishedVersionId { get; set; } /// /// Gets a value indicating whether the content item is a blueprint. /// - bool Blueprint { get; } + bool Blueprint { get; set; } /// /// Gets the template id used to render the published version of the content. /// /// When editing the content, the template can change, but this will not until the content is published. - int? PublishTemplateId { get; } + int? PublishTemplateId { get; set; } /// /// Gets the name of the published version of the content. /// /// When editing the content, the name can change, but this will not until the content is published. - string PublishName { get; } + string PublishName { get; set; } /// /// Gets the identifier of the user who published the content. /// - int? PublisherId { get; } + int? PublisherId { get; set; } /// /// Gets the date and time the content was published. /// - DateTime? PublishDate { get; } + DateTime? PublishDate { get; set; } /// /// Gets the content type of this content. @@ -117,7 +117,7 @@ namespace Umbraco.Core.Models /// Because a dictionary key cannot be null this cannot get the invariant /// name, which must be get via the property. /// - IReadOnlyDictionary PublishCultureInfos { get; } + ContentCultureInfosCollection PublishCultureInfos { get; set; } /// /// Gets the published cultures. @@ -127,30 +127,13 @@ namespace Umbraco.Core.Models /// /// Gets the edited cultures. /// - IEnumerable EditedCultures { get; } + IEnumerable EditedCultures { get; set; } /// /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset /// /// IContent DeepCloneWithResetIdentities(); - - /// - /// Registers a culture to be published. - /// - /// A value indicating whether the culture can be published. - /// - /// Fails if properties don't pass variant validation rules. - /// Publishing must be finalized via the content service SavePublishing method. - /// - bool PublishCulture(string culture = "*"); - - /// - /// Registers a culture to be unpublished. - /// - /// - /// Unpublishing must be finalized via the content service SavePublishing method. - /// - void UnpublishCulture(string culture = "*"); + } } diff --git a/src/Umbraco.Core/Models/IContentBase.cs b/src/Umbraco.Core/Models/IContentBase.cs index 40a1c57097..b3cc266f0a 100644 --- a/src/Umbraco.Core/Models/IContentBase.cs +++ b/src/Umbraco.Core/Models/IContentBase.cs @@ -26,7 +26,7 @@ namespace Umbraco.Core.Models /// /// Gets the version identifier. /// - int VersionId { get; } + int VersionId { get; set; } /// /// Sets the name of the content item for a specified culture. @@ -57,8 +57,8 @@ namespace Umbraco.Core.Models /// Because a dictionary key cannot be null this cannot contain the invariant /// culture name, which must be get or set via the property. /// - IReadOnlyDictionary CultureInfos { get; } - + ContentCultureInfosCollection CultureInfos { get; set; } + /// /// Gets the available cultures. /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 9b4175b63f..3cf407d304 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -380,9 +380,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement content.AdjustDates(contentVersionDto.VersionDate); // names also impact 'edited' - foreach (var (culture, infos) in content.CultureInfos) - if (infos.Name != content.GetPublishName(culture)) - (editedCultures ?? (editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase))).Add(culture); + // ReSharper disable once UseDeconstruction + foreach (var cultureInfo in content.CultureInfos) + if (cultureInfo.Name != content.GetPublishName(cultureInfo.Culture)) + (editedCultures ?? (editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase))).Add(cultureInfo.Culture); // insert content variations Database.BulkInsertRecords(GetContentVariationDtos(content, publishing)); @@ -541,11 +542,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement content.AdjustDates(contentVersionDto.VersionDate); // names also impact 'edited' - foreach (var (culture, infos) in content.CultureInfos) - if (infos.Name != content.GetPublishName(culture)) + // ReSharper disable once UseDeconstruction + foreach (var cultureInfo in content.CultureInfos) + if (cultureInfo.Name != content.GetPublishName(cultureInfo.Culture)) { edited = true; - (editedCultures ?? (editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase))).Add(culture); + (editedCultures ?? (editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase))).Add(cultureInfo.Culture); // TODO: change tracking // at the moment, we don't do any dirty tracking on property values, so we don't know whether the @@ -951,6 +953,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #endregion + + protected override string ApplySystemOrdering(ref Sql sql, Ordering ordering) { // note: 'updater' is the user who created the latest draft version, @@ -1182,6 +1186,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return result; } + private void SetVariations(Content content, IDictionary> contentVariations, IDictionary> documentVariations) { if (contentVariations.TryGetValue(content.VersionId, out var contentVariation)) @@ -1191,8 +1196,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement foreach (var v in contentVariation) content.SetPublishInfo(v.Culture, v.Name, v.Date); if (documentVariations.TryGetValue(content.Id, out var documentVariation)) - foreach (var v in documentVariation.Where(x => x.Edited)) - content.SetCultureEdited(v.Culture); + content.SetCultureEdited(documentVariation.Where(x => x.Edited).Select(x => x.Culture)); } private IDictionary> GetContentVariations(List> temps) @@ -1262,14 +1266,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private IEnumerable GetContentVariationDtos(IContent content, bool publishing) { // create dtos for the 'current' (non-published) version, all cultures - foreach (var (culture, name) in content.CultureInfos) + // ReSharper disable once UseDeconstruction + foreach (var cultureInfo in content.CultureInfos) yield return new ContentVersionCultureVariationDto { VersionId = content.VersionId, - LanguageId = LanguageRepository.GetIdByIsoCode(culture) ?? throw new InvalidOperationException("Not a valid culture."), - Culture = culture, - Name = name.Name, - UpdateDate = content.GetUpdateDate(culture) ?? DateTime.MinValue // we *know* there is a value + LanguageId = LanguageRepository.GetIdByIsoCode(cultureInfo.Culture) ?? throw new InvalidOperationException("Not a valid culture."), + Culture = cultureInfo.Culture, + Name = cultureInfo.Name, + UpdateDate = content.GetUpdateDate(cultureInfo.Culture) ?? DateTime.MinValue // we *know* there is a value }; // if not publishing, we're just updating the 'current' (non-published) version, @@ -1277,14 +1282,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (!publishing) yield break; // create dtos for the 'published' version, for published cultures (those having a name) - foreach (var (culture, name) in content.PublishCultureInfos) + // ReSharper disable once UseDeconstruction + foreach (var cultureInfo in content.PublishCultureInfos) yield return new ContentVersionCultureVariationDto { VersionId = content.PublishedVersionId, - LanguageId = LanguageRepository.GetIdByIsoCode(culture) ?? throw new InvalidOperationException("Not a valid culture."), - Culture = culture, - Name = name.Name, - UpdateDate = content.GetPublishDate(culture) ?? DateTime.MinValue // we *know* there is a value + LanguageId = LanguageRepository.GetIdByIsoCode(cultureInfo.Culture) ?? throw new InvalidOperationException("Not a valid culture."), + Culture = cultureInfo.Culture, + Name = cultureInfo.Name, + UpdateDate = content.GetPublishDate(cultureInfo.Culture) ?? DateTime.MinValue // we *know* there is a value }; } @@ -1344,7 +1350,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement EnsureVariantNamesAreUnique(content, publishing); } - private void EnsureInvariantNameExists(Content content) + private void EnsureInvariantNameExists(IContent content) { if (content.ContentType.VariesByCulture()) { @@ -1358,7 +1364,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var defaultCulture = LanguageRepository.GetDefaultIsoCode(); content.Name = defaultCulture != null && content.CultureInfos.TryGetValue(defaultCulture, out var cultureName) ? cultureName.Name - : content.CultureInfos.First().Value.Name; + : content.CultureInfos[0].Name; } else { @@ -1368,7 +1374,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } } - private void EnsureInvariantNameIsUnique(Content content) + private void EnsureInvariantNameIsUnique(IContent content) { content.Name = EnsureUniqueNodeName(content.ParentId, content.Name, content.Id); } @@ -1405,22 +1411,22 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // of whether the name has changed (ie the culture has been updated) - some saving culture // fr-FR could cause culture en-UK name to change - not sure that is clean - foreach (var (culture, name) in content.CultureInfos) + foreach (var cultureInfo in content.CultureInfos) { - var langId = LanguageRepository.GetIdByIsoCode(culture); + var langId = LanguageRepository.GetIdByIsoCode(cultureInfo.Culture); if (!langId.HasValue) continue; if (!names.TryGetValue(langId.Value, out var cultureNames)) continue; // get a unique name var otherNames = cultureNames.Select(x => new SimilarNodeName { Id = x.Id, Name = x.Name }); - var uniqueName = SimilarNodeName.GetUniqueName(otherNames, 0, name.Name); + var uniqueName = SimilarNodeName.GetUniqueName(otherNames, 0, cultureInfo.Name); - if (uniqueName == content.GetCultureName(culture)) continue; + if (uniqueName == content.GetCultureName(cultureInfo.Culture)) continue; // update the name, and the publish name if published - content.SetCultureName(uniqueName, culture); - if (publishing && content.PublishCultureInfos.ContainsKey(culture)) - content.SetPublishInfo(culture, uniqueName, DateTime.Now); + content.SetCultureName(uniqueName, cultureInfo.Culture); + if (publishing && content.PublishCultureInfos.ContainsKey(cultureInfo.Culture)) + content.SetPublishInfo(cultureInfo.Culture, uniqueName, DateTime.Now); //TODO: This is weird, this call will have already been made in the SetCultureName } } diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 9c2721cbb4..a6ef1b9f36 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -776,7 +776,7 @@ namespace Umbraco.Core.Services.Implement //track the cultures that have changed var culturesChanging = content.ContentType.VariesByCulture() - ? content.CultureInfos.Where(x => x.Value.IsDirty()).Select(x => x.Key).ToList() + ? content.CultureInfos.Values.Where(x => x.IsDirty()).Select(x => x.Culture).ToList() : null; // TODO: Currently there's no way to change track which variant properties have changed, we only have change // tracking enabled on all values on the Property which doesn't allow us to know which variants have changed. @@ -1017,7 +1017,7 @@ namespace Umbraco.Core.Services.Implement IReadOnlyList culturesPublishing = null; IReadOnlyList culturesUnpublishing = null; IReadOnlyList culturesChanging = variesByCulture - ? content.CultureInfos.Where(x => x.Value.IsDirty()).Select(x => x.Key).ToList() + ? content.CultureInfos.Values.Where(x => x.IsDirty()).Select(x => x.Culture).ToList() : null; var isNew = !content.HasIdentity; @@ -1036,7 +1036,7 @@ namespace Umbraco.Core.Services.Implement culturesUnpublishing = content.GetCulturesUnpublishing(persisted); culturesPublishing = variesByCulture - ? content.PublishCultureInfos.Where(x => x.Value.IsDirty()).Select(x => x.Key).ToList() + ? content.PublishCultureInfos.Values.Where(x => x.IsDirty()).Select(x => x.Culture).ToList() : null; // ensure that the document can be published, and publish handling events, business rules, etc @@ -2043,7 +2043,7 @@ namespace Umbraco.Core.Services.Implement //track the cultures changing for auditing var culturesChanging = content.ContentType.VariesByCulture() - ? string.Join(",", content.CultureInfos.Where(x => x.Value.IsDirty()).Select(x => x.Key)) + ? string.Join(",", content.CultureInfos.Values.Where(x => x.IsDirty()).Select(x => x.Culture)) : null; // TODO: Currently there's no way to change track which variant properties have changed, we only have change @@ -2781,7 +2781,7 @@ namespace Umbraco.Core.Services.Implement content.WriterId = userId; var now = DateTime.Now; - var cultures = blueprint.CultureInfos.Any() ? blueprint.CultureInfos.Select(x=>x.Key) : ArrayOfOneNullString; + var cultures = blueprint.CultureInfos.Count > 0 ? blueprint.CultureInfos.Values.Select(x => x.Culture) : ArrayOfOneNullString; foreach (var culture in cultures) { foreach (var property in blueprint.Properties) diff --git a/src/Umbraco.Core/Services/Implement/NotificationService.cs b/src/Umbraco.Core/Services/Implement/NotificationService.cs index d981809364..2b21945ba8 100644 --- a/src/Umbraco.Core/Services/Implement/NotificationService.cs +++ b/src/Umbraco.Core/Services/Implement/NotificationService.cs @@ -345,8 +345,8 @@ namespace Umbraco.Core.Services.Implement { //Create the HTML based summary (ul of culture names) - var culturesChanged = content.CultureInfos.Where(x => x.Value.WasDirty()) - .Select(x => x.Key) + var culturesChanged = content.CultureInfos.Values.Where(x => x.WasDirty()) + .Select(x => x.Culture) .Select(_localizationService.GetLanguageByIsoCode) .WhereNotNull() .Select(x => x.CultureName); @@ -363,8 +363,8 @@ namespace Umbraco.Core.Services.Implement { //Create the text based summary (csv of culture names) - var culturesChanged = string.Join(", ", content.CultureInfos.Where(x => x.Value.WasDirty()) - .Select(x => x.Key) + var culturesChanged = string.Join(", ", content.CultureInfos.Values.Where(x => x.WasDirty()) + .Select(x => x.Culture) .Select(_localizationService.GetLanguageByIsoCode) .WhereNotNull() .Select(x => x.CultureName)); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index b327a9281d..13db7e2259 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -391,6 +391,7 @@ + diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index 2e98b31cca..c5dd8a6aea 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -437,16 +437,16 @@ namespace Umbraco.Tests.Models Assert.IsTrue(content.WasPropertyDirty("CultureInfos")); foreach(var culture in content.CultureInfos) { - Assert.IsTrue(culture.Value.WasDirty()); - Assert.IsTrue(culture.Value.WasPropertyDirty("Name")); - Assert.IsTrue(culture.Value.WasPropertyDirty("Date")); + Assert.IsTrue(culture.WasDirty()); + Assert.IsTrue(culture.WasPropertyDirty("Name")); + Assert.IsTrue(culture.WasPropertyDirty("Date")); } Assert.IsTrue(content.WasPropertyDirty("PublishCultureInfos")); foreach (var culture in content.PublishCultureInfos) { - Assert.IsTrue(culture.Value.WasDirty()); - Assert.IsTrue(culture.Value.WasPropertyDirty("Name")); - Assert.IsTrue(culture.Value.WasPropertyDirty("Date")); + Assert.IsTrue(culture.WasDirty()); + Assert.IsTrue(culture.WasPropertyDirty("Name")); + Assert.IsTrue(culture.WasPropertyDirty("Date")); } } diff --git a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs index f40c8a7d98..9c02fa040d 100644 --- a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs +++ b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs @@ -252,8 +252,8 @@ namespace Umbraco.Web.Macros if (_cultureInfos != null) return _cultureInfos; - return _cultureInfos = _inner.PublishCultureInfos - .ToDictionary(x => x.Key, x => new PublishedCultureInfo(x.Key, x.Value.Name, x.Value.Date)); + return _cultureInfos = _inner.PublishCultureInfos.Values + .ToDictionary(x => x.Culture, x => new PublishedCultureInfo(x.Culture, x.Name, x.Date)); } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index f470e5a65a..c503f382dc 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -1214,10 +1214,11 @@ namespace Umbraco.Web.PublishedCache.NuCache : document.CultureInfos) : content.CultureInfos; - foreach (var (culture, info) in infos) + // ReSharper disable once UseDeconstruction + foreach (var cultureInfo in infos) { - var cultureIsDraft = !published && content is IContent d && d.IsCultureEdited(culture); - cultureData[culture] = new CultureVariation { Name = info.Name, Date = content.GetUpdateDate(culture) ?? DateTime.MinValue, IsDraft = cultureIsDraft }; + var cultureIsDraft = !published && content is IContent d && d.IsCultureEdited(cultureInfo.Culture); + cultureData[cultureInfo.Culture] = new CultureVariation { Name = cultureInfo.Name, Date = content.GetUpdateDate(cultureInfo.Culture) ?? DateTime.MinValue, IsDraft = cultureIsDraft }; } }