diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index a6482b48ab..8ff648553c 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -117,7 +117,7 @@ namespace Umbraco.Core.Models.PublishedContent /// the cultures that are published. For a draft content, those that are 'available' ie /// have a non-empty content name. /// - IReadOnlyList Cultures { get; } + IReadOnlyCollection Cultures { get; } /// /// Gets a value indicating whether the content is draft. @@ -164,8 +164,8 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Gets children that are available for the specified culture. /// Children are sorted by their sortOrder. + /// The '*' culture and supported and returns everything. /// - // FIXME: can culture be '*'? IEnumerable Children(string culture = null); #endregion diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs index 5bac22ad24..b2feec38c6 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -100,7 +100,7 @@ namespace Umbraco.Core.Models.PublishedContent public DateTime CultureDate(string culture = null) => _content.CultureDate(culture); /// - public IReadOnlyList Cultures => _content.Cultures; + public IReadOnlyCollection Cultures => _content.Cultures; /// public virtual bool IsDraft(string culture = null) => _content.IsDraft(culture); diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs index cd362cadc0..811851224c 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/DictionaryPublishedContent.cs @@ -157,7 +157,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache // ReSharper disable once CollectionNeverUpdated.Local private static readonly List EmptyListOfString = new List(); - public override IReadOnlyList Cultures => EmptyListOfString; + public override IReadOnlyCollection Cultures => EmptyListOfString; public override string UrlSegment(string culture = null) => _urlName; diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs index bc9ab8010d..7d24eec1e9 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedContent.cs @@ -140,7 +140,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache // ReSharper disable once CollectionNeverUpdated.Local private static readonly List EmptyListOfString = new List(); - public override IReadOnlyList Cultures => EmptyListOfString; + public override IReadOnlyCollection Cultures => EmptyListOfString; public override string WriterName { diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index 8b954d6ab0..5e024c2a72 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -285,7 +285,7 @@ namespace Umbraco.Tests.Published public override int SortOrder { get; } public override string Name(string culture = null) => default; public override DateTime CultureDate(string culture = null) => throw new NotSupportedException(); - public override IReadOnlyList Cultures => throw new NotSupportedException(); + public override IReadOnlyCollection Cultures => throw new NotSupportedException(); public override string UrlSegment(string culture = null) => default; public override string WriterName { get; } public override string CreatorName { get; } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs index 077dfd3c94..e4bf02fec0 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs @@ -200,7 +200,7 @@ namespace Umbraco.Tests.PublishedContent public string Name(string culture = null) => _names.TryGetValue(culture ?? "", out var name) ? name : null; public void SetName(string name, string culture = null) => _names[culture ?? ""] = name; public DateTime CultureDate(string culture = null) => throw new NotSupportedException(); - public IReadOnlyList Cultures => throw new NotSupportedException(); + public IReadOnlyCollection Cultures => throw new NotSupportedException(); public string UrlSegment(string culture = null) => _urlSegments.TryGetValue(culture ?? "", out var urlSegment) ? urlSegment : null; public void SetUrlSegment(string urlSegment, string culture = null) => _urlSegments[culture ?? ""] = urlSegment; public string WriterName { get; set; } diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index d61dead9c2..23aa93bb8d 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -184,7 +184,7 @@ namespace Umbraco.Tests.PublishedContent public string Name(string culture = null) => _names.TryGetValue(culture ?? "", out var name) ? name : null; public void SetName(string name, string culture = null) => _names[culture ?? ""] = name; public DateTime CultureDate(string culture = null) => throw new NotSupportedException(); - public IReadOnlyList Cultures => throw new NotSupportedException(); + public IReadOnlyCollection Cultures => throw new NotSupportedException(); public string UrlSegment(string culture = null) => _urlSegments.TryGetValue(culture ?? "", out var urlSegment) ? urlSegment : null; public void SetUrlSegment(string urlSegment, string culture = null) => _urlSegments[culture ?? ""] = urlSegment; public string WriterName { get; set; } diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs index 8c631bb067..1fb090e220 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs @@ -36,7 +36,7 @@ namespace Umbraco.Tests.TestHelpers.Stubs // get return _cultures.TryGetValue(culture, out var date) ? date : DateTime.MinValue; } - public IReadOnlyList Cultures { get; set; } + public IReadOnlyCollection Cultures { get; set; } public string UrlSegment(string culture = null) => _urlSegments.TryGetValue(culture ?? "", out var urlSegment) ? urlSegment : null; public void SetUrlSegment(string urlSegment, string culture = null) => _urlSegments[culture ?? ""] = urlSegment; public string DocumentTypeAlias => ContentType.Alias; diff --git a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs index ebeafd2c06..cf2dca0b64 100644 --- a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs +++ b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs @@ -248,7 +248,7 @@ namespace Umbraco.Web.Macros private static readonly List EmptyListOfString = new List(); private IReadOnlyList _cultures; - public IReadOnlyList Cultures + public IReadOnlyCollection Cultures { get { diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index f29dc48d2b..086fae8b16 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -12,7 +12,7 @@ namespace Umbraco.Web.Models /// /// This base class does which (a) consistently resolves and caches the Url, (b) provides an implementation /// for this[alias], and (c) provides basic content set management. - [DebuggerDisplay("Content Id: {Id}, Name: {Name}")] + [DebuggerDisplay("Content Id: {Id}}")] public abstract class PublishedContentBase : IPublishedContent { protected PublishedContentBase(IUmbracoContextAccessor umbracoContextAccessor) @@ -138,7 +138,7 @@ namespace Umbraco.Web.Models public abstract DateTime CultureDate(string culture = null); /// - public abstract IReadOnlyList Cultures { get; } + public abstract IReadOnlyCollection Cultures { get; } /// public abstract bool IsDraft(string culture = null); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs index e422c04f72..89de144403 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs @@ -246,28 +246,9 @@ namespace Umbraco.Web.PublishedCache.NuCache public override IEnumerable GetAtRoot(bool preview) { - if (PublishedSnapshotService.CacheContentCacheRoots == false) - return GetAtRootNoCache(preview); - - var cache = preview == false || PublishedSnapshotService.FullCacheWhenPreviewing - ? _elementsCache - : _snapshotCache; - - if (cache == null) - return GetAtRootNoCache(preview); - - // note: ToArray is important here, we want to cache the result, not the function! - return (IEnumerable)cache.Get( - CacheKeys.ContentCacheRoots(preview), - () => GetAtRootNoCache(preview).ToArray()); - } - - private IEnumerable GetAtRootNoCache(bool preview) - { - var c = _snapshot.GetAtRoot(); - // both .Draft and .Published cannot be null at the same time - return c.Select(n => GetNodePublishedContent(n, preview)).WhereNotNull().OrderBy(x => x.SortOrder); + // root is already sorted by sortOrder, and does not contain nulls + return _snapshot.GetAtRoot().Select(n => GetNodePublishedContent(n, preview)); } private static IPublishedContent GetNodePublishedContent(ContentNode node, bool preview) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs index 58e60cd8ad..7c9a739448 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.PublishedCache.NuCache.DataSource; @@ -10,13 +11,17 @@ namespace Umbraco.Web.PublishedCache.NuCache { // special ctor for root pseudo node public ContentNode() - { } + { + FirstChildContentId = -1; + NextSiblingContentId = -1; + } // special ctor with no content data - for members public ContentNode(int id, Guid uid, IPublishedContentType contentType, int level, string path, int sortOrder, int parentContentId, DateTime createDate, int creatorId) + : this() { Id = id; Uid = uid; @@ -25,8 +30,6 @@ namespace Umbraco.Web.PublishedCache.NuCache Path = path; SortOrder = sortOrder; ParentContentId = parentContentId; - FirstChildContentId = -1; - NextSiblingContentId = -1; CreateDate = createDate; CreatorId = creatorId; } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 5b54bb7345..9b1955fe43 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -508,7 +508,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // new, add to parent AddNodeLocked(kit.Node); } - else if (moving) + else if (moving || existing.SortOrder != kit.Node.SortOrder) { // moved, remove existing from its parent, add content to its parent RemoveNodeLocked(existing); @@ -562,7 +562,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - // IMPORTANT kits must be sorted out by LEVEL + // IMPORTANT kits must be sorted out by LEVEL and by SORT ORDER public void SetBranch(int rootContentId, IEnumerable kits) { var lockInfo = new WriteLockInfo(); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/BTree.DictionaryOfCultureVariationSerializer.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/BTree.DictionaryOfCultureVariationSerializer.cs index 958f6302fa..4521311302 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/BTree.DictionaryOfCultureVariationSerializer.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/BTree.DictionaryOfCultureVariationSerializer.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using CSharpTest.Net.Serialization; using Umbraco.Core; @@ -14,7 +15,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource if (pcount == 0) return Empty; // read each variation - var dict = new Dictionary(); + var dict = new Dictionary(StringComparer.InvariantCultureIgnoreCase); for (var i = 0; i < pcount; i++) { var languageId = PrimitiveSerializer.String.ReadFrom(stream); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/BTree.DictionaryOfPropertyDataSerializer.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/BTree.DictionaryOfPropertyDataSerializer.cs index 19c8beedb5..aa5dc9eb30 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/BTree.DictionaryOfPropertyDataSerializer.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/BTree.DictionaryOfPropertyDataSerializer.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using CSharpTest.Net.Serialization; using Umbraco.Core; @@ -9,7 +10,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource { public IDictionary ReadFrom(Stream stream) { - var dict = new Dictionary(); + var dict = new Dictionary(StringComparer.InvariantCultureIgnoreCase); // read properties count var pcount = PrimitiveSerializer.Int32.ReadFrom(stream); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index 3ededddba3..0a55049f9f 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -116,14 +116,18 @@ namespace Umbraco.Web.PublishedCache.NuCache internal static Func GetMediaByIdFunc { get; set; } = (publishedShapshot, previewing, id) => publishedShapshot.Media.GetById(previewing, id); - private IPublishedContent GetContentById(bool previewing, int id) + private Func GetGetterById(PublishedItemType itemType) { - return GetContentByIdFunc(_publishedSnapshotAccessor.PublishedSnapshot, previewing, id); - } + switch (ContentType.ItemType) + { + case PublishedItemType.Content: + return GetContentByIdFunc; + case PublishedItemType.Media: + return GetMediaByIdFunc; + default: + throw new Exception("panic: invalid item type"); + } - private IPublishedContent GetMediaById(bool previewing, int id) - { - return GetMediaByIdFunc(_publishedSnapshotAccessor.PublishedSnapshot, previewing, id); } #endregion @@ -226,17 +230,17 @@ namespace Umbraco.Web.PublishedCache.NuCache // ReSharper disable once CollectionNeverUpdated.Local private static readonly List EmptyListOfString = new List(); - private IReadOnlyList _cultures; + private IReadOnlyCollection _cultures; /// - public override IReadOnlyList Cultures + public override IReadOnlyCollection Cultures { get { if (!ContentType.VariesByCulture()) return EmptyListOfString; - return _cultures ?? (_cultures = ContentData.CultureInfos.Keys.ToList()); + return _cultures ?? (_cultures = new HashSet(ContentData.CultureInfos.Keys, StringComparer.OrdinalIgnoreCase)); } } @@ -292,49 +296,36 @@ namespace Umbraco.Web.PublishedCache.NuCache /// public override IPublishedContent Parent() { - // have to use the "current" cache because a PublishedContent can be shared - // amongst many snapshots and other content depend on the snapshots - switch (_contentNode.ContentType.ItemType) - { - case PublishedItemType.Content: - return GetContentById(IsPreviewing, _contentNode.ParentContentId); - case PublishedItemType.Media: - return GetMediaById(IsPreviewing, _contentNode.ParentContentId); - default: - throw new Exception($"Panic: unsupported item type \"{_contentNode.ContentType.ItemType}\"."); - } + var getById = GetGetterById(ContentType.ItemType); + var publishedSnapshot = _publishedSnapshotAccessor.PublishedSnapshot; + return getById(publishedSnapshot, IsPreviewing, ParentId); } /// public override IEnumerable Children(string culture = null) { - Func getById; + // invariant has invariant value (whatever the requested culture) + if (!ContentType.VariesByCulture() && culture != "*") + culture = ""; - switch (ContentType.ItemType) - { - case PublishedItemType.Content: - getById = GetContentByIdFunc; - break; - case PublishedItemType.Media: - getById = GetMediaByIdFunc; - break; - default: - throw new Exception("panic: invalid item type"); - } + // handle context culture for variant + if (culture == null) + culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; + var getById = GetGetterById(ContentType.ItemType); var publishedSnapshot = _publishedSnapshotAccessor.PublishedSnapshot; var id = _contentNode.FirstChildContentId; while (id > 0) { - var content = (PublishedContent) getById(publishedSnapshot, IsPreviewing, id); + var content = getById(publishedSnapshot, IsPreviewing, id); if (content == null) throw new Exception("panic: failed to get content"); - if (content.IsInvariantOrHasCulture(culture)) + if (culture == "*" || content.IsInvariantOrHasCulture(culture)) yield return content; - id = content._contentNode.NextSiblingContentId; + id = UnwrapIPublishedContent(content)._contentNode.NextSiblingContentId; } } @@ -422,11 +413,9 @@ namespace Umbraco.Web.PublishedCache.NuCache // used by Navigable.Source,... internal static PublishedContent UnwrapIPublishedContent(IPublishedContent content) { - PublishedContentWrapped wrapped; - while ((wrapped = content as PublishedContentWrapped) != null) + while (content is PublishedContentWrapped wrapped) content = wrapped.Unwrap(); - var inner = content as PublishedContent; - if (inner == null) + if (!(content is PublishedContent inner)) throw new InvalidOperationException("Innermost content is not PublishedContent."); return inner; } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index ce19764fb6..7c8b69f9b2 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -649,7 +649,7 @@ namespace Umbraco.Web.PublishedCache.NuCache if (capture.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch)) { // ?? should we do some RV check here? - // IMPORTANT GetbranchContentSources sorts kits by level + // IMPORTANT GetbranchContentSources sorts kits by level and by sort order var kits = _dataSource.GetBranchContentSources(scope, capture.Id); _contentStore.SetBranch(capture.Id, kits); } @@ -741,7 +741,7 @@ namespace Umbraco.Web.PublishedCache.NuCache if (capture.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch)) { // ?? should we do some RV check here? - // IMPORTANT GetbranchContentSources sorts kits by level + // IMPORTANT GetbranchContentSources sorts kits by level and by sort order var kits = _dataSource.GetBranchMediaSources(scope, capture.Id); _mediaStore.SetBranch(capture.Id, kits); } diff --git a/src/Umbraco.Web/PublishedCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/PublishedMember.cs index f0c3ac4f5b..eec973c182 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedMember.cs @@ -142,7 +142,7 @@ namespace Umbraco.Web.PublishedCache public override DateTime CultureDate(string culture = null) => throw new NotSupportedException(); - public override IReadOnlyList Cultures => throw new NotSupportedException(); + public override IReadOnlyCollection Cultures => throw new NotSupportedException(); public override string UrlSegment(string culture = null) => throw new NotSupportedException(); diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 2e8e532a58..b31527eb57 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -172,9 +172,12 @@ namespace Umbraco.Web /// /// Culture is case-insensitive. public static bool HasCulture(this IPublishedContent content, string culture) - => content.Cultures.Contains(culture ?? string.Empty); // fixme oops?! + => content.Cultures.Contains(culture ?? string.Empty); - // fixme + /// + /// Determines whether the content is invariant, or has a culture. + /// + /// Culture is case-insensitive. public static bool IsInvariantOrHasCulture(this IPublishedContent content, string culture) => !content.ContentType.VariesByCulture() || content.Cultures.Contains(culture ?? ""); @@ -895,24 +898,6 @@ namespace Umbraco.Web #region Axes: children - // FIXME: kill that one - /// - /// Gets the children of the content. - /// - /// The content. - /// The specific culture to filter for. If null is used the current culture is used. (Default is null) - /// The children of the content. - /// - /// Children are sorted by their sortOrder. - /// This method exists for consistency, it is the same as calling content.Children as a property. - /// - public static IEnumerable Children(this IPublishedContent content, string culture = null) - { - if (content == null) throw new ArgumentNullException(nameof(content)); - - return content.Children(culture); //.WhereIsInvariantOrHasCulture(culture); - } - /// /// Gets the children of the content, filtered by a predicate. ///