using System; using System.Linq; using System.Security; using System.Xml; using System.Xml.Linq; using Examine; using Examine.LuceneEngine; using Lucene.Net.Documents; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Services; using UmbracoExamine; using umbraco; using umbraco.BusinessLogic; using umbraco.cms.businesslogic; using umbraco.cms.businesslogic.member; using umbraco.interfaces; using Content = umbraco.cms.businesslogic.Content; using Document = umbraco.cms.businesslogic.web.Document; namespace Umbraco.Web.Search { /// /// Used to wire up events for Examine /// public sealed class ExamineEvents : ApplicationEventHandler { /// /// Once the application has started we should bind to all events and initialize the providers. /// /// /// /// /// We need to do this on the Started event as to guarantee that all resolvers are setup properly. /// [SecuritySafeCritical] protected override void ApplicationStarted(UmbracoApplicationBase httpApplication, ApplicationContext applicationContext) { LogHelper.Info("Initializing Examine and binding to business logic events"); var registeredProviders = ExamineManager.Instance.IndexProviderCollection .OfType().Count(x => x.EnableDefaultEventHandler); LogHelper.Info("Adding examine event handlers for index providers: {0}", () => registeredProviders); //don't bind event handlers if we're not suppose to listen 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; Member.AfterSave += MemberAfterSave; Member.AfterDelete += MemberAfterDelete; var contentIndexer = ExamineManager.Instance.IndexProviderCollection["InternalIndexer"] as UmbracoContentIndexer; if (contentIndexer != null) { contentIndexer.DocumentWriting += IndexerDocumentWriting; } var memberIndexer = ExamineManager.Instance.IndexProviderCollection["InternalMemberIndexer"] as UmbracoMemberIndexer; if (memberIndexer != null) { memberIndexer.DocumentWriting += IndexerDocumentWriting; } } [SecuritySafeCritical] static void ContentServiceTrashed(IContentService sender, Core.Events.MoveEventArgs e) { IndexConent(e.Entity); } [SecuritySafeCritical] static void MediaServiceTrashed(IMediaService sender, Core.Events.MoveEventArgs e) { IndexMedia(e.Entity); } [SecuritySafeCritical] static void ContentServiceMoved(IContentService sender, Umbraco.Core.Events.MoveEventArgs e) { IndexConent(e.Entity); } [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))); } [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); } [SecuritySafeCritical] private static void MemberAfterSave(Member sender, SaveEventArgs e) { //ensure that only the providers are flagged to listen execute var xml = ExamineXmlExtensions.ToXElement(sender.ToXml(new System.Xml.XmlDocument(), false)); var providers = ExamineManager.Instance.IndexProviderCollection.OfType() .Where(x => x.EnableDefaultEventHandler); ExamineManager.Instance.ReIndexNode(xml, IndexTypes.Member, providers); } [SecuritySafeCritical] private static void MemberAfterDelete(Member sender, DeleteEventArgs e) { var nodeId = sender.Id.ToString(); //ensure that only the providers are flagged to listen execute ExamineManager.Instance.DeleteFromIndex(nodeId, ExamineManager.Instance.IndexProviderCollection.OfType() .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 /// /// /// [SecuritySafeCritical] private static void IndexerDocumentWriting(object sender, DocumentWritingEventArgs e) { if (e.Fields.Keys.Contains("nodeName")) { //add the lower cased version e.Document.Add(new Field("__nodeName", e.Fields["nodeName"].ToLower(), Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.NO )); } } private static void IndexMedia(IMedia sender) { ExamineManager.Instance.ReIndexNode( sender.ToXml(), "media", ExamineManager.Instance.IndexProviderCollection.OfType().Where(x => x.EnableDefaultEventHandler)); } private static void IndexConent(IContent sender) { //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)); } /// /// Converts a content node to XDocument /// /// /// true if data is going to be returned from cache /// [SecuritySafeCritical] [Obsolete("This method is no longer used and will be removed from the core in future versions, the cacheOnly parameter has no effect. Use the other ToXDocument overload instead")] public static XDocument ToXDocument(Content node, bool cacheOnly) { return ToXDocument(node); } /// /// Converts a content node to Xml /// /// /// [SecuritySafeCritical] private static XDocument ToXDocument(Content node) { if (TypeHelper.IsTypeAssignableFrom(node)) { return new XDocument(((Document) node).Content.ToXml()); } if (TypeHelper.IsTypeAssignableFrom(node)) { return new XDocument(((global::umbraco.cms.businesslogic.media.Media) node).MediaItem.ToXml()); } var xDoc = new XmlDocument(); var xNode = xDoc.CreateNode(XmlNodeType.Element, "node", ""); node.XmlPopulate(xDoc, ref xNode, false); if (xNode.Attributes["nodeTypeAlias"] == null) { //we'll add the nodeTypeAlias ourselves XmlAttribute d = xDoc.CreateAttribute("nodeTypeAlias"); d.Value = node.ContentType.Alias; xNode.Attributes.Append(d); } return new XDocument(ExamineXmlExtensions.ToXElement(xNode)); } } }