From d0cc93d6a2daa6cefaba7aca50fad56a453bcf9b Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 6 Mar 2014 17:50:08 +1100 Subject: [PATCH] Fixes: U4-3937 Ensure media is indexed across all servers in LB environment with Distributed Cache calls This fix is for media indexes to be distributed properly and for unpublished content to be distributed properly, now to get members to do the same. --- src/Umbraco.Core/Models/ContentExtensions.cs | 14 + src/Umbraco.Core/Models/EntityExtensions.cs | 2 +- .../Cache/CacheRefresherEventHandler.cs | 81 ++++- src/Umbraco.Web/Cache/DistributedCache.cs | 1 + .../Cache/DistributedCacheExtensions.cs | 38 +- src/Umbraco.Web/Cache/MediaCacheRefresher.cs | 36 +- .../Cache/UnpublishedPageCacheRefresher.cs | 33 ++ src/Umbraco.Web/Search/ExamineEvents.cs | 334 ++++++++++++------ src/Umbraco.Web/Umbraco.Web.csproj | 1 + 9 files changed, 404 insertions(+), 136 deletions(-) create mode 100644 src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs diff --git a/src/Umbraco.Core/Models/ContentExtensions.cs b/src/Umbraco.Core/Models/ContentExtensions.cs index 8b3440d435..e89cb6e2af 100644 --- a/src/Umbraco.Core/Models/ContentExtensions.cs +++ b/src/Umbraco.Core/Models/ContentExtensions.cs @@ -25,6 +25,20 @@ namespace Umbraco.Core.Models { #region IContent + /// + /// Returns true if this entity was just published as part of a recent save operation (i.e. it wasn't previously published) + /// + /// + /// + /// + /// This is helpful for determining if the published event will execute during the saved event for a content item. + /// + internal static bool JustPublished(this IContent entity) + { + var dirty = (IRememberBeingDirty)entity; + return dirty.WasPropertyDirty("Published") && entity.Published; + } + /// /// Determines if a new version should be created /// diff --git a/src/Umbraco.Core/Models/EntityExtensions.cs b/src/Umbraco.Core/Models/EntityExtensions.cs index f461c4007c..6daf99a58d 100644 --- a/src/Umbraco.Core/Models/EntityExtensions.cs +++ b/src/Umbraco.Core/Models/EntityExtensions.cs @@ -23,5 +23,5 @@ namespace Umbraco.Core.Models var dirty = (IRememberBeingDirty)entity; return dirty.WasPropertyDirty("Id"); } - } + } } diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs index a9b01fb6e5..6f8e708219 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs @@ -131,19 +131,30 @@ namespace Umbraco.Web.Cache MediaService.Moving += MediaServiceMoving; MediaService.Trashing += MediaServiceTrashing; - ContentService.Created += ContentServiceCreated; + //Bind to content events - this is for unpublished content syncing across servers (primarily for examine) + + ContentService.Saved += ContentServiceSaved; + ContentService.Deleted += ContentServiceDeleted; ContentService.Copied += ContentServiceCopied; + //NOTE: we do not listen for the trashed event because there is no cache to update for content in that case since + // the unpublishing event handles that, and for examine with unpublished content indexes, we want to keep that data + // in the index, it's not until it's complete deleted that we want to remove it. } + + #region Content service event handlers /// - /// When an entity is copied new permissions may be assigned to it based on it's parent, if that is the - /// case then we need to clear all user permissions cache. + /// Handles cache refreshgi for when content is copied /// /// /// - static void ContentServiceCopied(IContentService sender, Core.Events.CopyEventArgs e) + /// + /// When an entity is copied new permissions may be assigned to it based on it's parent, if that is the + /// case then we need to clear all user permissions cache. + /// + static void ContentServiceCopied(IContentService sender, CopyEventArgs e) { //check if permissions have changed var permissionsChanged = ((Content)e.Copy).WasPropertyDirty("PermissionsChanged"); @@ -151,23 +162,63 @@ namespace Umbraco.Web.Cache { DistributedCache.Instance.RefreshAllUserPermissionsCache(); } + + //run the un-published cache refresher + DistributedCache.Instance.RefreshUnpublishedPageCache(e.Copy); } /// - /// When an entity is created new permissions may be assigned to it based on it's parent, if that is the - /// case then we need to clear all user permissions cache. + /// Handles cache refreshing for when content is deleted (not unpublished) /// /// /// - static void ContentServiceCreated(IContentService sender, Core.Events.NewEventArgs e) + static void ContentServiceDeleted(IContentService sender, DeleteEventArgs e) { - //check if permissions have changed - var permissionsChanged = ((Content)e.Entity).WasPropertyDirty("PermissionsChanged"); - if (permissionsChanged) + DistributedCache.Instance.RemoveUnpublishedPageCache(e.DeletedEntities.ToArray()); + } + + /// + /// Handles cache refreshing for when content is saved (not published) + /// + /// + /// + /// + /// When an entity is saved we need to notify other servers about the change in order for the Examine indexes to + /// stay up-to-date for unpublished content. + /// + /// When an entity is created new permissions may be assigned to it based on it's parent, if that is the + /// case then we need to clear all user permissions cache. + /// + static void ContentServiceSaved(IContentService sender, SaveEventArgs e) + { + var clearUserPermissions = false; + e.SavedEntities.ForEach(x => + { + //check if it is new + if (x.IsNewEntity()) + { + //check if permissions have changed + var permissionsChanged = ((Content)x).WasPropertyDirty("PermissionsChanged"); + if (permissionsChanged) + { + clearUserPermissions = true; + } + } + }); + + if (clearUserPermissions) { DistributedCache.Instance.RefreshAllUserPermissionsCache(); } - } + + //filter out the entities that have only been saved (not newly published) since + // newly published ones will be synced with the published page cache refresher + var unpublished = e.SavedEntities.Where(x => x.JustPublished() == false); + //run the un-published cache refresher + DistributedCache.Instance.RefreshUnpublishedPageCache(unpublished.ToArray()); + } + + #endregion #region ApplicationTree event handlers @@ -451,12 +502,12 @@ namespace Umbraco.Web.Cache InvalidateCacheForPermissionsChange(sender); } - void UserServiceSavedUser(IUserService sender, Core.Events.SaveEventArgs e) + static void UserServiceSavedUser(IUserService sender, SaveEventArgs e) { e.SavedEntities.ForEach(x => DistributedCache.Instance.RefreshUserCache(x.Id)); } - void UserServiceDeletedUser(IUserService sender, Core.Events.DeleteEventArgs e) + static void UserServiceDeletedUser(IUserService sender, DeleteEventArgs e) { e.DeletedEntities.ForEach(x => DistributedCache.Instance.RemoveUserCache(x.Id)); } @@ -547,7 +598,7 @@ namespace Umbraco.Web.Cache #region Media event handlers static void MediaServiceTrashing(IMediaService sender, Core.Events.MoveEventArgs e) { - DistributedCache.Instance.RemoveMediaCache(e.Entity); + DistributedCache.Instance.RemoveMediaCache(false, e.Entity); } static void MediaServiceMoving(IMediaService sender, Core.Events.MoveEventArgs e) @@ -557,7 +608,7 @@ namespace Umbraco.Web.Cache static void MediaServiceDeleting(IMediaService sender, Core.Events.DeleteEventArgs e) { - DistributedCache.Instance.RemoveMediaCache(e.DeletedEntities.ToArray()); + DistributedCache.Instance.RemoveMediaCache(true, e.DeletedEntities.ToArray()); } static void MediaServiceSaved(IMediaService sender, Core.Events.SaveEventArgs e) diff --git a/src/Umbraco.Web/Cache/DistributedCache.cs b/src/Umbraco.Web/Cache/DistributedCache.cs index b66dd2174a..8fa1dec3b3 100644 --- a/src/Umbraco.Web/Cache/DistributedCache.cs +++ b/src/Umbraco.Web/Cache/DistributedCache.cs @@ -39,6 +39,7 @@ namespace Umbraco.Web.Cache public const string ApplicationCacheRefresherId = "B15F34A1-BC1D-4F8B-8369-3222728AB4C8"; public const string TemplateRefresherId = "DD12B6A0-14B9-46e8-8800-C154F74047C8"; public const string PageCacheRefresherId = "27AB3022-3DFA-47b6-9119-5945BC88FD66"; + public const string UnpublishedPageCacheRefresherId = "55698352-DFC5-4DBE-96BD-A4A0F6F77145"; public const string MemberCacheRefresherId = "E285DF34-ACDC-4226-AE32-C0CB5CF388DA"; public const string MemberGroupCacheRefresherId = "187F236B-BD21-4C85-8A7C-29FBA3D6C00C"; public const string MediaCacheRefresherId = "B29286DD-2D40-4DDB-B325-681226589FEC"; diff --git a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs index faede0bffc..e218537ef2 100644 --- a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs +++ b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs @@ -123,8 +123,7 @@ namespace Umbraco.Web.Cache } #endregion - - + #region Data type cache /// /// Refreshes the cache amongst servers for a data type @@ -232,7 +231,28 @@ namespace Umbraco.Web.Cache public static void RemovePageCache(this DistributedCache dc, int documentId) { dc.Remove(new Guid(DistributedCache.PageCacheRefresherId), documentId); - } + } + + /// + /// invokes the unpublished page cache refresher + /// + /// + /// + public static void RefreshUnpublishedPageCache(this DistributedCache dc, params IContent[] content) + { + dc.Refresh(new Guid(DistributedCache.UnpublishedPageCacheRefresherId), x => x.Id, content); + } + + /// + /// invokes the unpublished page cache refresher + /// + /// + /// + public static void RemoveUnpublishedPageCache(this DistributedCache dc, params IContent[] content) + { + dc.Remove(new Guid(DistributedCache.UnpublishedPageCacheRefresherId), x => x.Id, content); + } + #endregion #region Member cache @@ -291,7 +311,7 @@ namespace Umbraco.Web.Cache public static void RefreshMediaCache(this DistributedCache dc, params IMedia[] media) { dc.RefreshByJson(new Guid(DistributedCache.MediaCacheRefresherId), - MediaCacheRefresher.SerializeToJsonPayload(media)); + MediaCacheRefresher.SerializeToJsonPayload(MediaCacheRefresher.OperationType.Saved, media)); } /// @@ -304,6 +324,7 @@ namespace Umbraco.Web.Cache /// to clear all of the cache but the media item will be removed before the other servers can /// look it up. Only here for legacy purposes. /// + [Obsolete("Ensure to clear with other RemoveMediaCache overload")] public static void RemoveMediaCache(this DistributedCache dc, int mediaId) { dc.Remove(new Guid(DistributedCache.MediaCacheRefresherId), mediaId); @@ -313,11 +334,14 @@ namespace Umbraco.Web.Cache /// Removes the cache amongst servers for media items /// /// + /// /// - public static void RemoveMediaCache(this DistributedCache dc, params IMedia[] media) + public static void RemoveMediaCache(this DistributedCache dc, bool isPermanentlyDeleted, params IMedia[] media) { - dc.RefreshByJson(new Guid(DistributedCache.MediaCacheRefresherId), - MediaCacheRefresher.SerializeToJsonPayload(media)); + dc.RefreshByJson(new Guid(DistributedCache.MediaCacheRefresherId), + MediaCacheRefresher.SerializeToJsonPayload( + isPermanentlyDeleted ? MediaCacheRefresher.OperationType.Deleted : MediaCacheRefresher.OperationType.Trashed, + media)); } #endregion diff --git a/src/Umbraco.Web/Cache/MediaCacheRefresher.cs b/src/Umbraco.Web/Cache/MediaCacheRefresher.cs index 53abacc7a5..72a541b720 100644 --- a/src/Umbraco.Web/Cache/MediaCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/MediaCacheRefresher.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Web.Script.Serialization; using Umbraco.Core; using Umbraco.Core.Cache; @@ -19,13 +20,13 @@ namespace Umbraco.Web.Cache public class MediaCacheRefresher : JsonCacheRefresherBase { #region Static helpers - + /// /// Converts the json to a JsonPayload object /// /// /// - private static JsonPayload[] DeserializeFromJsonPayload(string json) + internal static JsonPayload[] DeserializeFromJsonPayload(string json) { var serializer = new JavaScriptSerializer(); var jsonObject = serializer.Deserialize(json); @@ -35,12 +36,13 @@ namespace Umbraco.Web.Cache /// /// Creates the custom Json payload used to refresh cache amongst the servers /// + /// /// /// - internal static string SerializeToJsonPayload(params IMedia[] media) + internal static string SerializeToJsonPayload(OperationType operation, params IMedia[] media) { var serializer = new JavaScriptSerializer(); - var items = media.Select(FromMedia).ToArray(); + var items = media.Select(x => FromMedia(x, operation)).ToArray(); var json = serializer.Serialize(items); return json; } @@ -49,15 +51,17 @@ namespace Umbraco.Web.Cache /// Converts a macro to a jsonPayload object /// /// + /// /// - private static JsonPayload FromMedia(IMedia media) + internal static JsonPayload FromMedia(IMedia media, OperationType operation) { if (media == null) return null; var payload = new JsonPayload { Id = media.Id, - Path = media.Path + Path = media.Path, + Operation = operation }; return payload; } @@ -66,10 +70,18 @@ namespace Umbraco.Web.Cache #region Sub classes - private class JsonPayload + internal enum OperationType + { + Saved, + Trashed, + Deleted + } + + internal class JsonPayload { public string Path { get; set; } public int Id { get; set; } + public OperationType Operation { get; set; } } #endregion @@ -97,13 +109,15 @@ namespace Umbraco.Web.Cache public override void Refresh(int id) { - ClearCache(FromMedia(ApplicationContext.Current.Services.MediaService.GetById(id))); + ClearCache(FromMedia(ApplicationContext.Current.Services.MediaService.GetById(id), OperationType.Saved)); base.Refresh(id); } public override void Remove(int id) - { - ClearCache(FromMedia(ApplicationContext.Current.Services.MediaService.GetById(id))); + { + ClearCache(FromMedia(ApplicationContext.Current.Services.MediaService.GetById(id), + //NOTE: we'll just default to trashed for this one. + OperationType.Trashed)); base.Remove(id); } @@ -121,7 +135,7 @@ namespace Umbraco.Web.Cache string.Format("{0}_{1}_True", CacheKeys.MediaCacheKey, idPart)); // Also clear calls that only query this specific item! - if (idPart == payload.Id.ToString()) + if (idPart == payload.Id.ToString(CultureInfo.InvariantCulture)) ApplicationContext.Current.ApplicationCache.ClearCacheByKeySearch( string.Format("{0}_{1}", CacheKeys.MediaCacheKey, payload.Id)); diff --git a/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs b/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs new file mode 100644 index 0000000000..4413a61e9b --- /dev/null +++ b/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs @@ -0,0 +1,33 @@ +using System; +using Umbraco.Core.Cache; +using Umbraco.Core.Models; + +namespace Umbraco.Web.Cache +{ + /// + /// A cache refresher used for non-published content, this is primarily to notify Examine indexes to update + /// + public sealed class UnpublishedPageCacheRefresher : TypedCacheRefresherBase + { + protected override UnpublishedPageCacheRefresher Instance + { + get { return this; } + } + + public override Guid UniqueIdentifier + { + get { return new Guid(DistributedCache.UnpublishedPageCacheRefresherId); } + } + + public override string Name + { + get { return "Unpublished Page Refresher"; } + } + + //NOTE: There is no functionality for this cache refresher, it is here simply to emit events on each server for which examine + // binds to. We could put the Examine index functionality in here but we've kept it all in the ExamineEvents class so that all of + // the logic is in one place. In the future we may put the examine logic in a cache refresher instead (that would make sense) but we'd + // want to get this done before making more cache refreshers: + // http://issues.umbraco.org/issue/U4-2633 + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Search/ExamineEvents.cs b/src/Umbraco.Web/Search/ExamineEvents.cs index 102038a7c0..0d4cb9cdeb 100644 --- a/src/Umbraco.Web/Search/ExamineEvents.cs +++ b/src/Umbraco.Web/Search/ExamineEvents.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using System.Security; using System.Xml; @@ -7,9 +8,13 @@ using Examine; using Examine.LuceneEngine; using Lucene.Net.Documents; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Services; +using Umbraco.Core.Sync; +using Umbraco.Web.Cache; using UmbracoExamine; using umbraco; using umbraco.BusinessLogic; @@ -50,20 +55,12 @@ namespace Umbraco.Web.Search if (registeredProviders == 0) return; - MediaService.Saved += MediaServiceSaved; - MediaService.Deleted += MediaServiceDeleted; - MediaService.Moved += MediaServiceMoved; - MediaService.Trashed += MediaServiceTrashed; - - ContentService.Saved += ContentServiceSaved; - ContentService.Deleted += ContentServiceDeleted; - ContentService.Moved += ContentServiceMoved; - ContentService.Trashed += ContentServiceTrashed; - - //These should only fire for providers that DONT have SupportUnpublishedContent set to true - content.AfterUpdateDocumentCache += ContentAfterUpdateDocumentCache; - content.AfterClearDocumentCache += ContentAfterClearDocumentCache; - + //Bind to distributed cache events - this ensures that this logic occurs on ALL servers that are taking part + // in a load balanced environment. + CacheRefresherBase.CacheUpdated += UnpublishedPageCacheRefresherCacheUpdated; + CacheRefresherBase.CacheUpdated += PublishedPageCacheRefresherCacheUpdated; + CacheRefresherBase.CacheUpdated += MediaCacheRefresherCacheUpdated; + Member.AfterSave += MemberAfterSave; Member.AfterDelete += MemberAfterDelete; @@ -79,60 +76,192 @@ namespace Umbraco.Web.Search } } - [SecuritySafeCritical] - static void ContentServiceTrashed(IContentService sender, Core.Events.MoveEventArgs e) + /// + /// Handles index management for all media events - basically handling saving/copying/trashing/deleting + /// + /// + /// + static void MediaCacheRefresherCacheUpdated(MediaCacheRefresher sender, CacheRefresherEventArgs e) { - IndexConent(e.Entity); + switch (e.MessageType) + { + case MessageType.RefreshById: + var c1 = ApplicationContext.Current.Services.MediaService.GetById((int)e.MessageObject); + if (c1 != null) + { + ReIndexForMedia(c1, c1.Trashed == false); + } + break; + case MessageType.RemoveById: + var c2 = ApplicationContext.Current.Services.MediaService.GetById((int)e.MessageObject); + if (c2 != null) + { + //This is triggered when the item has trashed. + // So we need to delete the index from all indexes not supporting unpublished content. + + DeleteIndexForEntity(c2.Id, true); + + //We then need to re-index this item for all indexes supporting unpublished content + + ReIndexForMedia(c2, false); + } + break; + case MessageType.RefreshByJson: + + var jsonPayloads = MediaCacheRefresher.DeserializeFromJsonPayload((string)e.MessageObject); + if (jsonPayloads.Any()) + { + foreach (var payload in jsonPayloads) + { + switch (payload.Operation) + { + case MediaCacheRefresher.OperationType.Saved: + var media = ApplicationContext.Current.Services.MediaService.GetById(payload.Id); + if (media != null) + { + ReIndexForMedia(media, media.Trashed == false); + } + break; + case MediaCacheRefresher.OperationType.Trashed: + //keep if trashed for indexes supporting unpublished + DeleteIndexForEntity(payload.Id, true); + break; + case MediaCacheRefresher.OperationType.Deleted: + //permanently remove from all indexes + DeleteIndexForEntity(payload.Id, false); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + } + + break; + case MessageType.RefreshByInstance: + case MessageType.RemoveByInstance: + case MessageType.RefreshAll: + default: + //We don't support these, these message types will not fire for media + break; + } } + /// + /// Handles index management for all published content events - basically handling published/unpublished + /// + /// + /// + /// + /// This will execute on all servers taking part in load balancing + /// [SecuritySafeCritical] - static void MediaServiceTrashed(IMediaService sender, Core.Events.MoveEventArgs e) + static void PublishedPageCacheRefresherCacheUpdated(PageCacheRefresher sender, CacheRefresherEventArgs e) { - IndexMedia(e.Entity); + switch (e.MessageType) + { + case MessageType.RefreshById: + var c1 = ApplicationContext.Current.Services.ContentService.GetById((int)e.MessageObject); + if (c1 != null) + { + ReIndexForContent(c1, true); + } + break; + case MessageType.RemoveById: + + //This is triggered when the item has been unpublished or trashed (which also performs an unpublish). + + var c2 = ApplicationContext.Current.Services.ContentService.GetById((int)e.MessageObject); + if (c2 != null) + { + // So we need to delete the index from all indexes not supporting unpublished content. + + DeleteIndexForEntity(c2.Id, true); + + // We then need to re-index this item for all indexes supporting unpublished content + + ReIndexForContent(c2, false); + } + break; + case MessageType.RefreshByInstance: + var c3 = e.MessageObject as IContent; + if (c3 != null) + { + ReIndexForContent(c3, true); + } + break; + case MessageType.RemoveByInstance: + + //This is triggered when the item has been unpublished or trashed (which also performs an unpublish). + + var c4 = e.MessageObject as IContent; + if (c4 != null) + { + // So we need to delete the index from all indexes not supporting unpublished content. + + DeleteIndexForEntity(c4.Id, true); + + // We then need to re-index this item for all indexes supporting unpublished content + + ReIndexForContent(c4, false); + } + break; + case MessageType.RefreshAll: + case MessageType.RefreshByJson: + default: + //We don't support these for examine indexing + break; + } } + /// + /// Handles index management for all unpublished content events - basically handling saving/copying/deleting + /// + /// + /// + /// + /// This will execute on all servers taking part in load balancing + /// [SecuritySafeCritical] - static void ContentServiceMoved(IContentService sender, Umbraco.Core.Events.MoveEventArgs e) + static void UnpublishedPageCacheRefresherCacheUpdated(UnpublishedPageCacheRefresher sender, CacheRefresherEventArgs e) { - IndexConent(e.Entity); - } + switch (e.MessageType) + { + case MessageType.RefreshById: + var c1 = ApplicationContext.Current.Services.ContentService.GetById((int) e.MessageObject); + if (c1 != null) + { + ReIndexForContent(c1, false); + } + break; + case MessageType.RemoveById: + + // This is triggered when the item is permanently deleted + + DeleteIndexForEntity((int)e.MessageObject, false); + break; + case MessageType.RefreshByInstance: + var c3 = e.MessageObject as IContent; + if (c3 != null) + { + ReIndexForContent(c3, false); + } + break; + case MessageType.RemoveByInstance: - [SecuritySafeCritical] - static void ContentServiceDeleted(IContentService sender, Umbraco.Core.Events.DeleteEventArgs e) - { - e.DeletedEntities.ForEach( - content => - ExamineManager.Instance.DeleteFromIndex( - content.Id.ToString(), - ExamineManager.Instance.IndexProviderCollection.OfType().Where(x => x.EnableDefaultEventHandler))); - } + // This is triggered when the item is permanently deleted - [SecuritySafeCritical] - static void ContentServiceSaved(IContentService sender, Umbraco.Core.Events.SaveEventArgs e) - { - e.SavedEntities.ForEach(IndexConent); - } - - [SecuritySafeCritical] - static void MediaServiceMoved(IMediaService sender, Umbraco.Core.Events.MoveEventArgs e) - { - IndexMedia(e.Entity); - } - - [SecuritySafeCritical] - static void MediaServiceDeleted(IMediaService sender, Umbraco.Core.Events.DeleteEventArgs e) - { - e.DeletedEntities.ForEach( - media => - ExamineManager.Instance.DeleteFromIndex( - media.Id.ToString(), - ExamineManager.Instance.IndexProviderCollection.OfType().Where(x => x.EnableDefaultEventHandler))); - } - - [SecuritySafeCritical] - static void MediaServiceSaved(IMediaService sender, Umbraco.Core.Events.SaveEventArgs e) - { - e.SavedEntities.ForEach(IndexMedia); + var c4 = e.MessageObject as IContent; + if (c4 != null) + { + DeleteIndexForEntity(c4.Id, false); + } + break; + case MessageType.RefreshAll: + case MessageType.RefreshByJson: + default: + //We don't support these, these message types will not fire for unpublished content + break; + } } [SecuritySafeCritical] @@ -156,39 +285,6 @@ namespace Umbraco.Web.Search .Where(x => x.EnableDefaultEventHandler)); } - /// - /// Only Update indexes for providers that dont SupportUnpublishedContent - /// - /// - /// - [SecuritySafeCritical] - private static void ContentAfterUpdateDocumentCache(Document sender, DocumentCacheEventArgs e) - { - //ensure that only the providers that have DONT unpublishing support enabled - //that are also flagged to listen - ExamineManager.Instance.ReIndexNode(ToXDocument(sender, true).Root, IndexTypes.Content, - ExamineManager.Instance.IndexProviderCollection.OfType() - .Where(x => !x.SupportUnpublishedContent - && x.EnableDefaultEventHandler)); - } - - /// - /// Only update indexes for providers that don't SupportUnpublishedContnet - /// - /// - /// - [SecuritySafeCritical] - private static void ContentAfterClearDocumentCache(Document sender, DocumentCacheEventArgs e) - { - var nodeId = sender.Id.ToString(); - //ensure that only the providers that DONT have unpublishing support enabled - //that are also flagged to listen - ExamineManager.Instance.DeleteFromIndex(nodeId, - ExamineManager.Instance.IndexProviderCollection.OfType() - .Where(x => !x.SupportUnpublishedContent - && x.EnableDefaultEventHandler)); - } - /// /// Event handler to create a lower cased version of the node name, this is so we can support case-insensitive searching and still /// use the Whitespace Analyzer @@ -210,27 +306,61 @@ namespace Umbraco.Web.Search } } - - private static void IndexMedia(IMedia sender) + [SecuritySafeCritical] + private static void ReIndexForMedia(IMedia sender, bool isMediaPublished) { ExamineManager.Instance.ReIndexNode( sender.ToXml(), "media", - ExamineManager.Instance.IndexProviderCollection.OfType().Where(x => x.EnableDefaultEventHandler)); + ExamineManager.Instance.IndexProviderCollection.OfType() + + //Index this item for all indexers if the media is not trashed, otherwise if the item is trashed + // then only index this for indexers supporting unpublished media + + .Where(x => isMediaPublished || (x.SupportUnpublishedContent)) + .Where(x => x.EnableDefaultEventHandler)); } - private static void IndexConent(IContent sender) + /// + /// Remove items from any index that doesn't support unpublished content + /// + /// + /// + /// If true, indicates that we will only delete this item from indexes that don't support unpublished content. + /// If false it will delete this from all indexes regardless. + /// + [SecuritySafeCritical] + private static void DeleteIndexForEntity(int entityId, bool keepIfUnpublished) + { + ExamineManager.Instance.DeleteFromIndex( + entityId.ToString(CultureInfo.InvariantCulture), + ExamineManager.Instance.IndexProviderCollection.OfType() + + //if keepIfUnpublished == true then only delete this item from indexes not supporting unpublished content, + // otherwise if keepIfUnpublished == false then remove from all indexes + + .Where(x => keepIfUnpublished == false || x.SupportUnpublishedContent == false) + .Where(x => x.EnableDefaultEventHandler)); + } + + /// + /// Re-indexes a content item whether published or not but only indexes them for indexes supporting unpublished content + /// + /// + /// + /// Value indicating whether the item is published or not + /// + [SecuritySafeCritical] + private static void ReIndexForContent(IContent sender, bool isContentPublished) { - //only index this content if the indexer supports unpublished content. that is because the - // content.AfterUpdateDocumentCache will handle anything being published and will only index against indexers - // that only support published content. - // NOTE: The events for publishing have changed slightly from 6.0 to 6.1 and are streamlined in 6.1. Before - // this event would fire before publishing, then again after publishing. Now the save event fires once before - // publishing and that is all. - ExamineManager.Instance.ReIndexNode( sender.ToXml(), "content", ExamineManager.Instance.IndexProviderCollection.OfType() - .Where(x => x.SupportUnpublishedContent && x.EnableDefaultEventHandler)); + + //Index this item for all indexers if the content is published, otherwise if the item is not published + // then only index this for indexers supporting unpublished content + + .Where(x => isContentPublished || (x.SupportUnpublishedContent)) + .Where(x => x.EnableDefaultEventHandler)); } /// diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 944d51536a..bff6ea8189 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -288,6 +288,7 @@ +