2024-09-27 09:12:19 +02:00
using Microsoft.Extensions.DependencyInjection ;
using Umbraco.Cms.Core.DependencyInjection ;
2021-03-12 21:48:24 +01:00
using Umbraco.Cms.Core.Events ;
2021-02-18 11:06:02 +01:00
using Umbraco.Cms.Core.Models ;
2021-05-11 14:33:49 +02:00
using Umbraco.Cms.Core.Notifications ;
2021-02-18 11:06:02 +01:00
using Umbraco.Cms.Core.Persistence.Repositories ;
using Umbraco.Cms.Core.PublishedCache ;
using Umbraco.Cms.Core.Serialization ;
using Umbraco.Cms.Core.Services ;
using Umbraco.Cms.Core.Services.Changes ;
2024-09-27 09:12:19 +02:00
using Umbraco.Cms.Core.Services.Navigation ;
2021-02-18 11:06:02 +01:00
using Umbraco.Extensions ;
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
namespace Umbraco.Cms.Core.Cache ;
public sealed class ContentCacheRefresher : PayloadCacheRefresherBase < ContentCacheRefresherNotification ,
ContentCacheRefresher . JsonPayload >
2016-05-26 17:12:04 +02:00
{
2022-06-07 15:28:38 +02:00
private readonly IDomainService _domainService ;
2024-09-27 09:12:19 +02:00
private readonly IDocumentUrlService _documentUrlService ;
private readonly IDocumentNavigationQueryService _documentNavigationQueryService ;
2024-09-30 16:43:05 +02:00
private readonly IDocumentNavigationManagementService _documentNavigationManagementService ;
private readonly IContentService _contentService ;
2022-06-07 15:28:38 +02:00
private readonly IIdKeyMap _idKeyMap ;
private readonly IPublishedSnapshotService _publishedSnapshotService ;
2024-09-27 09:12:19 +02:00
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 16")]
2022-06-07 15:28:38 +02:00
public ContentCacheRefresher (
AppCaches appCaches ,
IJsonSerializer serializer ,
IPublishedSnapshotService publishedSnapshotService ,
IIdKeyMap idKeyMap ,
IDomainService domainService ,
IEventAggregator eventAggregator ,
ICacheRefresherNotificationFactory factory )
2024-09-27 09:12:19 +02:00
: this (
appCaches ,
serializer ,
publishedSnapshotService ,
idKeyMap ,
domainService ,
eventAggregator ,
factory ,
StaticServiceProvider . Instance . GetRequiredService < IDocumentUrlService > ( ) ,
2024-09-30 16:43:05 +02:00
StaticServiceProvider . Instance . GetRequiredService < IDocumentNavigationQueryService > ( ) ,
StaticServiceProvider . Instance . GetRequiredService < IDocumentNavigationManagementService > ( ) ,
StaticServiceProvider . Instance . GetRequiredService < IContentService > ( )
2024-09-27 09:12:19 +02:00
)
{
}
public ContentCacheRefresher (
AppCaches appCaches ,
IJsonSerializer serializer ,
IPublishedSnapshotService publishedSnapshotService ,
IIdKeyMap idKeyMap ,
IDomainService domainService ,
IEventAggregator eventAggregator ,
ICacheRefresherNotificationFactory factory ,
IDocumentUrlService documentUrlService ,
2024-09-30 16:43:05 +02:00
IDocumentNavigationQueryService documentNavigationQueryService ,
IDocumentNavigationManagementService documentNavigationManagementService ,
IContentService contentService )
2022-06-07 15:28:38 +02:00
: base ( appCaches , serializer , eventAggregator , factory )
2016-05-26 17:12:04 +02:00
{
2022-06-07 15:28:38 +02:00
_publishedSnapshotService = publishedSnapshotService ;
_idKeyMap = idKeyMap ;
_domainService = domainService ;
2024-09-27 09:12:19 +02:00
_documentUrlService = documentUrlService ;
_documentNavigationQueryService = documentNavigationQueryService ;
2024-09-30 16:43:05 +02:00
_documentNavigationManagementService = documentNavigationManagementService ;
_contentService = contentService ;
2022-06-07 15:28:38 +02:00
}
#region Indirect
public static void RefreshContentTypes ( AppCaches appCaches )
{
// we could try to have a mechanism to notify the PublishedCachesService
// and figure out whether published items were modified or not... keep it
// simple for now, just clear the whole thing
appCaches . ClearPartialViewCache ( ) ;
appCaches . IsolatedCaches . ClearCache < PublicAccessEntry > ( ) ;
appCaches . IsolatedCaches . ClearCache < IContent > ( ) ;
}
#endregion
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
#region Define
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
public static readonly Guid UniqueId = Guid . Parse ( "900A4FBE-DF3C-41E6-BB77-BE896CD158EA" ) ;
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
public override Guid RefresherUniqueId = > UniqueId ;
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
public override string Name = > "ContentCacheRefresher" ;
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
#endregion
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
#region Refresher
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
public override void Refresh ( JsonPayload [ ] payloads )
{
AppCaches . RuntimeCache . ClearOfType < PublicAccessEntry > ( ) ;
AppCaches . RuntimeCache . ClearByKey ( CacheKeys . ContentRecycleBinCacheKey ) ;
var idsRemoved = new HashSet < int > ( ) ;
IAppPolicyCache isolatedCache = AppCaches . IsolatedCaches . GetOrCreate < IContent > ( ) ;
foreach ( JsonPayload payload in payloads . Where ( x = > x . Id ! = default ) )
2016-05-26 17:12:04 +02:00
{
2022-06-07 15:28:38 +02:00
// By INT Id
isolatedCache . Clear ( RepositoryCacheKeys . GetKey < IContent , int > ( payload . Id ) ) ;
2016-11-03 10:31:44 +01:00
2022-06-07 15:28:38 +02:00
// By GUID Key
isolatedCache . Clear ( RepositoryCacheKeys . GetKey < IContent , Guid ? > ( payload . Key ) ) ;
2018-05-02 14:52:00 +10:00
2024-09-27 09:12:19 +02:00
2022-06-07 15:28:38 +02:00
// remove those that are in the branch
if ( payload . ChangeTypes . HasTypesAny ( TreeChangeTypes . RefreshBranch | TreeChangeTypes . Remove ) )
2016-05-26 17:12:04 +02:00
{
2022-06-07 15:28:38 +02:00
var pathid = "," + payload . Id + "," ;
isolatedCache . ClearOfType < IContent > ( ( k , v ) = > v . Path ? . Contains ( pathid ) ? ? false ) ;
2018-05-02 14:52:00 +10:00
}
2024-02-01 09:55:09 +01:00
// if the item is not a blueprint and is being completely removed, we need to refresh the domains cache if any domain was assigned to the content
if ( payload . Blueprint is false & & payload . ChangeTypes . HasTypesAny ( TreeChangeTypes . Remove ) )
2018-05-02 14:52:00 +10:00
{
2022-06-07 15:28:38 +02:00
idsRemoved . Add ( payload . Id ) ;
2016-05-26 17:12:04 +02:00
}
2024-09-27 09:12:19 +02:00
HandleRouting ( payload ) ;
2024-09-30 16:43:05 +02:00
HandleNavigation ( payload ) ;
2024-09-27 09:12:19 +02:00
_idKeyMap . ClearCache ( payload . Id ) ;
if ( payload . Key . HasValue )
{
_idKeyMap . ClearCache ( payload . Key . Value ) ;
}
2016-05-26 17:12:04 +02:00
}
2022-06-07 15:28:38 +02:00
if ( idsRemoved . Count > 0 )
{
var assignedDomains = _domainService . GetAll ( true )
? . Where ( x = > x . RootContentId . HasValue & & idsRemoved . Contains ( x . RootContentId . Value ) ) . ToList ( ) ;
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
if ( assignedDomains ? . Count > 0 )
{
// TODO: this is duplicating the logic in DomainCacheRefresher BUT we cannot inject that into this because it it not registered explicitly in the container,
// and we cannot inject the CacheRefresherCollection since that would be a circular reference, so what is the best way to call directly in to the
// DomainCacheRefresher?
ClearAllIsolatedCacheByEntityType < IDomain > ( ) ;
// note: must do what's above FIRST else the repositories still have the old cached
// content and when the PublishedCachesService is notified of changes it does not see
// the new content...
// notify
_publishedSnapshotService . Notify ( assignedDomains
. Select ( x = > new DomainCacheRefresher . JsonPayload ( x . Id , DomainChangeTypes . Remove ) ) . ToArray ( ) ) ;
}
}
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
// note: must do what's above FIRST else the repositories still have the old cached
// content and when the PublishedCachesService is notified of changes it does not see
// the new content...
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
// TODO: what about this?
// should rename it, and then, this is only for Deploy, and then, ???
// if (Suspendable.PageCacheRefresher.CanUpdateDocumentCache)
// ...
2024-02-01 09:55:09 +01:00
if ( payloads . Any ( x = > x . Blueprint is false ) )
{
// Only notify if the payload contains actual (non-blueprint) contents
NotifyPublishedSnapshotService ( _publishedSnapshotService , AppCaches , payloads ) ;
}
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
base . Refresh ( payloads ) ;
}
2016-05-26 17:12:04 +02:00
2024-09-30 16:43:05 +02:00
private void HandleNavigation ( JsonPayload payload )
{
if ( payload . Key is null )
{
return ;
}
if ( payload . ChangeTypes . HasType ( TreeChangeTypes . Remove ) )
{
_documentNavigationManagementService . MoveToBin ( payload . Key . Value ) ;
_documentNavigationManagementService . RemoveFromBin ( payload . Key . Value ) ;
}
if ( payload . ChangeTypes . HasType ( TreeChangeTypes . RefreshAll ) )
{
_documentNavigationManagementService . RebuildAsync ( ) ;
_documentNavigationManagementService . RebuildBinAsync ( ) ;
}
if ( payload . ChangeTypes . HasType ( TreeChangeTypes . RefreshNode ) )
{
IContent ? content = _contentService . GetById ( payload . Id ) ;
if ( content is null )
{
return ;
}
HandleNavigationForSingleContent ( content ) ;
}
if ( payload . ChangeTypes . HasType ( TreeChangeTypes . RefreshBranch ) )
{
IContent ? content = _contentService . GetById ( payload . Id ) ;
if ( content is null )
{
return ;
}
IEnumerable < IContent > descendants = _contentService . GetPagedDescendants ( content . Id , 0 , int . MaxValue , out _ ) ;
foreach ( IContent descendant in content . Yield ( ) . Concat ( descendants ) )
{
HandleNavigationForSingleContent ( descendant ) ;
}
}
}
private void HandleNavigationForSingleContent ( IContent content )
{
// First creation
if ( ExistsInNavigation ( content . Key ) is false & & ExistsInNavigationBin ( content . Key ) is false )
{
_documentNavigationManagementService . Add ( content . Key , GetParentKey ( content ) ) ;
if ( content . Trashed )
{
// If created as trashed, move to bin
_documentNavigationManagementService . MoveToBin ( content . Key ) ;
}
}
else if ( ExistsInNavigation ( content . Key ) & & ExistsInNavigationBin ( content . Key ) is false )
{
if ( content . Trashed )
{
// It must have been trashed
_documentNavigationManagementService . MoveToBin ( content . Key ) ;
}
else
{
// It must have been saved. Check if parent is different
if ( _documentNavigationQueryService . TryGetParentKey ( content . Key , out var oldParentKey ) )
{
Guid ? newParentKey = GetParentKey ( content ) ;
if ( oldParentKey ! = newParentKey )
{
_documentNavigationManagementService . Move ( content . Key , newParentKey ) ;
}
}
}
}
else if ( ExistsInNavigation ( content . Key ) is false & & ExistsInNavigationBin ( content . Key ) )
{
if ( content . Trashed is false )
{
// It must have been restored
_documentNavigationManagementService . RestoreFromBin ( content . Key , GetParentKey ( content ) ) ;
}
}
}
private Guid ? GetParentKey ( IContent content ) = > ( content . ParentId = = - 1 ) ? null : _idKeyMap . GetKeyForId ( content . ParentId , UmbracoObjectTypes . Document ) . Result ;
private bool ExistsInNavigation ( Guid contentKey ) = > _documentNavigationQueryService . TryGetParentKey ( contentKey , out _ ) ;
private bool ExistsInNavigationBin ( Guid contentKey ) = > _documentNavigationQueryService . TryGetParentKeyInBin ( contentKey , out _ ) ;
2024-09-27 09:12:19 +02:00
private void HandleRouting ( JsonPayload payload )
{
if ( payload . ChangeTypes . HasType ( TreeChangeTypes . Remove ) )
{
var key = payload . Key ? ? _idKeyMap . GetKeyForId ( payload . Id , UmbracoObjectTypes . Document ) . Result ;
//Note the we need to clear the navigation service as the last thing
if ( _documentNavigationQueryService . TryGetDescendantsKeysOrSelfKeys ( key , out var descendantsOrSelfKeys ) )
{
_documentUrlService . DeleteUrlsFromCacheAsync ( descendantsOrSelfKeys ) . GetAwaiter ( ) . GetResult ( ) ;
} else if ( _documentNavigationQueryService . TryGetDescendantsKeysOrSelfKeysInBin ( key , out var descendantsOrSelfKeysInBin ) )
{
_documentUrlService . DeleteUrlsFromCacheAsync ( descendantsOrSelfKeysInBin ) . GetAwaiter ( ) . GetResult ( ) ;
}
}
if ( payload . ChangeTypes . HasType ( TreeChangeTypes . RefreshAll ) )
{
_documentUrlService . RebuildAllUrlsAsync ( ) . GetAwaiter ( ) . GetResult ( ) ; //TODO make async
}
if ( payload . ChangeTypes . HasType ( TreeChangeTypes . RefreshNode ) )
{
var key = payload . Key ? ? _idKeyMap . GetKeyForId ( payload . Id , UmbracoObjectTypes . Document ) . Result ;
_documentUrlService . CreateOrUpdateUrlSegmentsAsync ( key ) . GetAwaiter ( ) . GetResult ( ) ;
}
if ( payload . ChangeTypes . HasType ( TreeChangeTypes . RefreshBranch ) )
{
var key = payload . Key ? ? _idKeyMap . GetKeyForId ( payload . Id , UmbracoObjectTypes . Document ) . Result ;
_documentUrlService . CreateOrUpdateUrlSegmentsWithDescendantsAsync ( key ) . GetAwaiter ( ) . GetResult ( ) ;
}
}
2022-06-07 15:28:38 +02:00
// these events should never trigger
// everything should be PAYLOAD/JSON
public override void RefreshAll ( ) = > throw new NotSupportedException ( ) ;
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
public override void Refresh ( int id ) = > throw new NotSupportedException ( ) ;
2019-10-14 15:21:00 +11:00
2022-06-07 15:28:38 +02:00
public override void Refresh ( Guid id ) = > throw new NotSupportedException ( ) ;
2019-10-14 15:21:00 +11:00
2022-06-07 15:28:38 +02:00
public override void Remove ( int id ) = > throw new NotSupportedException ( ) ;
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
#endregion
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
#region Json
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
/// <summary>
/// Refreshes the publish snapshot service and if there are published changes ensures that partial view caches are
/// refreshed too
/// </summary>
/// <param name="service"></param>
/// <param name="appCaches"></param>
/// <param name="payloads"></param>
internal static void NotifyPublishedSnapshotService ( IPublishedSnapshotService service , AppCaches appCaches , JsonPayload [ ] payloads )
{
service . Notify ( payloads , out _ , out var publishedChanged ) ;
2016-05-26 17:12:04 +02:00
2022-06-07 15:28:38 +02:00
if ( payloads . Any ( x = > x . ChangeTypes . HasType ( TreeChangeTypes . RefreshAll ) ) | | publishedChanged )
2016-05-26 17:12:04 +02:00
{
2022-06-07 15:28:38 +02:00
// when a public version changes
2019-01-17 08:34:29 +01:00
appCaches . ClearPartialViewCache ( ) ;
2022-06-07 15:28:38 +02:00
}
}
2016-05-26 17:12:04 +02:00
2024-02-01 09:55:09 +01:00
// TODO (V14): Change into a record
2022-06-07 15:28:38 +02:00
public class JsonPayload
{
2024-02-01 09:55:09 +01:00
public JsonPayload ( )
{ }
[Obsolete("Use the default constructor and property initializers.")]
2022-06-07 15:28:38 +02:00
public JsonPayload ( int id , Guid ? key , TreeChangeTypes changeTypes )
{
Id = id ;
Key = key ;
ChangeTypes = changeTypes ;
2016-05-26 17:12:04 +02:00
}
2024-02-01 09:55:09 +01:00
public int Id { get ; init ; }
public Guid ? Key { get ; init ; }
2016-05-26 17:12:04 +02:00
2024-02-01 09:55:09 +01:00
public TreeChangeTypes ChangeTypes { get ; init ; }
2022-06-07 15:28:38 +02:00
2024-02-01 09:55:09 +01:00
public bool Blueprint { get ; init ; }
2016-05-26 17:12:04 +02:00
}
2022-06-07 15:28:38 +02:00
#endregion
2016-05-26 17:12:04 +02:00
}