Files
Umbraco-CMS/src/Umbraco.PublishedCache.NuCache/PublishedContent.cs

439 lines
15 KiB
C#
Raw Normal View History

using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Exceptions;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Infrastructure.PublishedCache.DataSource;
using Umbraco.Extensions;
2016-05-27 14:26:28 +02:00
namespace Umbraco.Cms.Infrastructure.PublishedCache;
2016-05-27 14:26:28 +02:00
internal class PublishedContent : PublishedContentBase
{
private readonly ContentNode _contentNode;
private readonly IPublishedModelFactory? _publishedModelFactory;
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
private readonly string? _urlSegment;
2016-05-27 14:26:28 +02:00
#region Content Type
2016-05-27 14:26:28 +02:00
/// <inheritdoc />
public override IPublishedContentType ContentType => _contentNode.ContentType;
2016-05-27 14:26:28 +02:00
#endregion
2016-05-27 14:26:28 +02:00
#region PublishedElement
2016-05-27 14:26:28 +02:00
/// <inheritdoc />
public override Guid Key => _contentNode.Uid;
2016-05-27 14:26:28 +02:00
#endregion
2016-05-27 14:26:28 +02:00
#region Constructors
2016-05-27 14:26:28 +02:00
public PublishedContent(
ContentNode contentNode,
ContentData contentData,
IPublishedSnapshotAccessor? publishedSnapshotAccessor,
IVariationContextAccessor? variationContextAccessor,
IPublishedModelFactory? publishedModelFactory)
: base(variationContextAccessor)
{
_contentNode = contentNode ?? throw new ArgumentNullException(nameof(contentNode));
ContentData = contentData ?? throw new ArgumentNullException(nameof(contentData));
_publishedSnapshotAccessor = publishedSnapshotAccessor ??
throw new ArgumentNullException(nameof(publishedSnapshotAccessor));
_publishedModelFactory = publishedModelFactory;
VariationContextAccessor = variationContextAccessor ??
throw new ArgumentNullException(nameof(variationContextAccessor));
_urlSegment = ContentData.UrlSegment;
IsPreviewing = ContentData.Published == false;
var properties = new IPublishedProperty[_contentNode.ContentType.PropertyTypes.Count()];
var i = 0;
foreach (IPublishedPropertyType propertyType in _contentNode.ContentType.PropertyTypes)
2016-05-27 14:26:28 +02:00
{
// add one property per property type - this is required, for the indexing to work
// if contentData supplies pdatas, use them, else use null
contentData.Properties.TryGetValue(propertyType.Alias, out PropertyData[]? pdatas); // else will be null
properties[i++] = new Property(propertyType, this, pdatas, _publishedSnapshotAccessor);
2016-05-27 14:26:28 +02:00
}
PropertiesArray = properties;
}
2016-05-27 14:26:28 +02:00
// used when cloning in ContentNode
public PublishedContent(
ContentNode contentNode,
PublishedContent origin,
IVariationContextAccessor variationContextAccessor)
: base(variationContextAccessor)
{
_contentNode = contentNode;
_publishedSnapshotAccessor = origin._publishedSnapshotAccessor;
VariationContextAccessor = origin.VariationContextAccessor;
ContentData = origin.ContentData;
_urlSegment = origin._urlSegment;
IsPreviewing = origin.IsPreviewing;
// here is the main benefit: we do not re-create properties so if anything
// is cached locally, we share the cache - which is fine - if anything depends
// on the tree structure, it should not be cached locally to begin with
PropertiesArray = origin.PropertiesArray;
}
2016-05-27 14:26:28 +02:00
// clone for previewing as draft a published content that is published and has no draft
private PublishedContent(PublishedContent origin)
: base(origin.VariationContextAccessor)
{
_publishedSnapshotAccessor = origin._publishedSnapshotAccessor;
VariationContextAccessor = origin.VariationContextAccessor;
_contentNode = origin._contentNode;
ContentData = origin.ContentData;
2018-04-28 16:34:43 +02:00
_urlSegment = origin._urlSegment;
IsPreviewing = true;
2018-04-28 16:34:43 +02:00
// clone properties so _isPreviewing is true
PropertiesArray = origin.PropertiesArray.Select(x => (IPublishedProperty)new Property((Property)x, this))
.ToArray();
}
2018-04-28 16:34:43 +02:00
#endregion
2016-05-27 14:26:28 +02:00
#region Get Content/Media for Parent/Children
2018-04-28 16:34:43 +02:00
// this is for tests purposes
// args are: current published snapshot (may be null), previewing, content id - returns: content
internal static Func<IPublishedSnapshot, bool, int, IPublishedContent?> GetContentByIdFunc { get; set; }
= (publishedShapshot, previewing, id) => publishedShapshot.Content?.GetById(previewing, id);
2018-04-28 16:34:43 +02:00
internal static Func<IPublishedSnapshot, bool, int, IPublishedContent?> GetMediaByIdFunc { get; set; }
= (publishedShapshot, previewing, id) => publishedShapshot.Media?.GetById(previewing, id);
2019-01-28 16:22:40 +01:00
private Func<IPublishedSnapshot, bool, int, IPublishedContent?> GetGetterById()
{
switch (ContentType.ItemType)
{
case PublishedItemType.Content:
return GetContentByIdFunc;
case PublishedItemType.Media:
return GetMediaByIdFunc;
default:
throw new PanicException("invalid item type");
}
}
2018-04-28 16:34:43 +02:00
#endregion
2018-04-28 16:34:43 +02:00
#region PublishedContent
2018-04-28 16:34:43 +02:00
internal ContentData ContentData { get; }
2018-04-28 16:34:43 +02:00
/// <inheritdoc />
public override int Id => _contentNode.Id;
2016-05-27 14:26:28 +02:00
/// <inheritdoc />
public override int SortOrder => _contentNode.SortOrder;
2018-04-28 16:34:43 +02:00
/// <inheritdoc />
public override int Level => _contentNode.Level;
2016-05-27 14:26:28 +02:00
/// <inheritdoc />
public override string Path => _contentNode.Path;
2018-04-28 16:34:43 +02:00
/// <inheritdoc />
public override int? TemplateId => ContentData.TemplateId;
2018-04-28 16:34:43 +02:00
/// <inheritdoc />
public override int CreatorId => _contentNode.CreatorId;
/// <inheritdoc />
public override DateTime CreateDate => _contentNode.CreateDate;
/// <inheritdoc />
public override int WriterId => ContentData.WriterId;
2019-06-07 11:15:58 +02:00
/// <inheritdoc />
public override DateTime UpdateDate => ContentData.VersionDate;
2019-03-05 11:15:30 +01:00
// ReSharper disable once CollectionNeverUpdated.Local
private static readonly IReadOnlyDictionary<string, PublishedCultureInfo> EmptyCultures =
new Dictionary<string, PublishedCultureInfo>();
2018-04-28 16:34:43 +02:00
private IReadOnlyDictionary<string, PublishedCultureInfo>? _cultures;
/// <inheritdoc />
public override IReadOnlyDictionary<string, PublishedCultureInfo> Cultures
{
get
{
if (_cultures != null)
{
return _cultures;
}
if (!ContentType.VariesByCulture())
{
return _cultures = new Dictionary<string, PublishedCultureInfo>
{
{ string.Empty, new PublishedCultureInfo(string.Empty, ContentData.Name, _urlSegment, CreateDate) },
};
}
if (ContentData.CultureInfos == null)
{
throw new PanicException("_contentDate.CultureInfos is null.");
}
return _cultures = ContentData.CultureInfos
.ToDictionary(
x => x.Key,
x => new PublishedCultureInfo(x.Key, x.Value.Name, x.Value.UrlSegment, x.Value.Date),
StringComparer.OrdinalIgnoreCase);
}
}
/// <inheritdoc />
public override PublishedItemType ItemType => _contentNode.ContentType.ItemType;
/// <inheritdoc />
public override bool IsDraft(string? culture = null)
{
// if this is the 'published' published content, nothing can be draft
if (ContentData.Published)
{
return false;
}
// not the 'published' published content, and does not vary = must be draft
if (!ContentType.VariesByCulture())
{
return true;
}
2016-05-27 14:26:28 +02:00
// handle context culture
if (culture == null)
{
culture = VariationContextAccessor?.VariationContext?.Culture ?? string.Empty;
}
2019-01-24 15:22:20 +01:00
// not the 'published' published content, and varies
// = depends on the culture
return ContentData.CultureInfos is not null &&
ContentData.CultureInfos.TryGetValue(culture, out CultureVariation? cvar) && cvar.IsDraft;
}
2019-01-28 14:04:31 +01:00
/// <inheritdoc />
public override bool IsPublished(string? culture = null)
{
// whether we are the 'draft' or 'published' content, need to determine whether
// there is a 'published' version for the specified culture (or at all, for
// invariant content items)
// if there is no 'published' published content, no culture can be published
if (!_contentNode.HasPublished)
{
return false;
}
// if there is a 'published' published content, and does not vary = published
if (!ContentType.VariesByCulture())
{
return true;
}
2016-05-27 14:26:28 +02:00
// handle context culture
if (culture == null)
{
culture = VariationContextAccessor?.VariationContext?.Culture ?? string.Empty;
}
2018-04-28 16:34:43 +02:00
// there is a 'published' published content, and varies
// = depends on the culture
return _contentNode.HasPublishedCulture(culture);
}
2018-04-28 16:34:43 +02:00
#endregion
#region Tree
/// <inheritdoc />
public override IPublishedContent? Parent
{
get
2016-05-27 14:26:28 +02:00
{
Func<IPublishedSnapshot, bool, int, IPublishedContent?> getById = GetGetterById();
IPublishedSnapshot publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot();
return getById(publishedSnapshot, IsPreviewing, ParentId);
2016-05-27 14:26:28 +02:00
}
}
2016-05-27 14:26:28 +02:00
/// <inheritdoc />
public override IEnumerable<IPublishedContent> ChildrenForAllCultures
{
get
2016-05-27 14:26:28 +02:00
{
Func<IPublishedSnapshot, bool, int, IPublishedContent?> getById = GetGetterById();
IPublishedSnapshot publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot();
var id = _contentNode.FirstChildContentId;
while (id > 0)
2019-04-22 17:51:07 +02:00
{
// is IsPreviewing is false, then this can return null
IPublishedContent? content = getById(publishedSnapshot, IsPreviewing, id);
if (content != null)
{
yield return content;
}
else
{
// but if IsPreviewing is true, we should have a child
if (IsPreviewing)
{
throw new PanicException($"failed to get content with id={id}");
}
// if IsPreviewing is false, get the unpublished child nevertheless
// we need it to keep enumerating children! but we don't return it
content = getById(publishedSnapshot, true, id);
if (content == null)
{
throw new PanicException($"failed to get content with id={id}");
}
}
2016-05-27 14:26:28 +02:00
var next = UnwrapIPublishedContent(content)._contentNode.NextSiblingContentId;
Merge commit '94d525d88f713b36419f28bfda4d82ee68637d83' into v9/dev # Conflicts: # build/NuSpecs/UmbracoCms.Web.nuspec # src/Umbraco.Core/Composing/Current.cs # src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs # src/Umbraco.Core/Runtime/CoreRuntime.cs # src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs # src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions.cs # src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs # src/Umbraco.Persistence.SqlCe/SqlCeSyntaxProvider.cs # src/Umbraco.PublishedCache.NuCache/DataSource/BTree.cs # src/Umbraco.PublishedCache.NuCache/DataSource/ContentCacheDataModel.cs # src/Umbraco.PublishedCache.NuCache/DataSource/ContentCacheDataSerializationResult.cs # src/Umbraco.PublishedCache.NuCache/DataSource/ContentCacheDataSerializerEntityType.cs # src/Umbraco.PublishedCache.NuCache/DataSource/ContentData.cs # src/Umbraco.PublishedCache.NuCache/DataSource/ContentNestedData.cs # src/Umbraco.PublishedCache.NuCache/DataSource/CultureVariation.cs # src/Umbraco.PublishedCache.NuCache/DataSource/IContentCacheDataSerializer.cs # src/Umbraco.PublishedCache.NuCache/DataSource/IContentCacheDataSerializerFactory.cs # src/Umbraco.PublishedCache.NuCache/DataSource/IDictionaryOfPropertyDataSerializer.cs # src/Umbraco.PublishedCache.NuCache/DataSource/JsonContentNestedDataSerializer.cs # src/Umbraco.PublishedCache.NuCache/DataSource/JsonContentNestedDataSerializerFactory.cs # src/Umbraco.PublishedCache.NuCache/DataSource/LazyCompressedString.cs # src/Umbraco.PublishedCache.NuCache/DataSource/MsgPackContentNestedDataSerializer.cs # src/Umbraco.PublishedCache.NuCache/DataSource/MsgPackContentNestedDataSerializerFactory.cs # src/Umbraco.PublishedCache.NuCache/DataSource/PropertyData.cs # src/Umbraco.PublishedCache.NuCache/NuCacheSerializerComponent.cs # src/Umbraco.PublishedCache.NuCache/NuCacheSerializerComposer.cs # src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs # src/Umbraco.Tests/App.config # src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs # src/Umbraco.Tests/PublishedContent/NuCacheTests.cs # src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml # src/Umbraco.Web.UI/web.Template.Debug.config # src/Umbraco.Web.UI/web.Template.config # src/Umbraco.Web/Composing/ModuleInjector.cs # src/Umbraco.Web/Editors/NuCacheStatusController.cs # src/Umbraco.Web/PublishedCache/NuCache/DataSource/ContentNestedData.cs # src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs # src/Umbraco.Web/PublishedCache/NuCache/NuCacheComposer.cs # src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs # src/Umbraco.Web/Runtime/WebRuntime.cs
2021-06-24 09:43:57 -06:00
#if DEBUG
// I've seen this happen but I think that may have been due to corrupt DB data due to my own
// bugs, but I'm leaving this here just in case we encounter it again while we're debugging.
if (next == id)
{
throw new PanicException($"The current content id {id} is the same as it's next sibling id {next}");
}
Merge commit '94d525d88f713b36419f28bfda4d82ee68637d83' into v9/dev # Conflicts: # build/NuSpecs/UmbracoCms.Web.nuspec # src/Umbraco.Core/Composing/Current.cs # src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs # src/Umbraco.Core/Runtime/CoreRuntime.cs # src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs # src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions.cs # src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs # src/Umbraco.Persistence.SqlCe/SqlCeSyntaxProvider.cs # src/Umbraco.PublishedCache.NuCache/DataSource/BTree.cs # src/Umbraco.PublishedCache.NuCache/DataSource/ContentCacheDataModel.cs # src/Umbraco.PublishedCache.NuCache/DataSource/ContentCacheDataSerializationResult.cs # src/Umbraco.PublishedCache.NuCache/DataSource/ContentCacheDataSerializerEntityType.cs # src/Umbraco.PublishedCache.NuCache/DataSource/ContentData.cs # src/Umbraco.PublishedCache.NuCache/DataSource/ContentNestedData.cs # src/Umbraco.PublishedCache.NuCache/DataSource/CultureVariation.cs # src/Umbraco.PublishedCache.NuCache/DataSource/IContentCacheDataSerializer.cs # src/Umbraco.PublishedCache.NuCache/DataSource/IContentCacheDataSerializerFactory.cs # src/Umbraco.PublishedCache.NuCache/DataSource/IDictionaryOfPropertyDataSerializer.cs # src/Umbraco.PublishedCache.NuCache/DataSource/JsonContentNestedDataSerializer.cs # src/Umbraco.PublishedCache.NuCache/DataSource/JsonContentNestedDataSerializerFactory.cs # src/Umbraco.PublishedCache.NuCache/DataSource/LazyCompressedString.cs # src/Umbraco.PublishedCache.NuCache/DataSource/MsgPackContentNestedDataSerializer.cs # src/Umbraco.PublishedCache.NuCache/DataSource/MsgPackContentNestedDataSerializerFactory.cs # src/Umbraco.PublishedCache.NuCache/DataSource/PropertyData.cs # src/Umbraco.PublishedCache.NuCache/NuCacheSerializerComponent.cs # src/Umbraco.PublishedCache.NuCache/NuCacheSerializerComposer.cs # src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs # src/Umbraco.Tests/App.config # src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs # src/Umbraco.Tests/PublishedContent/NuCacheTests.cs # src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml # src/Umbraco.Web.UI/web.Template.Debug.config # src/Umbraco.Web.UI/web.Template.config # src/Umbraco.Web/Composing/ModuleInjector.cs # src/Umbraco.Web/Editors/NuCacheStatusController.cs # src/Umbraco.Web/PublishedCache/NuCache/DataSource/ContentNestedData.cs # src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs # src/Umbraco.Web/PublishedCache/NuCache/NuCacheComposer.cs # src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs # src/Umbraco.Web/Runtime/WebRuntime.cs
2021-06-24 09:43:57 -06:00
#endif
id = next;
2019-04-22 17:51:07 +02:00
}
2016-05-27 14:26:28 +02:00
}
}
2016-05-27 14:26:28 +02:00
#endregion
2018-04-28 16:34:43 +02:00
#region Properties
2018-04-28 16:34:43 +02:00
/// <inheritdoc cref="IPublishedElement.Properties" />
public override IEnumerable<IPublishedProperty> Properties => PropertiesArray;
2018-04-28 16:34:43 +02:00
/// <inheritdoc cref="IPublishedElement.GetProperty(string)" />
public override IPublishedProperty? GetProperty(string alias)
{
var index = _contentNode.ContentType.GetPropertyIndex(alias);
if (index < 0)
{
return null; // happens when 'alias' does not match a content type property alias
}
2016-05-27 14:26:28 +02:00
// should never happen - properties array must be in sync with property type
if (index >= PropertiesArray.Length)
2016-05-27 14:26:28 +02:00
{
throw new IndexOutOfRangeException(
"Index points outside the properties array, which means the properties array is corrupt.");
2016-05-27 14:26:28 +02:00
}
IPublishedProperty property = PropertiesArray[index];
return property;
}
2016-05-30 19:54:36 +02:00
#endregion
2016-05-30 19:54:36 +02:00
#region Caching
2016-05-30 19:54:36 +02:00
// beware what you use that one for - you don't want to cache its result
private IAppCache? GetAppropriateCache()
{
IPublishedSnapshot? publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot();
IAppCache? cache = publishedSnapshot == null
? null
: (IsPreviewing == false || PublishedSnapshotService.FullCacheWhenPreviewing) &&
ContentType.ItemType != PublishedItemType.Member
? publishedSnapshot.ElementsCache
: publishedSnapshot.SnapshotCache;
return cache;
}
private IAppCache? GetCurrentSnapshotCache()
{
IPublishedSnapshot? publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot();
return publishedSnapshot?.SnapshotCache;
}
#endregion
2016-05-27 14:26:28 +02:00
#region Internal
2016-05-27 14:26:28 +02:00
// used by property
internal IVariationContextAccessor VariationContextAccessor { get; }
2016-05-27 14:26:28 +02:00
// used by navigable content
internal IPublishedProperty[] PropertiesArray { get; }
// used by navigable content
internal int ParentId => _contentNode.ParentContentId;
2016-05-27 14:26:28 +02:00
// used by navigable content
// includes all children, published or unpublished
// NavigableNavigator takes care of selecting those it wants
// note: this is not efficient - we do not try to be (would require a double-linked list)
internal IList<int>? ChildIds => Children?.Select(x => x.Id).ToList();
2016-05-27 14:26:28 +02:00
// used by Property
// gets a value indicating whether the content or media exists in
// a previewing context or not, ie whether its Parent, Children, and
// properties should refer to published, or draft content
internal bool IsPreviewing { get; }
2016-05-27 14:26:28 +02:00
private string? _asPreviewingCacheKey;
2016-05-27 14:26:28 +02:00
private string AsPreviewingCacheKey =>
_asPreviewingCacheKey ?? (_asPreviewingCacheKey = CacheKeys.PublishedContentAsPreviewing(Key));
2016-05-27 14:26:28 +02:00
// used by ContentCache
internal IPublishedContent? AsDraft()
{
if (IsPreviewing)
{
return this;
}
2016-05-27 14:26:28 +02:00
IAppCache? cache = GetAppropriateCache();
if (cache == null)
2016-05-27 14:26:28 +02:00
{
return new PublishedContent(this).CreateModel(_publishedModelFactory);
}
return (IPublishedContent?)cache.Get(AsPreviewingCacheKey, () => new PublishedContent(this).CreateModel(_publishedModelFactory));
}
2016-05-27 14:26:28 +02:00
// used by Navigable.Source,...
internal static PublishedContent UnwrapIPublishedContent(IPublishedContent content)
{
while (content is PublishedContentWrapped wrapped)
{
content = wrapped.Unwrap();
2016-05-27 14:26:28 +02:00
}
if (!(content is PublishedContent inner))
2016-05-27 14:26:28 +02:00
{
throw new InvalidOperationException("Innermost content is not PublishedContent.");
2016-05-27 14:26:28 +02:00
}
return inner;
2016-05-27 14:26:28 +02:00
}
#endregion
2016-05-27 14:26:28 +02:00
}