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 @@ +