2016-05-27 14:26:28 +02:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using Umbraco.Core ;
using Umbraco.Core.Cache ;
2018-04-24 01:31:01 +10:00
using Umbraco.Core.Models ;
2016-05-27 14:26:28 +02:00
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
{
2016-06-09 19:38:51 +02:00
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 ;
// ReSharper disable once InconsistentNaming
internal readonly ContentData _contentData ; // internal for ContentNode cloning
private readonly string _urlName ;
2018-04-24 01:31:01 +10:00
private IReadOnlyDictionary < string , PublishedCultureName > _cultureNames ;
2016-05-27 14:26:28 +02:00
#region Constructors
2017-10-31 12:48:24 +01:00
public PublishedContent ( ContentNode contentNode , ContentData contentData , IPublishedSnapshotAccessor publishedSnapshotAccessor )
2016-05-27 14:26:28 +02:00
{
_contentNode = contentNode ;
_contentData = contentData ;
2017-10-31 12:48:24 +01:00
_publishedSnapshotAccessor = publishedSnapshotAccessor ;
2016-05-27 14:26:28 +02:00
_urlName = _contentData . Name . ToUrlSegment ( ) ;
2016-05-30 19:54:36 +02:00
IsPreviewing = _contentData . Published = = false ;
2016-06-10 16:37:28 +02:00
2017-12-07 13:22:32 +01:00
var properties = new List < IPublishedProperty > ( ) ;
foreach ( var propertyType in _contentNode . ContentType . PropertyTypes )
{
2018-01-10 12:48:51 +01:00
if ( contentData . Properties . TryGetValue ( propertyType . Alias , out var pdatas ) )
2018-04-24 17:28:57 +10:00
{
2017-12-07 13:22:32 +01:00
properties . Add ( new Property ( propertyType , this , pdatas , _publishedSnapshotAccessor ) ) ;
2018-04-24 17:28:57 +10:00
}
else
{
//it doesn't exist in our serialized json but we should add it as an empty property so they are in sync
properties . Add ( new Property ( propertyType , this , null , _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 )
2018-04-24 01:31:01 +10:00
: ( string ) cache . GetCacheItem ( CacheKeys . ProfileName ( id ) , ( ) = > GetProfileNameByIdNoCache ( id ) ) ;
2016-05-27 14:26:28 +02:00
}
private static string GetProfileNameByIdNoCache ( int id )
{
#if DEBUG
2016-09-01 19:06:08 +02:00
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
2016-09-01 19:06:08 +02:00
// 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)
2016-06-10 16:37:28 +02:00
public PublishedContent ( ContentNode contentNode , PublishedContent origin )
2016-05-27 14:26:28 +02:00
{
_contentNode = contentNode ;
2017-10-31 12:48:24 +01:00
_publishedSnapshotAccessor = origin . _publishedSnapshotAccessor ;
2016-05-27 14:26:28 +02:00
_contentData = origin . _contentData ;
_urlName = origin . _urlName ;
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
2016-06-10 16:37:28 +02:00
private PublishedContent ( PublishedContent origin )
2016-05-27 14:26:28 +02:00
{
2017-10-31 12:48:24 +01:00
_publishedSnapshotAccessor = origin . _publishedSnapshotAccessor ;
2016-05-27 14:26:28 +02:00
_contentNode = origin . _contentNode ;
_contentData = origin . _contentData ;
_urlName = origin . _urlName ;
2016-05-30 19:54:36 +02:00
IsPreviewing = true ;
2016-05-27 14:26:28 +02:00
// clone properties so _isPreviewing is true
2018-04-24 01:31:01 +10:00
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
2017-10-31 12:48:24 +01:00
internal static Func < IPublishedShapshot , 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
2017-10-31 12:48:24 +01:00
internal static Func < IPublishedShapshot , 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
#region IPublishedContent
2016-05-30 19:54:36 +02:00
public override int Id = > _contentNode . Id ;
public override Guid Key = > _contentNode . Uid ;
public override int DocumentTypeId = > _contentNode . ContentType . Id ;
public override string DocumentTypeAlias = > _contentNode . ContentType . Alias ;
public override PublishedItemType ItemType = > _contentNode . ContentType . ItemType ;
2016-05-27 14:26:28 +02:00
2016-05-30 19:54:36 +02:00
public override string Name = > _contentData . Name ;
2018-04-24 01:31:01 +10:00
public override IReadOnlyDictionary < string , PublishedCultureName > CultureNames
{
get
{
if ( ! ContentType . Variations . HasFlag ( ContentVariation . CultureNeutral ) )
return null ;
if ( _cultureNames = = null )
{
var d = new Dictionary < string , PublishedCultureName > ( ) ;
foreach ( var c in _contentData . CultureInfos )
{
d [ c . Key ] = new PublishedCultureName ( c . Value . Name , c . Value . Name . ToUrlSegment ( ) ) ;
}
_cultureNames = d ;
}
return _cultureNames ;
}
}
2016-05-30 19:54:36 +02:00
public override int Level = > _contentNode . Level ;
public override string Path = > _contentNode . Path ;
public override int SortOrder = > _contentNode . SortOrder ;
public override int TemplateId = > _contentData . TemplateId ;
2016-05-27 14:26:28 +02:00
2016-05-30 19:54:36 +02:00
public override string UrlName = > _urlName ;
2016-05-27 14:26:28 +02:00
2016-05-30 19:54:36 +02:00
public override DateTime CreateDate = > _contentNode . CreateDate ;
public override DateTime UpdateDate = > _contentData . VersionDate ;
2016-05-27 14:26:28 +02:00
2016-05-30 19:54:36 +02:00
public override int CreatorId = > _contentNode . CreatorId ;
public override string CreatorName = > GetProfileNameById ( _contentNode . CreatorId ) ;
public override int WriterId = > _contentData . WriterId ;
public override string WriterName = > GetProfileNameById ( _contentData . WriterId ) ;
2016-05-27 14:26:28 +02:00
2016-05-30 19:54:36 +02:00
public override bool IsDraft = > _contentData . Published = = false ;
2016-05-27 14:26:28 +02:00
public override IPublishedContent Parent
{
get
{
// 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 :
2016-05-30 19:54:36 +02:00
return GetContentById ( IsPreviewing , _contentNode . ParentContentId ) ;
2016-05-27 14:26:28 +02:00
case PublishedItemType . Media :
2016-05-30 19:54:36 +02:00
return GetMediaById ( IsPreviewing , _contentNode . ParentContentId ) ;
2016-05-27 14:26:28 +02:00
default :
2018-01-26 17:55:20 +01:00
throw new Exception ( $"Panic: unsupported item type \" { _contentNode . ContentType . ItemType } \ "." ) ;
2016-05-27 14:26:28 +02:00
}
}
}
private string _childrenCacheKey ;
2016-05-30 19:54:36 +02:00
private string ChildrenCacheKey = > _childrenCacheKey ? ? ( _childrenCacheKey = CacheKeys . PublishedContentChildren ( Key , IsPreviewing ) ) ;
2016-05-27 14:26:28 +02:00
public override IEnumerable < IPublishedContent > Children
{
get
{
2016-06-10 16:37:28 +02:00
var cache = GetAppropriateCache ( ) ;
2017-10-31 12:48:24 +01:00
if ( cache = = null | | PublishedSnapshotService . CachePublishedContentChildren = = false )
2016-05-27 14:26:28 +02:00
return GetChildren ( ) ;
2016-06-10 16:37:28 +02:00
2016-05-27 14:26:28 +02:00
// note: ToArray is important here, we want to cache the result, not the function!
2018-04-24 01:31:01 +10:00
return ( IEnumerable < IPublishedContent > ) cache . GetCacheItem ( ChildrenCacheKey , ( ) = > GetChildren ( ) . ToArray ( ) ) ;
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" ) ;
}
2016-06-10 16:37:28 +02:00
return c . OrderBy ( x = > x . SortOrder ) ;
2016-05-27 14:26:28 +02:00
// notes:
// _contentNode.ChildContentIds is an unordered int[]
// need needs to fetch & sort - do it only once, lazyily, though
// Q: perfs-wise, is it better than having the store managed an ordered list
}
2016-06-10 16:37:28 +02:00
public override IEnumerable < IPublishedProperty > Properties = > PropertiesArray ;
2016-05-27 14:26:28 +02:00
public override IPublishedProperty GetProperty ( string alias )
{
var index = _contentNode . ContentType . GetPropertyIndex ( alias ) ;
2018-04-24 17:11:09 +10:00
if ( index < 0 ) return null ;
2018-04-24 17:28:57 +10:00
//fixme: This should not happen since we align the PropertiesArray with the property types in the ctor, if this does happen maybe we should throw a descriptive exception
2018-04-24 17:15:35 +10:00
if ( index > = PropertiesArray . Length ) return null ;
2018-04-24 17:11:09 +10:00
var property = PropertiesArray [ index ] ;
2016-05-27 14:26:28 +02:00
return property ;
}
public override IPublishedProperty GetProperty ( string alias , bool recurse )
{
var property = GetProperty ( alias ) ;
if ( recurse = = false ) return property ;
2016-06-10 16:37:28 +02:00
var cache = GetAppropriateCache ( ) ;
2016-05-27 14:26:28 +02:00
if ( cache = = null )
return base . GetProperty ( alias , true ) ;
2018-04-24 01:31:01 +10:00
var key = ( ( Property ) property ) . RecurseCacheKey ;
return ( Property ) cache . GetCacheItem ( key , ( ) = > base . GetProperty ( alias , true ) ) ;
2016-05-27 14:26:28 +02:00
}
2016-05-30 19:54:36 +02:00
public override PublishedContentType ContentType = > _contentNode . ContentType ;
#endregion
#region Caching
// beware what you use that one for - you don't want to cache its result
2016-06-10 16:37:28 +02:00
private ICacheProvider GetAppropriateCache ( )
2016-05-30 19:54:36 +02:00
{
2018-04-24 01:31:01 +10:00
var publishedSnapshot = ( PublishedShapshot ) _publishedSnapshotAccessor . PublishedSnapshot ;
2017-10-31 12:48:24 +01:00
var cache = publishedSnapshot = = null
2016-05-30 19:54:36 +02:00
? null
2017-10-31 12:48:24 +01:00
: ( ( IsPreviewing = = false | | PublishedSnapshotService . FullCacheWhenPreviewing ) & & ( ItemType ! = PublishedItemType . Member )
? publishedSnapshot . ElementsCache
: publishedSnapshot . SnapshotCache ) ;
2016-05-30 19:54:36 +02:00
return cache ;
}
2017-10-31 12:48:24 +01:00
private ICacheProvider GetCurrentSnapshotCache ( )
2016-05-27 14:26:28 +02:00
{
2018-04-24 01:31:01 +10:00
var publishedSnapshot = ( PublishedShapshot ) _publishedSnapshotAccessor . PublishedSnapshot ;
2017-10-31 12:48:24 +01:00
return publishedSnapshot ? . SnapshotCache ;
2016-05-27 14:26:28 +02:00
}
#endregion
#region Internal
// 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 AsPreviewingModel ( )
{
2016-05-30 19:54:36 +02:00
if ( IsPreviewing )
2016-05-27 14:26:28 +02:00
return this ;
2016-06-10 16:37:28 +02:00
var cache = GetAppropriateCache ( ) ;
if ( cache = = null ) return new PublishedContent ( this ) . CreateModel ( ) ;
2018-04-24 01:31:01 +10:00
return ( IPublishedContent ) cache . GetCacheItem ( AsPreviewingCacheKey , ( ) = > new PublishedContent ( this ) . 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
}
}