2025-08-27 13:01:44 +02:00
using Microsoft.Extensions.Logging ;
using Umbraco.Cms.Core.Cache ;
using Umbraco.Cms.Core.Models ;
2024-09-10 00:49:18 +09:00
using Umbraco.Cms.Core.Models.PublishedContent ;
using Umbraco.Cms.Core.PublishedCache ;
2025-08-27 13:01:44 +02:00
using Umbraco.Extensions ;
2024-09-10 00:49:18 +09:00
namespace Umbraco.Cms.Infrastructure.HybridCache.Factories ;
2025-08-27 13:01:44 +02:00
/// <summary>
/// Defines a factory to create <see cref="IPublishedContent"/> and <see cref="IPublishedMember"/> from a <see cref="ContentCacheNode"/> or <see cref="IMember"/>.
/// </summary>
2025-07-21 08:32:54 +02:00
internal sealed class PublishedContentFactory : IPublishedContentFactory
2024-09-10 00:49:18 +09:00
{
private readonly IElementsCache _elementsCache ;
private readonly IVariationContextAccessor _variationContextAccessor ;
private readonly IPublishedContentTypeCache _publishedContentTypeCache ;
2025-08-27 13:01:44 +02:00
private readonly ILogger < PublishedContentFactory > _logger ;
private readonly AppCaches _appCaches ;
2024-09-10 00:49:18 +09:00
2025-08-27 13:01:44 +02:00
/// <summary>
/// Initializes a new instance of the <see cref="PublishedContentFactory"/> class.
/// </summary>
2024-09-10 00:49:18 +09:00
public PublishedContentFactory (
IElementsCache elementsCache ,
IVariationContextAccessor variationContextAccessor ,
2025-08-27 13:01:44 +02:00
IPublishedContentTypeCache publishedContentTypeCache ,
ILogger < PublishedContentFactory > logger ,
AppCaches appCaches )
2024-09-10 00:49:18 +09:00
{
_elementsCache = elementsCache ;
_variationContextAccessor = variationContextAccessor ;
_publishedContentTypeCache = publishedContentTypeCache ;
2025-08-27 13:01:44 +02:00
_logger = logger ;
_appCaches = appCaches ;
2024-09-10 00:49:18 +09:00
}
2025-08-27 13:01:44 +02:00
/// <inheritdoc/>
2024-09-10 00:49:18 +09:00
public IPublishedContent ? ToIPublishedContent ( ContentCacheNode contentCacheNode , bool preview )
{
2025-08-27 13:01:44 +02:00
var cacheKey = $"{nameof(PublishedContentFactory)}DocumentCache_{contentCacheNode.Id}_{preview}" ;
IPublishedContent ? publishedContent = _appCaches . RequestCache . GetCacheItem < IPublishedContent ? > ( cacheKey ) ;
if ( publishedContent is not null )
{
_logger . LogDebug (
"Using cached IPublishedContent for document {ContentCacheNodeName} ({ContentCacheNodeId})." ,
contentCacheNode . Data ? . Name ? ? "No Name" ,
contentCacheNode . Id ) ;
return publishedContent ;
}
_logger . LogDebug (
"Creating IPublishedContent for document {ContentCacheNodeName} ({ContentCacheNodeId})." ,
contentCacheNode . Data ? . Name ? ? "No Name" ,
contentCacheNode . Id ) ;
IPublishedContentType contentType =
_publishedContentTypeCache . Get ( PublishedItemType . Content , contentCacheNode . ContentTypeId ) ;
2024-09-10 00:49:18 +09:00
var contentNode = new ContentNode (
contentCacheNode . Id ,
contentCacheNode . Key ,
contentCacheNode . SortOrder ,
contentCacheNode . CreateDate ,
contentCacheNode . CreatorId ,
contentType ,
preview ? contentCacheNode . Data : null ,
preview ? null : contentCacheNode . Data ) ;
2025-08-27 13:01:44 +02:00
publishedContent = GetModel ( contentNode , preview ) ;
2024-09-10 00:49:18 +09:00
if ( preview )
{
2025-08-27 13:01:44 +02:00
publishedContent ? ? = GetPublishedContentAsDraft ( publishedContent ) ;
}
if ( publishedContent is not null )
{
_appCaches . RequestCache . Set ( cacheKey , publishedContent ) ;
2024-09-10 00:49:18 +09:00
}
2025-08-27 13:01:44 +02:00
return publishedContent ;
2024-09-10 00:49:18 +09:00
}
2025-08-27 13:01:44 +02:00
/// <inheritdoc/>
2024-09-10 00:49:18 +09:00
public IPublishedContent ? ToIPublishedMedia ( ContentCacheNode contentCacheNode )
{
2025-08-27 13:01:44 +02:00
var cacheKey = $"{nameof(PublishedContentFactory)}MediaCache_{contentCacheNode.Id}" ;
IPublishedContent ? publishedContent = _appCaches . RequestCache . GetCacheItem < IPublishedContent ? > ( cacheKey ) ;
if ( publishedContent is not null )
{
_logger . LogDebug (
"Using cached IPublishedContent for media {ContentCacheNodeName} ({ContentCacheNodeId})." ,
contentCacheNode . Data ? . Name ? ? "No Name" ,
contentCacheNode . Id ) ;
return publishedContent ;
}
_logger . LogDebug (
"Creating IPublishedContent for media {ContentCacheNodeName} ({ContentCacheNodeId})." ,
contentCacheNode . Data ? . Name ? ? "No Name" ,
contentCacheNode . Id ) ;
IPublishedContentType contentType =
_publishedContentTypeCache . Get ( PublishedItemType . Media , contentCacheNode . ContentTypeId ) ;
2024-09-10 00:49:18 +09:00
var contentNode = new ContentNode (
contentCacheNode . Id ,
contentCacheNode . Key ,
contentCacheNode . SortOrder ,
contentCacheNode . CreateDate ,
contentCacheNode . CreatorId ,
contentType ,
null ,
contentCacheNode . Data ) ;
2025-08-27 13:01:44 +02:00
publishedContent = GetModel ( contentNode , false ) ;
if ( publishedContent is not null )
{
_appCaches . RequestCache . Set ( cacheKey , publishedContent ) ;
}
return publishedContent ;
2024-09-10 00:49:18 +09:00
}
2025-08-27 13:01:44 +02:00
/// <inheritdoc/>
2024-09-10 00:49:18 +09:00
public IPublishedMember ToPublishedMember ( IMember member )
{
2025-08-27 13:01:44 +02:00
string cacheKey = $"{nameof(PublishedContentFactory)}MemberCache_{member.Id}" ;
IPublishedMember ? publishedMember = _appCaches . RequestCache . GetCacheItem < IPublishedMember ? > ( cacheKey ) ;
if ( publishedMember is not null )
{
_logger . LogDebug (
"Using cached IPublishedMember for member {MemberName} ({MemberId})." ,
member . Username ,
member . Id ) ;
return publishedMember ;
}
_logger . LogDebug (
"Creating IPublishedMember for member {MemberName} ({MemberId})." ,
member . Username ,
member . Id ) ;
IPublishedContentType contentType =
_publishedContentTypeCache . Get ( PublishedItemType . Member , member . ContentTypeId ) ;
2024-09-10 00:49:18 +09:00
2025-08-27 13:01:44 +02:00
// Members are only "mapped" never cached, so these default values are a bit weird, but they are not used.
2024-09-10 00:49:18 +09:00
var contentData = new ContentData (
member . Name ,
null ,
0 ,
member . UpdateDate ,
member . CreatorId ,
null ,
true ,
GetPropertyValues ( contentType , member ) ,
null ) ;
var contentNode = new ContentNode (
member . Id ,
member . Key ,
member . SortOrder ,
member . UpdateDate ,
member . CreatorId ,
contentType ,
null ,
contentData ) ;
2025-08-27 13:01:44 +02:00
publishedMember = new PublishedMember ( member , contentNode , _elementsCache , _variationContextAccessor ) ;
_appCaches . RequestCache . Set ( cacheKey , publishedMember ) ;
return publishedMember ;
2024-09-10 00:49:18 +09:00
}
2025-07-21 08:32:54 +02:00
private static Dictionary < string , PropertyData [ ] > GetPropertyValues ( IPublishedContentType contentType , IMember member )
2024-09-10 00:49:18 +09:00
{
var properties = member
. Properties
. ToDictionary (
x = > x . Alias ,
x = > new [ ] { new PropertyData { Value = x . GetValue ( ) , Culture = string . Empty , Segment = string . Empty } } ,
StringComparer . OrdinalIgnoreCase ) ;
// Add member properties
AddIf ( contentType , properties , nameof ( IMember . Email ) , member . Email ) ;
AddIf ( contentType , properties , nameof ( IMember . Username ) , member . Username ) ;
AddIf ( contentType , properties , nameof ( IMember . Comments ) , member . Comments ) ;
AddIf ( contentType , properties , nameof ( IMember . IsApproved ) , member . IsApproved ) ;
AddIf ( contentType , properties , nameof ( IMember . IsLockedOut ) , member . IsLockedOut ) ;
AddIf ( contentType , properties , nameof ( IMember . LastLockoutDate ) , member . LastLockoutDate ) ;
AddIf ( contentType , properties , nameof ( IMember . CreateDate ) , member . CreateDate ) ;
AddIf ( contentType , properties , nameof ( IMember . LastLoginDate ) , member . LastLoginDate ) ;
AddIf ( contentType , properties , nameof ( IMember . LastPasswordChangeDate ) , member . LastPasswordChangeDate ) ;
return properties ;
}
2025-07-21 08:32:54 +02:00
private static void AddIf ( IPublishedContentType contentType , IDictionary < string , PropertyData [ ] > properties , string alias , object? value )
2024-09-10 00:49:18 +09:00
{
IPublishedPropertyType ? propertyType = contentType . GetPropertyType ( alias ) ;
if ( propertyType = = null | | propertyType . IsUserProperty )
{
return ;
}
properties [ alias ] = new [ ] { new PropertyData { Value = value , Culture = string . Empty , Segment = string . Empty } } ;
}
private IPublishedContent ? GetModel ( ContentNode node , bool preview )
{
ContentData ? contentData = preview ? node . DraftModel : node . PublishedModel ;
return contentData = = null
? null
: new PublishedContent (
node ,
preview ,
_elementsCache ,
_variationContextAccessor ) ;
}
2025-07-21 08:32:54 +02:00
private static IPublishedContent ? GetPublishedContentAsDraft ( IPublishedContent ? content ) = >
2024-09-10 00:49:18 +09:00
content = = null ? null :
// an object in the cache is either an IPublishedContentOrMedia,
// or a model inheriting from PublishedContentExtended - in which
// case we need to unwrap to get to the original IPublishedContentOrMedia.
UnwrapIPublishedContent ( content ) ;
2025-07-21 08:32:54 +02:00
private static PublishedContent UnwrapIPublishedContent ( IPublishedContent content )
2024-09-10 00:49:18 +09:00
{
while ( content is PublishedContentWrapped wrapped )
{
content = wrapped . Unwrap ( ) ;
}
2025-08-27 13:01:44 +02:00
if ( content is not PublishedContent inner )
2024-09-10 00:49:18 +09:00
{
throw new InvalidOperationException ( "Innermost content is not PublishedContent." ) ;
}
return inner ;
}
}