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

480 lines
19 KiB
C#
Raw Normal View History

2016-05-27 14:26:28 +02:00
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Models.PublishedContent;
2017-05-30 18:13:11 +02:00
using Umbraco.Web.Composing;
2016-05-27 14:26:28 +02:00
using Umbraco.Web.Models;
using Umbraco.Web.PublishedCache.NuCache.DataSource;
namespace Umbraco.Web.PublishedCache.NuCache
{
internal class PublishedContent : PublishedContentBase
2016-05-27 14:26:28 +02:00
{
2017-10-31 12:48:24 +01:00
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
2016-05-27 14:26:28 +02:00
private readonly ContentNode _contentNode;
2018-04-28 16:34:43 +02:00
private readonly string _urlSegment;
2016-05-27 14:26:28 +02:00
#region Constructors
public PublishedContent(
ContentNode contentNode,
ContentData contentData,
IPublishedSnapshotAccessor publishedSnapshotAccessor,
IVariationContextAccessor variationContextAccessor,
2019-02-15 19:32:13 +01:00
IUmbracoContextAccessor umbracoContextAccessor)
: base(umbracoContextAccessor)
2016-05-27 14:26:28 +02:00
{
2019-02-15 19:32:13 +01:00
_contentNode = contentNode ?? throw new ArgumentNullException(nameof(contentNode));
ContentData = contentData ?? throw new ArgumentNullException(nameof(contentData));
_publishedSnapshotAccessor = publishedSnapshotAccessor ?? throw new ArgumentNullException(nameof(publishedSnapshotAccessor));
VariationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor));
2016-05-27 14:26:28 +02:00
2019-03-05 11:15:30 +01:00
_urlSegment = ContentData.UrlSegment;
2019-01-28 16:22:40 +01:00
IsPreviewing = ContentData.Published == false;
2017-12-07 13:22:32 +01:00
var properties = new List<IPublishedProperty>();
foreach (var propertyType in _contentNode.ContentType.PropertyTypes)
{
2018-04-25 15:55:27 +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 var pdatas); // else will be null
properties.Add(new Property(propertyType, this, pdatas, _publishedSnapshotAccessor));
2017-12-07 13:22:32 +01:00
}
PropertiesArray = properties.ToArray();
2016-05-27 14:26:28 +02:00
}
2016-05-30 19:54:36 +02:00
private string GetProfileNameById(int id)
2016-05-27 14:26:28 +02:00
{
2017-10-31 12:48:24 +01:00
var cache = GetCurrentSnapshotCache();
2016-05-27 14:26:28 +02:00
return cache == null
? GetProfileNameByIdNoCache(id)
2019-01-17 11:01:23 +01:00
: (string)cache.Get(CacheKeys.ProfileName(id), () => GetProfileNameByIdNoCache(id));
2016-05-27 14:26:28 +02:00
}
private static string GetProfileNameByIdNoCache(int id)
{
#if DEBUG
var userService = Current.Services?.UserService;
2016-05-27 14:26:28 +02:00
if (userService == null) return "[null]"; // for tests
#else
// we don't want each published content to hold a reference to the service
// so where should they get the service from really? from the locator...
var userService = Current.Services.UserService;
2016-05-27 14:26:28 +02:00
#endif
var user = userService.GetProfileById(id);
2016-05-30 19:54:36 +02:00
return user?.Name;
2016-05-27 14:26:28 +02:00
}
// (see ContentNode.CloneParent)
public PublishedContent(
ContentNode contentNode,
PublishedContent origin,
2019-02-15 19:32:13 +01:00
IUmbracoContextAccessor umbracoContextAccessor)
: base(umbracoContextAccessor)
2016-05-27 14:26:28 +02:00
{
_contentNode = contentNode;
2017-10-31 12:48:24 +01:00
_publishedSnapshotAccessor = origin._publishedSnapshotAccessor;
2018-04-30 21:29:49 +02:00
VariationContextAccessor = origin.VariationContextAccessor;
2019-01-28 16:22:40 +01:00
ContentData = origin.ContentData;
2016-05-27 14:26:28 +02:00
2018-04-28 16:34:43 +02:00
_urlSegment = origin._urlSegment;
2016-05-30 19:54:36 +02:00
IsPreviewing = origin.IsPreviewing;
2016-05-27 14:26:28 +02:00
// 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
2016-05-30 19:54:36 +02:00
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,
2019-02-15 19:32:13 +01:00
IUmbracoContextAccessor umbracoContextAccessor)
: base(umbracoContextAccessor)
2016-05-27 14:26:28 +02:00
{
2017-10-31 12:48:24 +01:00
_publishedSnapshotAccessor = origin._publishedSnapshotAccessor;
2018-04-30 21:29:49 +02:00
VariationContextAccessor = origin.VariationContextAccessor;
2016-05-27 14:26:28 +02:00
_contentNode = origin._contentNode;
2019-01-28 16:22:40 +01:00
ContentData = origin.ContentData;
2016-05-27 14:26:28 +02:00
2018-04-28 16:34:43 +02:00
_urlSegment = origin._urlSegment;
2016-05-30 19:54:36 +02:00
IsPreviewing = true;
2016-05-27 14:26:28 +02:00
// clone properties so _isPreviewing is true
PropertiesArray = origin.PropertiesArray.Select(x => (IPublishedProperty)new Property((Property)x, this)).ToArray();
2016-05-27 14:26:28 +02:00
}
#endregion
#region Get Content/Media for Parent/Children
// this is for tests purposes
2017-10-31 12:48:24 +01:00
// args are: current published snapshot (may be null), previewing, content id - returns: content
2016-05-27 14:26:28 +02:00
2018-04-27 11:38:50 +10:00
internal static Func<IPublishedSnapshot, bool, int, IPublishedContent> GetContentByIdFunc { get; set; }
2017-10-31 12:50:30 +01:00
= (publishedShapshot, previewing, id) => publishedShapshot.Content.GetById(previewing, id);
2016-05-27 14:26:28 +02:00
2018-04-27 11:38:50 +10:00
internal static Func<IPublishedSnapshot, bool, int, IPublishedContent> GetMediaByIdFunc { get; set; }
2017-10-31 12:50:30 +01:00
= (publishedShapshot, previewing, id) => publishedShapshot.Media.GetById(previewing, id);
2016-05-27 14:26:28 +02:00
2016-05-30 19:54:36 +02:00
private IPublishedContent GetContentById(bool previewing, int id)
2016-05-27 14:26:28 +02:00
{
2017-10-31 12:48:24 +01:00
return GetContentByIdFunc(_publishedSnapshotAccessor.PublishedSnapshot, previewing, id);
2016-05-27 14:26:28 +02:00
}
2016-05-30 19:54:36 +02:00
private IEnumerable<IPublishedContent> GetContentByIds(bool previewing, IEnumerable<int> ids)
2016-05-27 14:26:28 +02:00
{
2017-10-31 12:48:24 +01:00
var publishedSnapshot = _publishedSnapshotAccessor.PublishedSnapshot;
2016-05-27 14:26:28 +02:00
// beware! the loop below CANNOT be converted to query such as:
2017-10-31 12:48:24 +01:00
//return ids.Select(x => _getContentByIdFunc(publishedSnapshot, previewing, x)).Where(x => x != null);
// because it would capture the published snapshot and cause all sorts of issues
2016-05-27 14:26:28 +02:00
//
2017-10-31 12:48:24 +01:00
// we WANT to get the actual current published snapshot each time we run
2016-05-27 14:26:28 +02:00
// ReSharper disable once LoopCanBeConvertedToQuery
foreach (var id in ids)
{
2017-10-31 12:48:24 +01:00
var content = GetContentByIdFunc(publishedSnapshot, previewing, id);
2016-05-27 14:26:28 +02:00
if (content != null) yield return content;
}
}
2016-05-30 19:54:36 +02:00
private IPublishedContent GetMediaById(bool previewing, int id)
2016-05-27 14:26:28 +02:00
{
2017-10-31 12:48:24 +01:00
return GetMediaByIdFunc(_publishedSnapshotAccessor.PublishedSnapshot, previewing, id);
2016-05-27 14:26:28 +02:00
}
2016-05-30 19:54:36 +02:00
private IEnumerable<IPublishedContent> GetMediaByIds(bool previewing, IEnumerable<int> ids)
2016-05-27 14:26:28 +02:00
{
2017-10-31 12:48:24 +01:00
var publishedShapshot = _publishedSnapshotAccessor.PublishedSnapshot;
2016-05-27 14:26:28 +02:00
// see note above for content
// ReSharper disable once LoopCanBeConvertedToQuery
foreach (var id in ids)
{
2017-10-31 12:48:24 +01:00
var content = GetMediaByIdFunc(publishedShapshot, previewing, id);
2016-05-27 14:26:28 +02:00
if (content != null) yield return content;
}
}
#endregion
2018-04-28 16:34:43 +02:00
#region Content Type
2016-05-27 14:26:28 +02:00
2018-04-28 16:34:43 +02:00
/// <inheritdoc />
2019-04-15 13:04:14 +02:00
public override IPublishedContentType ContentType => _contentNode.ContentType;
2018-04-28 16:34:43 +02:00
#endregion
#region PublishedElement
/// <inheritdoc />
2016-05-30 19:54:36 +02:00
public override Guid Key => _contentNode.Uid;
2016-05-27 14:26:28 +02:00
2018-04-28 16:34:43 +02:00
#endregion
#region PublishedContent
2019-01-28 16:22:40 +01:00
internal ContentData ContentData { get; }
2018-04-28 16:34:43 +02:00
/// <inheritdoc />
public override int Id => _contentNode.Id;
/// <inheritdoc />
2019-04-16 16:25:10 +02:00
public override string Name(string culture = null)
{
// invariant has invariant value (whatever the requested culture)
if (!ContentType.VariesByCulture())
return ContentData.Name;
// handle context culture for variant
2019-04-16 16:25:10 +02:00
if (culture == null)
culture = VariationContextAccessor?.VariationContext?.Culture ?? "";
// get
return culture != "" && ContentData.CultureInfos.TryGetValue(culture, out var infos) ? infos.Name : null;
2018-04-28 16:34:43 +02:00
}
/// <inheritdoc />
public override string UrlSegment(string culture = null)
2018-04-28 16:34:43 +02:00
{
// invariant has invariant value (whatever the requested culture)
if (!ContentType.VariesByCulture())
return _urlSegment;
// handle context culture for variant
if (culture == null)
culture = VariationContextAccessor?.VariationContext?.Culture ?? "";
2018-04-28 16:34:43 +02:00
// get
return ContentData.CultureInfos.TryGetValue(culture, out var infos) ? infos.UrlSegment : null;
}
2018-04-28 16:34:43 +02:00
/// <inheritdoc />
public override int SortOrder => _contentNode.SortOrder;
/// <inheritdoc />
2016-05-30 19:54:36 +02:00
public override int Level => _contentNode.Level;
2018-04-28 16:34:43 +02:00
/// <inheritdoc />
2016-05-30 19:54:36 +02:00
public override string Path => _contentNode.Path;
2018-04-28 16:34:43 +02:00
/// <inheritdoc />
2019-01-28 16:22:40 +01:00
public override int? TemplateId => ContentData.TemplateId;
2016-05-27 14:26:28 +02:00
2018-04-28 16:34:43 +02:00
/// <inheritdoc />
public override int CreatorId => _contentNode.CreatorId;
/// <inheritdoc />
public override string CreatorName => GetProfileNameById(_contentNode.CreatorId);
2016-05-27 14:26:28 +02:00
2018-04-28 16:34:43 +02:00
/// <inheritdoc />
2016-05-30 19:54:36 +02:00
public override DateTime CreateDate => _contentNode.CreateDate;
2016-05-27 14:26:28 +02:00
2018-04-28 16:34:43 +02:00
/// <inheritdoc />
2019-01-28 16:22:40 +01:00
public override int WriterId => ContentData.WriterId;
2018-04-28 16:34:43 +02:00
/// <inheritdoc />
2019-01-28 16:22:40 +01:00
public override string WriterName => GetProfileNameById(ContentData.WriterId);
2016-05-27 14:26:28 +02:00
2018-04-28 16:34:43 +02:00
/// <inheritdoc />
2019-01-28 16:22:40 +01:00
public override DateTime UpdateDate => ContentData.VersionDate;
2018-04-28 16:34:43 +02:00
/// <inheritdoc />
public override DateTime CultureDate(string culture = null)
2018-04-28 16:34:43 +02:00
{
// invariant has invariant value (whatever the requested culture)
if (!ContentType.VariesByCulture())
return UpdateDate;
// handle context culture for variant
if (culture == null)
culture = VariationContextAccessor?.VariationContext?.Culture ?? "";
2018-04-28 16:34:43 +02:00
// get
return culture != "" && ContentData.CultureInfos.TryGetValue(culture, out var infos) ? infos.Date : DateTime.MinValue;
2018-04-28 16:34:43 +02:00
}
// ReSharper disable once CollectionNeverUpdated.Local
private static readonly List<string> EmptyListOfString = new List<string>();
private IReadOnlyList<string> _cultures;
2018-04-28 16:34:43 +02:00
/// <inheritdoc />
public override IReadOnlyList<string> Cultures
2018-04-28 16:34:43 +02:00
{
get
{
2018-06-20 14:18:57 +02:00
if (!ContentType.VariesByCulture())
return EmptyListOfString;
2019-03-05 11:15:30 +01:00
return _cultures ?? (_cultures = ContentData.CultureInfos.Keys.ToList());
2018-04-28 16:34:43 +02:00
}
}
/// <inheritdoc />
public override bool IsDraft(string culture = null)
{
// if this is the 'published' published content, nothing can be draft
2019-01-28 16:22:40 +01:00
if (ContentData.Published)
return false;
// not the 'published' published content, and does not vary = must be draft
if (!ContentType.VariesByCulture())
return true;
// handle context culture
if (culture == null)
culture = VariationContextAccessor?.VariationContext?.Culture ?? "";
// not the 'published' published content, and varies
// = depends on the culture
2019-01-28 16:22:40 +01:00
return ContentData.CultureInfos.TryGetValue(culture, out var cvar) && cvar.IsDraft;
}
2016-05-27 14:26:28 +02:00
2019-01-24 15:22:20 +01:00
/// <inheritdoc />
public override bool IsPublished(string culture = null)
{
2019-01-28 14:04:31 +01:00
// 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)
2019-01-24 15:22:20 +01:00
2019-01-28 14:04:31 +01:00
// if there is no 'published' published content, no culture can be published
2019-01-28 20:07:22 +01:00
var hasPublished = _contentNode.PublishedContent != null;
2019-01-28 14:04:31 +01:00
if (!hasPublished)
return false;
// if there is a 'published' published content, and does not vary = published
if (!ContentType.VariesByCulture())
2019-01-28 14:04:31 +01:00
return true;
// handle context culture
if (culture == null)
culture = VariationContextAccessor?.VariationContext?.Culture ?? "";
2019-01-28 14:04:31 +01:00
// there is a 'published' published content, and varies
// = depends on the culture
2019-01-28 16:22:40 +01:00
return _contentNode.PublishedContent.ContentData.CultureInfos.ContainsKey(culture);
}
2016-05-27 14:26:28 +02:00
2018-04-28 16:34:43 +02:00
#endregion
#region Tree
/// <inheritdoc />
2019-04-22 11:59:06 +02:00
public override IPublishedContent Parent()
2016-05-27 14:26:28 +02:00
{
2019-04-22 11:59:06 +02:00
// 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)
2016-05-27 14:26:28 +02:00
{
2019-04-22 11:59:06 +02:00
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}\".");
2016-05-27 14:26:28 +02:00
}
}
2018-04-28 16:34:43 +02:00
/// <inheritdoc />
2019-04-22 15:39:24 +02:00
public override IEnumerable<IPublishedContent> Children(string culture = null)
2016-05-27 14:26:28 +02:00
{
2019-04-22 15:39:24 +02:00
// FIXME THIS CANNOT WORK
// we cannot cache children this way, they should be a linked list!
throw new NotImplementedException();
2019-04-22 15:39:24 +02:00
var cache = GetAppropriateCache();
if (cache == null || PublishedSnapshotService.CachePublishedContentChildren == false)
return GetChildren();
// note: ToArray is important here, we want to cache the result, not the function!
return (IEnumerable<IPublishedContent>)cache.Get(ChildrenCacheKey, () => GetChildren().ToArray());
2016-05-27 14:26:28 +02:00
}
2018-04-28 16:34:43 +02:00
private string _childrenCacheKey;
private string ChildrenCacheKey => _childrenCacheKey ?? (_childrenCacheKey = CacheKeys.PublishedContentChildren(Key, IsPreviewing));
2016-05-27 14:26:28 +02:00
private IEnumerable<IPublishedContent> GetChildren()
{
IEnumerable<IPublishedContent> c;
switch (_contentNode.ContentType.ItemType)
{
case PublishedItemType.Content:
2016-05-30 19:54:36 +02:00
c = GetContentByIds(IsPreviewing, _contentNode.ChildContentIds);
2016-05-27 14:26:28 +02:00
break;
case PublishedItemType.Media:
2016-05-30 19:54:36 +02:00
c = GetMediaByIds(IsPreviewing, _contentNode.ChildContentIds);
2016-05-27 14:26:28 +02:00
break;
default:
throw new Exception("oops");
}
return c.OrderBy(x => x.SortOrder);
2016-05-27 14:26:28 +02:00
// notes:
// _contentNode.ChildContentIds is an unordered int[]
// needs to fetch & sort - do it only once, lazily, though
2016-05-27 14:26:28 +02:00
// Q: perfs-wise, is it better than having the store managed an ordered list
}
2018-04-28 16:34:43 +02:00
#endregion
#region Properties
/// <inheritdoc cref="IPublishedElement.Properties"/>
public override IEnumerable<IPublishedProperty> Properties => PropertiesArray;
2016-05-27 14:26:28 +02:00
2018-04-28 16:34:43 +02:00
/// <inheritdoc cref="IPublishedElement.GetProperty(string)"/>
2016-05-27 14:26:28 +02:00
public override IPublishedProperty GetProperty(string alias)
{
var index = _contentNode.ContentType.GetPropertyIndex(alias);
2018-04-25 15:55:27 +02:00
if (index < 0) return null; // happens when 'alias' does not match a content type property alias
if (index >= PropertiesArray.Length) // should never happen - properties array must be in sync with property type
throw new IndexOutOfRangeException("Index points outside the properties array, which means the properties array is corrupt.");
var property = PropertiesArray[index];
2016-05-27 14:26:28 +02:00
return property;
}
2016-05-30 19:54:36 +02:00
#endregion
#region Caching
// beware what you use that one for - you don't want to cache its result
2019-01-17 11:01:23 +01:00
private IAppCache GetAppropriateCache()
2016-05-30 19:54:36 +02:00
{
2018-04-27 11:38:50 +10:00
var publishedSnapshot = (PublishedSnapshot)_publishedSnapshotAccessor.PublishedSnapshot;
2017-10-31 12:48:24 +01:00
var cache = publishedSnapshot == null
2016-05-30 19:54:36 +02:00
? null
2019-04-16 21:15:13 +02:00
: ((IsPreviewing == false || PublishedSnapshotService.FullCacheWhenPreviewing) && (ContentType.ItemType != PublishedItemType.Member)
2017-10-31 12:48:24 +01:00
? publishedSnapshot.ElementsCache
: publishedSnapshot.SnapshotCache);
2016-05-30 19:54:36 +02:00
return cache;
}
2019-01-17 11:01:23 +01:00
private IAppCache GetCurrentSnapshotCache()
2016-05-27 14:26:28 +02:00
{
2018-04-27 11:38:50 +10:00
var publishedSnapshot = (PublishedSnapshot)_publishedSnapshotAccessor.PublishedSnapshot;
2017-10-31 12:48:24 +01:00
return publishedSnapshot?.SnapshotCache;
2016-05-27 14:26:28 +02:00
}
#endregion
#region Internal
2018-05-02 09:31:30 +02:00
// used by property
2018-04-30 21:29:49 +02:00
internal IVariationContextAccessor VariationContextAccessor { get; }
2016-05-27 14:26:28 +02:00
// used by navigable content
2016-05-30 19:54:36 +02:00
internal IPublishedProperty[] PropertiesArray { get; }
2016-05-27 14:26:28 +02:00
// used by navigable content
2016-05-30 19:54:36 +02:00
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
2016-05-30 19:54:36 +02:00
internal IList<int> ChildIds => _contentNode.ChildContentIds;
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
2016-05-30 19:54:36 +02:00
internal bool IsPreviewing { get; }
2016-05-27 14:26:28 +02:00
private string _asPreviewingCacheKey;
2016-05-30 19:54:36 +02:00
private string AsPreviewingCacheKey => _asPreviewingCacheKey ?? (_asPreviewingCacheKey = CacheKeys.PublishedContentAsPreviewing(Key));
2016-05-27 14:26:28 +02:00
// used by ContentCache
internal IPublishedContent AsDraft()
2016-05-27 14:26:28 +02:00
{
2016-05-30 19:54:36 +02:00
if (IsPreviewing)
2016-05-27 14:26:28 +02:00
return this;
var cache = GetAppropriateCache();
2019-02-15 19:32:13 +01:00
if (cache == null) return new PublishedContent(this, UmbracoContextAccessor).CreateModel();
return (IPublishedContent)cache.Get(AsPreviewingCacheKey, () => new PublishedContent(this, UmbracoContextAccessor).CreateModel());
2016-05-27 14:26:28 +02:00
}
// used by Navigable.Source,...
internal static PublishedContent UnwrapIPublishedContent(IPublishedContent content)
{
PublishedContentWrapped wrapped;
while ((wrapped = content as PublishedContentWrapped) != null)
content = wrapped.Unwrap();
var inner = content as PublishedContent;
if (inner == null)
throw new InvalidOperationException("Innermost content is not PublishedContent.");
return inner;
}
#endregion
}
}