Working on #U4-1356 - Moving UmbracoExamine to core
This commit is contained in:
@@ -42,7 +42,7 @@
|
||||
<RestorePackages>true</RestorePackages>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<OutputPath>bin\</OutputPath>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
|
||||
<BaseAddress>285212672</BaseAddress>
|
||||
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
|
||||
@@ -66,14 +66,14 @@
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<OutputPath>bin\</OutputPath>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
|
||||
<BaseAddress>285212672</BaseAddress>
|
||||
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
|
||||
<ConfigurationOverrideFile>
|
||||
</ConfigurationOverrideFile>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<DocumentationFile>bin\umbraco.xml</DocumentationFile>
|
||||
<DocumentationFile>bin\Release\umbraco.xml</DocumentationFile>
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
<FileAlignment>4096</FileAlignment>
|
||||
<NoStdLib>false</NoStdLib>
|
||||
|
||||
349
src/UmbracoExamine/BaseUmbracoIndexer.cs
Normal file
349
src/UmbracoExamine/BaseUmbracoIndexer.cs
Normal file
@@ -0,0 +1,349 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using Examine.LuceneEngine.Providers;
|
||||
using Lucene.Net.Analysis;
|
||||
using umbraco.BasePages;
|
||||
using umbraco.BusinessLogic;
|
||||
using UmbracoExamine.DataServices;
|
||||
using Examine;
|
||||
using System.IO;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace UmbracoExamine
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// An abstract provider containing the basic functionality to be able to query against
|
||||
/// Umbraco data.
|
||||
/// </summary>
|
||||
public abstract class BaseUmbracoIndexer : LuceneIndexer
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
protected BaseUmbracoIndexer()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor to allow for creating an indexer at runtime
|
||||
/// </summary>
|
||||
/// <param name="indexerData"></param>
|
||||
/// <param name="indexPath"></param>
|
||||
/// <param name="dataService"></param>
|
||||
/// <param name="analyzer"></param>
|
||||
[SecuritySafeCritical]
|
||||
protected BaseUmbracoIndexer(IIndexCriteria indexerData, DirectoryInfo indexPath, IDataService dataService, Analyzer analyzer, bool async)
|
||||
: base(indexerData, indexPath, analyzer, async)
|
||||
{
|
||||
DataService = dataService;
|
||||
}
|
||||
|
||||
[SecuritySafeCritical]
|
||||
protected BaseUmbracoIndexer(IIndexCriteria indexerData, Lucene.Net.Store.Directory luceneDirectory, IDataService dataService, Analyzer analyzer, bool async)
|
||||
: base(indexerData, luceneDirectory, analyzer, async)
|
||||
{
|
||||
DataService = dataService;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// If true, the IndexingActionHandler will be run to keep the default index up to date.
|
||||
/// </summary>
|
||||
public bool EnableDefaultEventHandler { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the manager will call the indexing methods when content is saved or deleted as
|
||||
/// opposed to cache being updated.
|
||||
/// </summary>
|
||||
public bool SupportUnpublishedContent { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The data service used for retreiving and submitting data to the cms
|
||||
/// </summary>
|
||||
public IDataService DataService { get; protected internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// the supported indexable types
|
||||
/// </summary>
|
||||
protected abstract IEnumerable<string> SupportedTypes { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Initialize
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Setup the properties for the indexer from the provider settings
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="config"></param>
|
||||
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
|
||||
{
|
||||
if (config["dataService"] != null && !string.IsNullOrEmpty(config["dataService"]))
|
||||
{
|
||||
//this should be a fully qualified type
|
||||
var serviceType = Type.GetType(config["dataService"]);
|
||||
DataService = (IDataService)Activator.CreateInstance(serviceType);
|
||||
}
|
||||
else if (DataService == null)
|
||||
{
|
||||
//By default, we will be using the UmbracoDataService
|
||||
//generally this would only need to be set differently for unit testing
|
||||
DataService = new UmbracoDataService();
|
||||
}
|
||||
|
||||
DataService.LogService.LogLevel = LoggingLevel.Normal;
|
||||
|
||||
if (config["logLevel"] != null && !string.IsNullOrEmpty(config["logLevel"]))
|
||||
{
|
||||
try
|
||||
{
|
||||
var logLevel = (LoggingLevel)Enum.Parse(typeof(LoggingLevel), config["logLevel"]);
|
||||
DataService.LogService.LogLevel = logLevel;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
//FAILED
|
||||
DataService.LogService.LogLevel = LoggingLevel.Normal;
|
||||
}
|
||||
}
|
||||
|
||||
DataService.LogService.ProviderName = name;
|
||||
|
||||
EnableDefaultEventHandler = true; //set to true by default
|
||||
bool enabled;
|
||||
if (bool.TryParse(config["enableDefaultEventHandler"], out enabled))
|
||||
{
|
||||
EnableDefaultEventHandler = enabled;
|
||||
}
|
||||
|
||||
DataService.LogService.AddVerboseLog(-1, string.Format("{0} indexer initializing", name));
|
||||
|
||||
base.Initialize(name, config);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
//public override void RebuildIndex()
|
||||
//{
|
||||
// //we can make the indexing rebuilding operation happen asynchronously in a web context by calling an http handler.
|
||||
// //we should only do this when async='true', the current request is running in a web context and the current user is authenticated.
|
||||
// if (RunAsync && HttpContext.Current != null)
|
||||
// {
|
||||
// if (UmbracoEnsuredPage.CurrentUser != null)
|
||||
// {
|
||||
// RebuildIndexAsync();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// //don't rebuild, user is not authenticated and if async is set then we shouldn't be generating the index files non-async either
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// base.RebuildIndex();
|
||||
// }
|
||||
//}
|
||||
|
||||
#region Protected
|
||||
|
||||
/////<summary>
|
||||
///// Calls a web request in a worker thread to rebuild the indexes
|
||||
/////</summary>
|
||||
//protected void RebuildIndexAsync()
|
||||
//{
|
||||
// if (HttpContext.Current != null && UmbracoEnsuredPage.CurrentUser != null)
|
||||
// {
|
||||
// var handler = VirtualPathUtility.ToAbsolute(ExamineHandlerPath);
|
||||
// var fullPath = HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority) + handler + "?index=" + Name;
|
||||
// var userContext = BasePage.umbracoUserContextID;
|
||||
// var userContextCookie = HttpContext.Current.Request.Cookies["UserContext"];
|
||||
// var thread = new Thread(() =>
|
||||
// {
|
||||
// var request = (HttpWebRequest)WebRequest.Create(fullPath);
|
||||
// request.CookieContainer = new CookieContainer();
|
||||
// request.CookieContainer.Add(new Cookie("UserContext", userContext, userContextCookie.Path,
|
||||
// string.IsNullOrEmpty(userContextCookie.Domain) ? "localhost" : userContextCookie.Domain));
|
||||
// request.Timeout = Timeout.Infinite;
|
||||
// request.UseDefaultCredentials = true;
|
||||
// request.Method = "GET";
|
||||
// request.Proxy = null;
|
||||
|
||||
// HttpWebResponse response;
|
||||
// try
|
||||
// {
|
||||
// response = (HttpWebResponse)request.GetResponse();
|
||||
|
||||
// if (response.StatusCode != HttpStatusCode.OK)
|
||||
// {
|
||||
// Log.Add(LogTypes.Custom, -1, "[UmbracoExamine] ExamineHandler request ended with an error: " + response.StatusDescription);
|
||||
// }
|
||||
// }
|
||||
// catch (WebException ex)
|
||||
// {
|
||||
// Log.Add(LogTypes.Custom, -1, "[UmbracoExamine] ExamineHandler request threw an exception: " + ex.Message);
|
||||
// }
|
||||
|
||||
// }) { IsBackground = true, Name = "ExamineAsyncHandler" };
|
||||
|
||||
// thread.Start();
|
||||
// }
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the node being indexed is of a correct type and is a descendent of the parent id specified.
|
||||
/// </summary>
|
||||
/// <param name="node"></param>
|
||||
/// <returns></returns>
|
||||
protected override bool ValidateDocument(XElement node)
|
||||
{
|
||||
//check if this document is a descendent of the parent
|
||||
if (IndexerData.ParentNodeId.HasValue && IndexerData.ParentNodeId.Value > 0)
|
||||
if (!((string)node.Attribute("path")).Contains("," + IndexerData.ParentNodeId.Value.ToString() + ","))
|
||||
return false;
|
||||
|
||||
return base.ValidateDocument(node);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reindexes all supported types
|
||||
/// </summary>
|
||||
protected override void PerformIndexRebuild()
|
||||
{
|
||||
foreach (var t in SupportedTypes)
|
||||
{
|
||||
IndexAll(t);
|
||||
}
|
||||
}
|
||||
|
||||
public override void ReIndexNode(XElement node, string type)
|
||||
{
|
||||
if (!SupportedTypes.Contains(type))
|
||||
return;
|
||||
|
||||
base.ReIndexNode(node, type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds an xpath statement to query against Umbraco data for the index type specified, then
|
||||
/// initiates the re-indexing of the data matched.
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
protected override void PerformIndexAll(string type)
|
||||
{
|
||||
if (!SupportedTypes.Contains(type))
|
||||
return;
|
||||
|
||||
var xPath = "//*[(number(@id) > 0 and (@isDoc or @nodeTypeAlias)){0}]"; //we'll add more filters to this below if needed
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
//create the xpath statement to match node type aliases if specified
|
||||
if (IndexerData.IncludeNodeTypes.Count() > 0)
|
||||
{
|
||||
sb.Append("(");
|
||||
foreach (var field in IndexerData.IncludeNodeTypes)
|
||||
{
|
||||
//this can be used across both schemas
|
||||
const string nodeTypeAlias = "(@nodeTypeAlias='{0}' or (count(@nodeTypeAlias)=0 and name()='{0}'))";
|
||||
|
||||
sb.Append(string.Format(nodeTypeAlias, field));
|
||||
sb.Append(" or ");
|
||||
}
|
||||
sb.Remove(sb.Length - 4, 4); //remove last " or "
|
||||
sb.Append(")");
|
||||
}
|
||||
|
||||
//create the xpath statement to match all children of the current node.
|
||||
if (IndexerData.ParentNodeId.HasValue && IndexerData.ParentNodeId.Value > 0)
|
||||
{
|
||||
if (sb.Length > 0)
|
||||
sb.Append(" and ");
|
||||
sb.Append("(");
|
||||
sb.Append("contains(@path, '," + IndexerData.ParentNodeId.Value + ",')"); //if the path contains comma - id - comma then the nodes must be a child
|
||||
sb.Append(")");
|
||||
}
|
||||
|
||||
//create the full xpath statement to match the appropriate nodes. If there is a filter
|
||||
//then apply it, otherwise just select all nodes.
|
||||
var filter = sb.ToString();
|
||||
xPath = string.Format(xPath, filter.Length > 0 ? " and " + filter : "");
|
||||
|
||||
//raise the event and set the xpath statement to the value returned
|
||||
var args = new IndexingNodesEventArgs(IndexerData, xPath, type);
|
||||
OnNodesIndexing(args);
|
||||
if (args.Cancel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
xPath = args.XPath;
|
||||
|
||||
DataService.LogService.AddVerboseLog(-1, string.Format("({0}) PerformIndexAll with XPATH: {1}", this.Name, xPath));
|
||||
|
||||
AddNodesToIndex(xPath, type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an XDocument for the entire tree stored for the IndexType specified.
|
||||
/// </summary>
|
||||
/// <param name="xPath">The xpath to the node.</param>
|
||||
/// <param name="type">The type of data to request from the data service.</param>
|
||||
/// <returns>Either the Content or Media xml. If the type is not of those specified null is returned</returns>
|
||||
protected virtual XDocument GetXDocument(string xPath, string type)
|
||||
{
|
||||
if (type == IndexTypes.Content)
|
||||
{
|
||||
if (this.SupportUnpublishedContent)
|
||||
{
|
||||
return DataService.ContentService.GetLatestContentByXPath(xPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
return DataService.ContentService.GetPublishedContentByXPath(xPath);
|
||||
}
|
||||
}
|
||||
else if (type == IndexTypes.Media)
|
||||
{
|
||||
return DataService.MediaService.GetLatestMediaByXpath(xPath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private
|
||||
/// <summary>
|
||||
/// Adds all nodes with the given xPath root.
|
||||
/// </summary>
|
||||
/// <param name="xPath">The x path.</param>
|
||||
/// <param name="type">The type.</param>
|
||||
private void AddNodesToIndex(string xPath, string type)
|
||||
{
|
||||
// Get all the nodes of nodeTypeAlias == nodeTypeAlias
|
||||
XDocument xDoc = GetXDocument(xPath, type);
|
||||
if (xDoc != null)
|
||||
{
|
||||
XElement rootNode = xDoc.Root;
|
||||
|
||||
IEnumerable<XElement> children = rootNode.Elements();
|
||||
|
||||
AddNodesToIndex(children, type);
|
||||
}
|
||||
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
64
src/UmbracoExamine/Config/IndexSetExtensions.cs
Normal file
64
src/UmbracoExamine/Config/IndexSetExtensions.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Examine;
|
||||
using UmbracoExamine.DataServices;
|
||||
using Examine.LuceneEngine.Config;
|
||||
|
||||
namespace UmbracoExamine.Config
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for IndexSet
|
||||
/// </summary>
|
||||
public static class IndexSetExtensions
|
||||
{
|
||||
|
||||
private static readonly object Locker = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Convert the indexset to indexerdata.
|
||||
/// This detects if there are no user/system fields specified and if not, uses the data service to look them
|
||||
/// up and update the in memory IndexSet.
|
||||
/// </summary>
|
||||
/// <param name="set"></param>
|
||||
/// <param name="svc"></param>
|
||||
/// <returns></returns>
|
||||
public static IIndexCriteria ToIndexCriteria(this IndexSet set, IDataService svc)
|
||||
{
|
||||
if (set.IndexUserFields.Count == 0)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
//we need to add all user fields to the collection if it is empty (this is the default if none are specified)
|
||||
var userFields = svc.ContentService.GetAllUserPropertyNames();
|
||||
foreach (var u in userFields)
|
||||
{
|
||||
set.IndexUserFields.Add(new IndexField() { Name = u });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (set.IndexAttributeFields.Count == 0)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
//we need to add all system fields to the collection if it is empty (this is the default if none are specified)
|
||||
var sysFields = svc.ContentService.GetAllSystemPropertyNames();
|
||||
foreach (var s in sysFields)
|
||||
{
|
||||
set.IndexAttributeFields.Add(new IndexField() { Name = s });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new IndexCriteria(
|
||||
set.IndexAttributeFields.Cast<IIndexField>().ToArray(),
|
||||
set.IndexUserFields.Cast<IIndexField>().ToArray(),
|
||||
set.IncludeNodeTypes.ToList().Select(x => x.Name).ToArray(),
|
||||
set.ExcludeNodeTypes.ToList().Select(x => x.Name).ToArray(),
|
||||
set.IndexParentId);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
72
src/UmbracoExamine/ContentExtensions.cs
Normal file
72
src/UmbracoExamine/ContentExtensions.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.XPath;
|
||||
using umbraco;
|
||||
using umbraco.cms.businesslogic;
|
||||
using umbraco.cms.businesslogic.web;
|
||||
using Examine.LuceneEngine;
|
||||
|
||||
namespace UmbracoExamine
|
||||
{
|
||||
/// <summary>
|
||||
/// Static methods to help query umbraco xml
|
||||
/// </summary>
|
||||
public static class ContentExtensions
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Converts a content node to XDocument
|
||||
/// </summary>
|
||||
/// <param name="node"></param>
|
||||
/// <param name="cacheOnly">true if data is going to be returned from cache</param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// If the type of node is not a Document, the cacheOnly has no effect, it will use the API to return
|
||||
/// the xml.
|
||||
/// </remarks>
|
||||
[SecuritySafeCritical]
|
||||
public static XDocument ToXDocument(this Content node, bool cacheOnly)
|
||||
{
|
||||
if (cacheOnly && node.GetType().Equals(typeof(Document)))
|
||||
{
|
||||
var umbXml = library.GetXmlNodeById(node.Id.ToString());
|
||||
if (umbXml != null)
|
||||
{
|
||||
return umbXml.ToXDocument();
|
||||
}
|
||||
}
|
||||
|
||||
//this will also occur if umbraco hasn't cached content yet....
|
||||
|
||||
//if it's not a using cache and it's not cacheOnly, then retrieve the Xml using the API
|
||||
return node.ToXDocument();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a content node to Xml
|
||||
/// </summary>
|
||||
/// <param name="node"></param>
|
||||
/// <returns></returns>
|
||||
[SecuritySafeCritical]
|
||||
private static XDocument ToXDocument(this Content node)
|
||||
{
|
||||
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(xNode.ToXElement());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
28
src/UmbracoExamine/DataServices/IContentService.cs
Normal file
28
src/UmbracoExamine/DataServices/IContentService.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UmbracoExamine.DataServices
|
||||
{
|
||||
public interface IContentService
|
||||
{
|
||||
XDocument GetLatestContentByXPath(string xpath);
|
||||
XDocument GetPublishedContentByXPath(string xpath);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of ALL properties names for all nodes defined in the data source
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IEnumerable<string> GetAllUserPropertyNames();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of ALL system property names for all nodes defined in the data source
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IEnumerable<string> GetAllSystemPropertyNames();
|
||||
|
||||
string StripHtml(string value);
|
||||
bool IsProtected(int nodeId, string path);
|
||||
}
|
||||
}
|
||||
14
src/UmbracoExamine/DataServices/IDataService.cs
Normal file
14
src/UmbracoExamine/DataServices/IDataService.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Web;
|
||||
|
||||
|
||||
namespace UmbracoExamine.DataServices
|
||||
{
|
||||
public interface IDataService
|
||||
{
|
||||
IContentService ContentService { get; }
|
||||
ILogService LogService { get; }
|
||||
IMediaService MediaService { get; }
|
||||
|
||||
string MapPath(string virtualPath);
|
||||
}
|
||||
}
|
||||
12
src/UmbracoExamine/DataServices/ILogService.cs
Normal file
12
src/UmbracoExamine/DataServices/ILogService.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
namespace UmbracoExamine.DataServices
|
||||
{
|
||||
public interface ILogService
|
||||
{
|
||||
string ProviderName { get; set; }
|
||||
void AddErrorLog(int nodeId, string msg);
|
||||
void AddInfoLog(int nodeId, string msg);
|
||||
void AddVerboseLog(int nodeId, string msg);
|
||||
LoggingLevel LogLevel { get; set; }
|
||||
}
|
||||
}
|
||||
9
src/UmbracoExamine/DataServices/IMediaService.cs
Normal file
9
src/UmbracoExamine/DataServices/IMediaService.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
namespace UmbracoExamine.DataServices
|
||||
{
|
||||
public interface IMediaService
|
||||
{
|
||||
XDocument GetLatestMediaByXpath(string xpath);
|
||||
}
|
||||
}
|
||||
164
src/UmbracoExamine/DataServices/UmbracoContentService.cs
Normal file
164
src/UmbracoExamine/DataServices/UmbracoContentService.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using umbraco;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml;
|
||||
using umbraco.cms.businesslogic.web;
|
||||
using System.Collections;
|
||||
using System.Xml.XPath;
|
||||
using umbraco.DataLayer;
|
||||
using umbraco.BusinessLogic;
|
||||
using UmbracoExamine.Config;
|
||||
using Examine.LuceneEngine;
|
||||
using System.Data.SqlClient;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace UmbracoExamine.DataServices
|
||||
{
|
||||
public class UmbracoContentService : UmbracoExamine.DataServices.IContentService
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// removes html markup from a string
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
[SecuritySafeCritical]
|
||||
public string StripHtml(string value)
|
||||
{
|
||||
return library.StripHtml(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets published content by xpath
|
||||
/// </summary>
|
||||
/// <param name="xpath"></param>
|
||||
/// <returns></returns>
|
||||
[SecuritySafeCritical]
|
||||
public XDocument GetPublishedContentByXPath(string xpath)
|
||||
{
|
||||
return library.GetXmlNodeByXPath(xpath).ToXDocument();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is quite an intensive operation...
|
||||
/// get all root content, then get the XML structure for all children,
|
||||
/// then run xpath against the navigator that's created
|
||||
/// </summary>
|
||||
/// <param name="xpath"></param>
|
||||
/// <returns></returns>
|
||||
[SecuritySafeCritical]
|
||||
public XDocument GetLatestContentByXPath(string xpath)
|
||||
{
|
||||
|
||||
var rootContent = Document.GetRootDocuments();
|
||||
var xmlContent = XDocument.Parse("<content></content>");
|
||||
var xDoc = new XmlDocument();
|
||||
foreach (var c in rootContent)
|
||||
{
|
||||
var xNode = xDoc.CreateNode(XmlNodeType.Element, "node", "");
|
||||
c.XmlPopulate(xDoc, ref xNode, true);
|
||||
|
||||
if (xNode.Attributes["nodeTypeAlias"] == null)
|
||||
{
|
||||
//we'll add the nodeTypeAlias ourselves
|
||||
XmlAttribute d = xDoc.CreateAttribute("nodeTypeAlias");
|
||||
d.Value = c.ContentType.Alias;
|
||||
xNode.Attributes.Append(d);
|
||||
}
|
||||
|
||||
xmlContent.Root.Add(xNode.ToXElement());
|
||||
}
|
||||
var result = ((IEnumerable)xmlContent.XPathEvaluate(xpath)).Cast<XElement>();
|
||||
return result.ToXDocument();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unfortunately, we need to implement our own IsProtected method since
|
||||
/// the Umbraco core code requires an HttpContext for this method and when we're running
|
||||
/// async, there is no context
|
||||
/// </summary>
|
||||
/// <param name="documentId"></param>
|
||||
/// <returns></returns>
|
||||
[SecuritySafeCritical]
|
||||
private XmlNode GetPage(int documentId)
|
||||
{
|
||||
XmlNode x = Access.AccessXml.SelectSingleNode("/access/page [@id=" + documentId.ToString() + "]");
|
||||
return x;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unfortunately, we need to implement our own IsProtected method since
|
||||
/// the Umbraco core code requires an HttpContext for this method and when we're running
|
||||
/// async, there is no context
|
||||
/// </summary>
|
||||
/// <param name="nodeId"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsProtected(int nodeId, string path)
|
||||
{
|
||||
foreach (string id in path.Split(','))
|
||||
{
|
||||
if (GetPage(int.Parse(id)) != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of all of the user defined property names in Umbraco
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[SecuritySafeCritical]
|
||||
public IEnumerable<string> GetAllUserPropertyNames()
|
||||
{
|
||||
//this is how umb codebase 4.0 does this... booo, should be in the data layer, will fix in 4.1
|
||||
|
||||
var aliases = new List<string>();
|
||||
var fieldSql = "select distinct alias from cmsPropertyType order by alias";
|
||||
try
|
||||
{
|
||||
using (var dr = Application.SqlHelper.ExecuteReader(fieldSql))
|
||||
{
|
||||
while (dr.Read())
|
||||
{
|
||||
aliases.Add(dr.GetString("alias"));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is SqlHelperException || ex is SqlException)
|
||||
{
|
||||
//if this happens, it could be due to wrong connection string, or something else.
|
||||
//we don't want to crash the app because of this so we'll actually swallow this
|
||||
//exception... Unfortunately logging probably won't work in this situation either :(
|
||||
|
||||
Debug.WriteLine("EXCEPTION OCCURRED reading GetAllUserPropertyNames: " + ex.Message, "Error");
|
||||
Trace.WriteLine("EXCEPTION OCCURRED reading GetAllUserPropertyNames: " + ex.Message, "Error");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
return aliases;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of all system field names in Umbraco
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<string> GetAllSystemPropertyNames()
|
||||
{
|
||||
return UmbracoContentIndexer.IndexFieldPolicies.Select(x => x.Key);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
25
src/UmbracoExamine/DataServices/UmbracoDataService.cs
Normal file
25
src/UmbracoExamine/DataServices/UmbracoDataService.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Web;
|
||||
using System.Web.Hosting;
|
||||
|
||||
namespace UmbracoExamine.DataServices
|
||||
{
|
||||
public class UmbracoDataService : IDataService
|
||||
{
|
||||
public UmbracoDataService()
|
||||
{
|
||||
ContentService = new UmbracoContentService();
|
||||
MediaService = new UmbracoMediaService();
|
||||
LogService = new UmbracoLogService();
|
||||
}
|
||||
|
||||
public IContentService ContentService { get; protected set; }
|
||||
public IMediaService MediaService { get; protected set; }
|
||||
public ILogService LogService { get; protected set; }
|
||||
|
||||
public string MapPath(string virtualPath)
|
||||
{
|
||||
return HostingEnvironment.MapPath(virtualPath);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
36
src/UmbracoExamine/DataServices/UmbracoLogService.cs
Normal file
36
src/UmbracoExamine/DataServices/UmbracoLogService.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using umbraco.BusinessLogic;
|
||||
|
||||
namespace UmbracoExamine.DataServices
|
||||
{
|
||||
public class UmbracoLogService : UmbracoExamine.DataServices.ILogService
|
||||
{
|
||||
public string ProviderName { get; set; }
|
||||
|
||||
[SecuritySafeCritical]
|
||||
public void AddInfoLog(int nodeId, string msg)
|
||||
{
|
||||
Log.Add(LogTypes.Custom, nodeId, "[UmbracoExamine] (" + ProviderName + ")" + msg);
|
||||
}
|
||||
|
||||
[SecuritySafeCritical]
|
||||
public void AddErrorLog(int nodeId, string msg)
|
||||
{
|
||||
Log.Add(LogTypes.Error, nodeId, "[UmbracoExamine] (" + ProviderName + ")" + msg);
|
||||
}
|
||||
|
||||
[SecuritySafeCritical]
|
||||
public void AddVerboseLog(int nodeId, string msg)
|
||||
{
|
||||
if (LogLevel == LoggingLevel.Verbose)
|
||||
Log.Add(LogTypes.Custom, nodeId, "[UmbracoExamine] (" + ProviderName + ")" + msg);
|
||||
}
|
||||
|
||||
public LoggingLevel LogLevel { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
51
src/UmbracoExamine/DataServices/UmbracoMediaService.cs
Normal file
51
src/UmbracoExamine/DataServices/UmbracoMediaService.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using System.Xml.XPath;
|
||||
using System.Xml.Linq;
|
||||
using umbraco.cms.businesslogic.media;
|
||||
using System.Collections;
|
||||
using Examine.LuceneEngine;
|
||||
|
||||
namespace UmbracoExamine.DataServices
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Data service used to query for media
|
||||
/// </summary>
|
||||
public class UmbracoMediaService : UmbracoExamine.DataServices.IMediaService
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// This is quite an intensive operation...
|
||||
/// get all root media, then get the XML structure for all children,
|
||||
/// then run xpath against the navigator that's created
|
||||
/// </summary>
|
||||
/// <param name="xpath"></param>
|
||||
/// <returns></returns>
|
||||
[SecuritySafeCritical]
|
||||
public XDocument GetLatestMediaByXpath(string xpath)
|
||||
{
|
||||
|
||||
Media[] rootMedia = Media.GetRootMedias();
|
||||
var xmlMedia = XDocument.Parse("<media></media>");
|
||||
foreach (Media media in rootMedia)
|
||||
{
|
||||
xmlMedia.Root.Add(GetMediaItem(media.Id));
|
||||
}
|
||||
var result = ((IEnumerable)xmlMedia.XPathEvaluate(xpath)).Cast<XElement>();
|
||||
return result.ToXDocument();
|
||||
}
|
||||
|
||||
[SecuritySafeCritical]
|
||||
private XElement GetMediaItem(int nodeId)
|
||||
{
|
||||
var nodes = umbraco.library.GetMedia(nodeId, true);
|
||||
return XElement.Parse(nodes.Current.OuterXml);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
38
src/UmbracoExamine/IndexTypes.cs
Normal file
38
src/UmbracoExamine/IndexTypes.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace UmbracoExamine
|
||||
{
|
||||
/// <summary>
|
||||
/// The index types stored in the Lucene Index
|
||||
/// </summary>
|
||||
public static class IndexTypes
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The content index type
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Is lower case because the Standard Analyzer requires lower case
|
||||
/// </remarks>
|
||||
public const string Content = "content";
|
||||
|
||||
/// <summary>
|
||||
/// The media index type
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Is lower case because the Standard Analyzer requires lower case
|
||||
/// </remarks>
|
||||
public const string Media = "media";
|
||||
|
||||
/// <summary>
|
||||
/// The member index type
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Is lower case because the Standard Analyzer requires lower case
|
||||
/// </remarks>
|
||||
public const string Member = "member";
|
||||
}
|
||||
}
|
||||
12
src/UmbracoExamine/LoggingLevel.cs
Normal file
12
src/UmbracoExamine/LoggingLevel.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace UmbracoExamine
|
||||
{
|
||||
public enum LoggingLevel
|
||||
{
|
||||
Verbose, Normal
|
||||
}
|
||||
}
|
||||
36
src/UmbracoExamine/Properties/AssemblyInfo.cs
Normal file
36
src/UmbracoExamine/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyCompany("umbraco")]
|
||||
[assembly: AssemblyCopyright("Copyright © Umbraco 2012")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: AssemblyTitle("UmbracoExamine")]
|
||||
[assembly: AssemblyDescription("Umbraco index & search providers based on the Examine model using Lucene.NET 2.9.2")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyProduct("UmbracoExamine")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("31c5b048-cfa8-49b4-8983-bdba0f99eef5")]
|
||||
|
||||
[assembly: NeutralResourcesLanguage("en-US")]
|
||||
|
||||
//NOTE: WE cannot make change the major version to be the same as Umbraco because of backwards compatibility, however we
|
||||
// will make the minor version the same as the umbraco version
|
||||
[assembly: AssemblyVersion("0.6.0.*")]
|
||||
[assembly: AssemblyFileVersion("0.6.0.*")]
|
||||
|
||||
[assembly: AllowPartiallyTrustedCallers]
|
||||
|
||||
[assembly: InternalsVisibleTo("Umbraco.Tests")]
|
||||
37
src/UmbracoExamine/SearchCriteria/ExamineValue.cs
Normal file
37
src/UmbracoExamine/SearchCriteria/ExamineValue.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Examine.SearchCriteria;
|
||||
|
||||
namespace UmbracoExamine.SearchCriteria
|
||||
{
|
||||
internal class ExamineValue : IExamineValue
|
||||
{
|
||||
public ExamineValue(Examineness vagueness, string value) : this(vagueness, value, 1)
|
||||
{
|
||||
}
|
||||
|
||||
public ExamineValue(Examineness vagueness, string value, float level)
|
||||
{
|
||||
this.Examineness = vagueness;
|
||||
this.Value = value;
|
||||
this.Level = level;
|
||||
}
|
||||
|
||||
public Examineness Examineness
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string Value
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public float Level
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
70
src/UmbracoExamine/SearchCriteria/LuceneBooleanOperation.cs
Normal file
70
src/UmbracoExamine/SearchCriteria/LuceneBooleanOperation.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using Examine.SearchCriteria;
|
||||
using Lucene.Net.Search;
|
||||
|
||||
namespace UmbracoExamine.SearchCriteria
|
||||
{
|
||||
/// <summary>
|
||||
/// An implementation of the fluent API boolean operations
|
||||
/// </summary>
|
||||
public class LuceneBooleanOperation : IBooleanOperation
|
||||
{
|
||||
private LuceneSearchCriteria search;
|
||||
|
||||
internal LuceneBooleanOperation(LuceneSearchCriteria search)
|
||||
{
|
||||
this.search = search;
|
||||
}
|
||||
|
||||
#region IBooleanOperation Members
|
||||
|
||||
/// <summary>
|
||||
/// Sets the next operation to be AND
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IQuery And()
|
||||
{
|
||||
return new LuceneQuery(this.search, BooleanClause.Occur.MUST);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the next operation to be OR
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IQuery Or()
|
||||
{
|
||||
return new LuceneQuery(this.search, BooleanClause.Occur.SHOULD);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the next operation to be NOT
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IQuery Not()
|
||||
{
|
||||
return new LuceneQuery(this.search, BooleanClause.Occur.MUST_NOT);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compiles this instance for fluent API conclusion
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ISearchCriteria Compile()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(this.search.SearchIndexType))
|
||||
{
|
||||
var query = this.search.query;
|
||||
|
||||
this.search.query = new BooleanQuery();
|
||||
this.search.query.Add(query, BooleanClause.Occur.MUST);
|
||||
|
||||
//this.search.query.Add(this.search.queryParser.Parse("(" + query.ToString() + ")"), BooleanClause.Occur.MUST);
|
||||
|
||||
this.search.FieldInternal(LuceneExamineIndexer.IndexTypeFieldName, new ExamineValue(Examineness.Explicit, this.search.SearchIndexType.ToString().ToLower()), BooleanClause.Occur.MUST);
|
||||
}
|
||||
|
||||
return this.search;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
330
src/UmbracoExamine/SearchCriteria/LuceneQuery.cs
Normal file
330
src/UmbracoExamine/SearchCriteria/LuceneQuery.cs
Normal file
@@ -0,0 +1,330 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Examine.SearchCriteria;
|
||||
using Lucene.Net.Search;
|
||||
|
||||
namespace UmbracoExamine.SearchCriteria
|
||||
{
|
||||
public class LuceneQuery : IQuery
|
||||
{
|
||||
private LuceneSearchCriteria search;
|
||||
private BooleanClause.Occur occurance;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LuceneQuery"/> class.
|
||||
/// </summary>
|
||||
/// <param name="search">The search.</param>
|
||||
/// <param name="occurance">The occurance.</param>
|
||||
internal LuceneQuery(LuceneSearchCriteria search, BooleanClause.Occur occurance)
|
||||
{
|
||||
this.search = search;
|
||||
this.occurance = occurance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the boolean operation which this query method will be added as
|
||||
/// </summary>
|
||||
/// <value>The boolean operation.</value>
|
||||
public BooleanOperation BooleanOperation
|
||||
{
|
||||
get { return occurance.ToBooleanOperation(); }
|
||||
}
|
||||
|
||||
|
||||
#region ISearch Members
|
||||
|
||||
/// <summary>
|
||||
/// Query on the id
|
||||
/// </summary>
|
||||
/// <param name="id">The id.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation Id(int id)
|
||||
{
|
||||
return this.search.IdInternal(id, this.occurance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query on the NodeName
|
||||
/// </summary>
|
||||
/// <param name="nodeName">Name of the node.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation NodeName(string nodeName)
|
||||
{
|
||||
return this.search.NodeNameInternal(new ExamineValue(Examineness.Explicit, nodeName), occurance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query on the NodeTypeAlias
|
||||
/// </summary>
|
||||
/// <param name="nodeTypeAlias">The node type alias.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation NodeTypeAlias(string nodeTypeAlias)
|
||||
{
|
||||
return this.search.NodeTypeAliasInternal(new ExamineValue(Examineness.Explicit, nodeTypeAlias), occurance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query on the Parent ID
|
||||
/// </summary>
|
||||
/// <param name="id">The id of the parent.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation ParentId(int id)
|
||||
{
|
||||
return this.search.ParentIdInternal(id, occurance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query on the specified field
|
||||
/// </summary>
|
||||
/// <param name="fieldName">Name of the field.</param>
|
||||
/// <param name="fieldValue">The field value.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation Field(string fieldName, string fieldValue)
|
||||
{
|
||||
return this.search.FieldInternal(fieldName, new ExamineValue(Examineness.Explicit, fieldValue), occurance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ranges the specified field name.
|
||||
/// </summary>
|
||||
/// <param name="fieldName">Name of the field.</param>
|
||||
/// <param name="start">The start.</param>
|
||||
/// <param name="end">The end.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation Range(string fieldName, DateTime start, DateTime end)
|
||||
{
|
||||
return this.Range(fieldName, start, end, true, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ranges the specified field name.
|
||||
/// </summary>
|
||||
/// <param name="fieldName">Name of the field.</param>
|
||||
/// <param name="start">The start.</param>
|
||||
/// <param name="end">The end.</param>
|
||||
/// <param name="includeLower">if set to <c>true</c> [include lower].</param>
|
||||
/// <param name="includeUpper">if set to <c>true</c> [include upper].</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation Range(string fieldName, DateTime start, DateTime end, bool includeLower, bool includeUpper)
|
||||
{
|
||||
return this.search.Range(fieldName, start, end, includeLower, includeUpper);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ranges the specified field name.
|
||||
/// </summary>
|
||||
/// <param name="fieldName">Name of the field.</param>
|
||||
/// <param name="start">The start.</param>
|
||||
/// <param name="end">The end.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation Range(string fieldName, int start, int end)
|
||||
{
|
||||
return this.Range(fieldName, start, end, true, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ranges the specified field name.
|
||||
/// </summary>
|
||||
/// <param name="fieldName">Name of the field.</param>
|
||||
/// <param name="start">The start.</param>
|
||||
/// <param name="end">The end.</param>
|
||||
/// <param name="includeLower">if set to <c>true</c> [include lower].</param>
|
||||
/// <param name="includeUpper">if set to <c>true</c> [include upper].</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation Range(string fieldName, int start, int end, bool includeLower, bool includeUpper)
|
||||
{
|
||||
return this.search.RangeInternal(fieldName, start, end, includeLower, includeUpper, occurance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ranges the specified field name.
|
||||
/// </summary>
|
||||
/// <param name="fieldName">Name of the field.</param>
|
||||
/// <param name="start">The start.</param>
|
||||
/// <param name="end">The end.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation Range(string fieldName, string start, string end)
|
||||
{
|
||||
return this.Range(fieldName, start, end, true, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ranges the specified field name.
|
||||
/// </summary>
|
||||
/// <param name="fieldName">Name of the field.</param>
|
||||
/// <param name="start">The start.</param>
|
||||
/// <param name="end">The end.</param>
|
||||
/// <param name="includeLower">if set to <c>true</c> [include lower].</param>
|
||||
/// <param name="includeUpper">if set to <c>true</c> [include upper].</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation Range(string fieldName, string start, string end, bool includeLower, bool includeUpper)
|
||||
{
|
||||
return this.search.RangeInternal(fieldName, start, end, includeLower, includeUpper, occurance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query on the NodeName
|
||||
/// </summary>
|
||||
/// <param name="nodeName">Name of the node.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation NodeName(IExamineValue nodeName)
|
||||
{
|
||||
return this.search.NodeNameInternal(nodeName, occurance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query on the NodeTypeAlias
|
||||
/// </summary>
|
||||
/// <param name="nodeTypeAlias">The node type alias.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation NodeTypeAlias(IExamineValue nodeTypeAlias)
|
||||
{
|
||||
return this.search.NodeTypeAliasInternal(nodeTypeAlias, occurance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query on the specified field
|
||||
/// </summary>
|
||||
/// <param name="fieldName">Name of the field.</param>
|
||||
/// <param name="fieldValue">The field value.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation Field(string fieldName, IExamineValue fieldValue)
|
||||
{
|
||||
return this.search.FieldInternal(fieldName, fieldValue, occurance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries multiple fields with each being an And boolean operation
|
||||
/// </summary>
|
||||
/// <param name="fields">The fields.</param>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation GroupedAnd(IEnumerable<string> fields, params string[] query)
|
||||
{
|
||||
var fieldVals = new List<IExamineValue>();
|
||||
foreach (var f in query)
|
||||
{
|
||||
fieldVals.Add(new ExamineValue(Examineness.Explicit, f));
|
||||
}
|
||||
return this.search.GroupedAndInternal(fields.ToArray(), fieldVals.ToArray(), this.occurance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries multiple fields with each being an And boolean operation
|
||||
/// </summary>
|
||||
/// <param name="fields">The fields.</param>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation GroupedAnd(IEnumerable<string> fields, params IExamineValue[] query)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries multiple fields with each being an Or boolean operation
|
||||
/// </summary>
|
||||
/// <param name="fields">The fields.</param>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation GroupedOr(IEnumerable<string> fields, params string[] query)
|
||||
{
|
||||
var fieldVals = new List<IExamineValue>();
|
||||
foreach (var f in query)
|
||||
{
|
||||
fieldVals.Add(new ExamineValue(Examineness.Explicit, f));
|
||||
}
|
||||
return this.search.GroupedOrInternal(fields.ToArray(), fieldVals.ToArray(), this.occurance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries multiple fields with each being an Or boolean operation
|
||||
/// </summary>
|
||||
/// <param name="fields">The fields.</param>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation GroupedOr(IEnumerable<string> fields, params IExamineValue[] query)
|
||||
{
|
||||
return this.search.GroupedOrInternal(fields.ToArray(), query, this.occurance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries multiple fields with each being an Not boolean operation
|
||||
/// </summary>
|
||||
/// <param name="fields">The fields.</param>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation GroupedNot(IEnumerable<string> fields, params string[] query)
|
||||
{
|
||||
var fieldVals = new List<IExamineValue>();
|
||||
foreach (var f in query)
|
||||
{
|
||||
fieldVals.Add(new ExamineValue(Examineness.Explicit, f));
|
||||
}
|
||||
return this.search.GroupedNotInternal(fields.ToArray(), fieldVals.ToArray(), this.occurance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries multiple fields with each being an Not boolean operation
|
||||
/// </summary>
|
||||
/// <param name="fields">The fields.</param>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation GroupedNot(IEnumerable<string> fields, params IExamineValue[] query)
|
||||
{
|
||||
return this.search.GroupedNotInternal(fields.ToArray(), query, this.occurance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries on multiple fields with their inclusions customly defined
|
||||
/// </summary>
|
||||
/// <param name="fields">The fields.</param>
|
||||
/// <param name="operations">The operations.</param>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation GroupedFlexible(IEnumerable<string> fields, IEnumerable<BooleanOperation> operations, params string[] query)
|
||||
{
|
||||
var fieldVals = new List<IExamineValue>();
|
||||
foreach (var f in query)
|
||||
{
|
||||
fieldVals.Add(new ExamineValue(Examineness.Explicit, f));
|
||||
}
|
||||
return this.search.GroupedFlexibleInternal(fields.ToArray(), operations.ToArray(), fieldVals.ToArray(), occurance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries on multiple fields with their inclusions customly defined
|
||||
/// </summary>
|
||||
/// <param name="fields">The fields.</param>
|
||||
/// <param name="operations">The operations.</param>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation GroupedFlexible(IEnumerable<string> fields, IEnumerable<BooleanOperation> operations, params IExamineValue[] query)
|
||||
{
|
||||
return this.search.GroupedFlexibleInternal(fields.ToArray(), operations.ToArray(), query, occurance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Orders the results by the specified fields
|
||||
/// </summary>
|
||||
/// <param name="fieldNames">The field names.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation OrderBy(params string[] fieldNames)
|
||||
{
|
||||
return this.search.OrderBy(fieldNames);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Orders the results by the specified fields in a descending order
|
||||
/// </summary>
|
||||
/// <param name="fieldNames">The field names.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation OrderByDescending(params string[] fieldNames)
|
||||
{
|
||||
return this.search.OrderByDescending(fieldNames);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
559
src/UmbracoExamine/SearchCriteria/LuceneSearchCriteria.cs
Normal file
559
src/UmbracoExamine/SearchCriteria/LuceneSearchCriteria.cs
Normal file
@@ -0,0 +1,559 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Examine;
|
||||
using Examine.SearchCriteria;
|
||||
using Lucene.Net.Analysis;
|
||||
using Lucene.Net.QueryParsers;
|
||||
using Lucene.Net.Search;
|
||||
using Lucene.Net.Search.Spans;
|
||||
using Lucene.Net.Index;
|
||||
using Lucene.Net.Documents;
|
||||
|
||||
namespace UmbracoExamine.SearchCriteria
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is used to query against Lucene.Net
|
||||
/// </summary>
|
||||
public class LuceneSearchCriteria : ISearchCriteria
|
||||
{
|
||||
internal MultiFieldQueryParser queryParser;
|
||||
internal BooleanQuery query;
|
||||
internal List<SortField> sortFields = new List<SortField>();
|
||||
private readonly BooleanClause.Occur occurance;
|
||||
private readonly Lucene.Net.Util.Version luceneVersion = Lucene.Net.Util.Version.LUCENE_29;
|
||||
|
||||
internal LuceneSearchCriteria(string type, Analyzer analyzer, string[] fields, bool allowLeadingWildcards, BooleanOperation occurance)
|
||||
{
|
||||
Enforcer.ArgumentNotNull(fields, "fields");
|
||||
|
||||
SearchIndexType = type;
|
||||
query = new BooleanQuery();
|
||||
this.BooleanOperation = occurance;
|
||||
this.queryParser = new MultiFieldQueryParser(luceneVersion, fields, analyzer);
|
||||
this.queryParser.SetAllowLeadingWildcard(allowLeadingWildcards);
|
||||
this.occurance = occurance.ToLuceneOccurance();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the boolean operation which this query method will be added as
|
||||
/// </summary>
|
||||
/// <value>The boolean operation.</value>
|
||||
public BooleanOperation BooleanOperation
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="System.String"/> that represents this instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="System.String"/> that represents this instance.
|
||||
/// </returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{{ SearchIndexType: {0}, LuceneQuery: {1} }}", this.SearchIndexType, this.query.ToString());
|
||||
}
|
||||
|
||||
private static void ValidateIExamineValue(IExamineValue v)
|
||||
{
|
||||
var ev = v as ExamineValue;
|
||||
if (ev == null)
|
||||
{
|
||||
throw new ArgumentException("IExamineValue was not created from this provider. Ensure that it is created from the ISearchCriteria this provider exposes");
|
||||
}
|
||||
}
|
||||
|
||||
#region ISearchCriteria Members
|
||||
|
||||
public string SearchIndexType
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public bool IncludeHitCount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public int TotalHits
|
||||
{
|
||||
get;
|
||||
internal protected set;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ISearch Members
|
||||
|
||||
/// <summary>
|
||||
/// Query on the id
|
||||
/// </summary>
|
||||
/// <param name="id">The id.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation Id(int id)
|
||||
{
|
||||
return IdInternal(id, occurance);
|
||||
}
|
||||
|
||||
internal protected IBooleanOperation IdInternal(int id, BooleanClause.Occur occurance)
|
||||
{
|
||||
//use a query parser (which uses the analyzer) to build up the field query which we want
|
||||
query.Add(this.queryParser.GetFieldQuery(LuceneExamineIndexer.IndexNodeIdFieldName, id.ToString()), occurance);
|
||||
|
||||
return new LuceneBooleanOperation(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query on the NodeName
|
||||
/// </summary>
|
||||
/// <param name="nodeName">Name of the node.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation NodeName(string nodeName)
|
||||
{
|
||||
Enforcer.ArgumentNotNull(nodeName, "nodeName");
|
||||
return NodeName(new ExamineValue(Examineness.Explicit, nodeName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query on the NodeName
|
||||
/// </summary>
|
||||
/// <param name="nodeName">Name of the node.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation NodeName(IExamineValue nodeName)
|
||||
{
|
||||
Enforcer.ArgumentNotNull(nodeName, "nodeName");
|
||||
return this.NodeNameInternal(nodeName, occurance);
|
||||
}
|
||||
|
||||
internal protected IBooleanOperation NodeNameInternal(IExamineValue examineValue, BooleanClause.Occur occurance)
|
||||
{
|
||||
return this.FieldInternal("nodeName", examineValue, occurance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query on the NodeTypeAlias
|
||||
/// </summary>
|
||||
/// <param name="nodeTypeAlias">The node type alias.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation NodeTypeAlias(string nodeTypeAlias)
|
||||
{
|
||||
Enforcer.ArgumentNotNull(nodeTypeAlias, "nodeTypeAlias");
|
||||
return this.NodeTypeAlias(new ExamineValue(Examineness.Explicit, nodeTypeAlias));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query on the NodeTypeAlias
|
||||
/// </summary>
|
||||
/// <param name="nodeTypeAlias">The node type alias.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation NodeTypeAlias(IExamineValue nodeTypeAlias)
|
||||
{
|
||||
Enforcer.ArgumentNotNull(nodeTypeAlias, "nodeTypeAlias");
|
||||
return this.NodeTypeAliasInternal(nodeTypeAlias, occurance);
|
||||
}
|
||||
|
||||
internal protected IBooleanOperation NodeTypeAliasInternal(IExamineValue examineValue, BooleanClause.Occur occurance)
|
||||
{
|
||||
return this.FieldInternal("nodeTypeAlias", examineValue, occurance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query on the Parent ID
|
||||
/// </summary>
|
||||
/// <param name="id">The id of the parent.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation ParentId(int id)
|
||||
{
|
||||
return this.ParentIdInternal(id, occurance);
|
||||
}
|
||||
|
||||
internal protected IBooleanOperation ParentIdInternal(int id, BooleanClause.Occur occurance)
|
||||
{
|
||||
query.Add(this.queryParser.GetFieldQuery("parentID", id.ToString()), occurance);
|
||||
|
||||
return new LuceneBooleanOperation(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query on the specified field
|
||||
/// </summary>
|
||||
/// <param name="fieldName">Name of the field.</param>
|
||||
/// <param name="fieldValue">The field value.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation Field(string fieldName, string fieldValue)
|
||||
{
|
||||
Enforcer.ArgumentNotNull(fieldName, "fieldName");
|
||||
Enforcer.ArgumentNotNull(fieldValue, "fieldValue");
|
||||
return this.FieldInternal(fieldName, new ExamineValue(Examineness.Explicit, fieldValue), occurance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query on the specified field
|
||||
/// </summary>
|
||||
/// <param name="fieldName">Name of the field.</param>
|
||||
/// <param name="fieldValue">The field value.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation Field(string fieldName, IExamineValue fieldValue)
|
||||
{
|
||||
Enforcer.ArgumentNotNull(fieldName, "fieldName");
|
||||
Enforcer.ArgumentNotNull(fieldValue, "fieldValue");
|
||||
return this.FieldInternal(fieldName, fieldValue, occurance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Lucene query object for a field given an IExamineValue
|
||||
/// </summary>
|
||||
/// <param name="fieldName"></param>
|
||||
/// <param name="fieldValue"></param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
internal protected Query GetFieldInternalQuery(string fieldName, IExamineValue fieldValue)
|
||||
{
|
||||
Query queryToAdd;
|
||||
|
||||
switch (fieldValue.Examineness)
|
||||
{
|
||||
case Examineness.Fuzzy:
|
||||
queryToAdd = this.queryParser.GetFuzzyQuery(fieldName, fieldValue.Value, fieldValue.Level);
|
||||
break;
|
||||
case Examineness.SimpleWildcard:
|
||||
case Examineness.ComplexWildcard:
|
||||
queryToAdd = this.queryParser.GetWildcardQuery(fieldName, fieldValue.Value);
|
||||
break;
|
||||
case Examineness.Boosted:
|
||||
queryToAdd = this.queryParser.GetFieldQuery(fieldName, fieldValue.Value);
|
||||
queryToAdd.SetBoost(fieldValue.Level);
|
||||
break;
|
||||
case Examineness.Proximity:
|
||||
//This is how you are supposed to do this based on this doc here:
|
||||
//http://lucene.apache.org/java/2_4_1/api/org/apache/lucene/search/spans/package-summary.html#package_description
|
||||
//but i think that lucene.net has an issue with it's internal parser since it parses to a very strange query
|
||||
//we'll just manually make it instead below
|
||||
|
||||
//var spans = new List<SpanQuery>();
|
||||
//foreach (var s in fieldValue.Value.Split(' '))
|
||||
//{
|
||||
// spans.Add(new SpanTermQuery(new Term(fieldName, s)));
|
||||
//}
|
||||
//queryToAdd = new SpanNearQuery(spans.ToArray(), Convert.ToInt32(fieldValue.Level), true);
|
||||
|
||||
var proxQuery = fieldName + ":\"" + fieldValue.Value + "\"~" + Convert.ToInt32(fieldValue.Level).ToString();
|
||||
queryToAdd = queryParser.Parse(proxQuery);
|
||||
|
||||
break;
|
||||
case Examineness.Explicit:
|
||||
default:
|
||||
queryToAdd = this.queryParser.GetFieldQuery(fieldName, fieldValue.Value);
|
||||
break;
|
||||
}
|
||||
return queryToAdd;
|
||||
}
|
||||
|
||||
internal protected IBooleanOperation FieldInternal(string fieldName, IExamineValue fieldValue, BooleanClause.Occur occurance)
|
||||
{
|
||||
Query queryToAdd = GetFieldInternalQuery(fieldName, fieldValue);
|
||||
|
||||
if (queryToAdd != null)
|
||||
query.Add(queryToAdd, occurance);
|
||||
|
||||
return new LuceneBooleanOperation(this);
|
||||
}
|
||||
|
||||
public IBooleanOperation Range(string fieldName, DateTime start, DateTime end)
|
||||
{
|
||||
return this.Range(fieldName, start, end, true, true);
|
||||
}
|
||||
|
||||
public IBooleanOperation Range(string fieldName, DateTime start, DateTime end, bool includeLower, bool includeUpper)
|
||||
{
|
||||
//since lucene works on string's for all searching we need to flatten the date
|
||||
return this.RangeInternal(fieldName, DateTools.DateToString(start, DateTools.Resolution.MILLISECOND), DateTools.DateToString(end, DateTools.Resolution.MILLISECOND), includeLower, includeUpper, occurance);
|
||||
}
|
||||
|
||||
public IBooleanOperation Range(string fieldName, int start, int end)
|
||||
{
|
||||
Enforcer.ArgumentNotNull(fieldName, "fieldName");
|
||||
return this.Range(fieldName, start, end, true, true);
|
||||
}
|
||||
|
||||
public IBooleanOperation Range(string fieldName, int start, int end, bool includeLower, bool includeUpper)
|
||||
{
|
||||
return this.RangeInternal(fieldName, start, end, includeLower, includeUpper, occurance);
|
||||
}
|
||||
|
||||
protected internal IBooleanOperation RangeInternal(string fieldName, int start, int end, bool includeLower, bool includeUpper, BooleanClause.Occur occurance)
|
||||
{
|
||||
query.Add(NumericRangeQuery.NewIntRange(fieldName, start, end, includeLower, includeUpper), occurance);
|
||||
return new LuceneBooleanOperation(this);
|
||||
}
|
||||
|
||||
public IBooleanOperation Range(string fieldName, string start, string end)
|
||||
{
|
||||
Enforcer.ArgumentNotNull(fieldName, "fieldName");
|
||||
Enforcer.ArgumentNotNull(start, "start");
|
||||
Enforcer.ArgumentNotNull(end, "end");
|
||||
return this.Range(fieldName, start, end, true, true);
|
||||
}
|
||||
|
||||
public IBooleanOperation Range(string fieldName, string start, string end, bool includeLower, bool includeUpper)
|
||||
{
|
||||
Enforcer.ArgumentNotNull(fieldName, "fieldName");
|
||||
Enforcer.ArgumentNotNull(start, "start");
|
||||
Enforcer.ArgumentNotNull(end, "end");
|
||||
return this.RangeInternal(fieldName, start, end, includeLower, includeUpper, occurance);
|
||||
}
|
||||
|
||||
protected internal IBooleanOperation RangeInternal(string fieldName, string start, string end, bool includeLower, bool includeUpper, BooleanClause.Occur occurance)
|
||||
{
|
||||
query.Add(new TermRangeQuery(fieldName, start, end, includeLower, includeUpper), occurance);
|
||||
|
||||
return new LuceneBooleanOperation(this);
|
||||
}
|
||||
|
||||
public IBooleanOperation GroupedAnd(IEnumerable<string> fields, params string[] query)
|
||||
{
|
||||
Enforcer.ArgumentNotNull(fields, "fields");
|
||||
Enforcer.ArgumentNotNull(query, "query");
|
||||
|
||||
var fieldVals = new List<IExamineValue>();
|
||||
foreach (var f in query)
|
||||
{
|
||||
fieldVals.Add(new ExamineValue(Examineness.Explicit, f));
|
||||
}
|
||||
return this.GroupedAnd(fields.ToArray(), fieldVals.ToArray());
|
||||
}
|
||||
|
||||
public IBooleanOperation GroupedAnd(IEnumerable<string> fields, IExamineValue[] fieldVals)
|
||||
{
|
||||
Enforcer.ArgumentNotNull(fields, "fields");
|
||||
Enforcer.ArgumentNotNull(query, "fieldVals");
|
||||
|
||||
return this.GroupedAndInternal(fields.ToArray(), fieldVals.ToArray(), occurance);
|
||||
}
|
||||
|
||||
protected internal IBooleanOperation GroupedAndInternal(string[] fields, IExamineValue[] fieldVals, BooleanClause.Occur occurance)
|
||||
{
|
||||
|
||||
//if there's only 1 query text we want to build up a string like this:
|
||||
//(+field1:query +field2:query +field3:query)
|
||||
//but Lucene will bork if you provide an array of length 1 (which is != to the field length)
|
||||
|
||||
query.Add(GetMultiFieldQuery(fields, fieldVals, BooleanClause.Occur.MUST), occurance);
|
||||
|
||||
return new LuceneBooleanOperation(this);
|
||||
}
|
||||
|
||||
public IBooleanOperation GroupedOr(IEnumerable<string> fields, params string[] query)
|
||||
{
|
||||
Enforcer.ArgumentNotNull(fields, "fields");
|
||||
Enforcer.ArgumentNotNull(query, "query");
|
||||
|
||||
var fieldVals = new List<IExamineValue>();
|
||||
foreach (var f in query)
|
||||
{
|
||||
fieldVals.Add(new ExamineValue(Examineness.Explicit, f));
|
||||
}
|
||||
|
||||
return this.GroupedOr(fields.ToArray(), fieldVals.ToArray());
|
||||
}
|
||||
|
||||
public IBooleanOperation GroupedOr(IEnumerable<string> fields, params IExamineValue[] fieldVals)
|
||||
{
|
||||
Enforcer.ArgumentNotNull(fields, "fields");
|
||||
Enforcer.ArgumentNotNull(query, "query");
|
||||
|
||||
return this.GroupedOrInternal(fields.ToArray(), fieldVals, occurance);
|
||||
}
|
||||
|
||||
protected internal IBooleanOperation GroupedOrInternal(string[] fields, IExamineValue[] fieldVals, BooleanClause.Occur occurance)
|
||||
{
|
||||
//if there's only 1 query text we want to build up a string like this:
|
||||
//(field1:query field2:query field3:query)
|
||||
//but Lucene will bork if you provide an array of length 1 (which is != to the field length)
|
||||
|
||||
query.Add(GetMultiFieldQuery(fields, fieldVals, BooleanClause.Occur.SHOULD), occurance);
|
||||
|
||||
return new LuceneBooleanOperation(this);
|
||||
}
|
||||
|
||||
public IBooleanOperation GroupedNot(IEnumerable<string> fields, params string[] query)
|
||||
{
|
||||
Enforcer.ArgumentNotNull(fields, "fields");
|
||||
Enforcer.ArgumentNotNull(query, "query");
|
||||
|
||||
var fieldVals = new List<IExamineValue>();
|
||||
foreach (var f in query)
|
||||
{
|
||||
fieldVals.Add(new ExamineValue(Examineness.Explicit, f));
|
||||
}
|
||||
|
||||
return this.GroupedNot(fields.ToArray(), fieldVals.ToArray());
|
||||
}
|
||||
|
||||
public IBooleanOperation GroupedNot(IEnumerable<string> fields, params IExamineValue[] query)
|
||||
{
|
||||
Enforcer.ArgumentNotNull(fields, "fields");
|
||||
Enforcer.ArgumentNotNull(query, "query");
|
||||
|
||||
return this.GroupedNotInternal(fields.ToArray(), query, occurance);
|
||||
}
|
||||
|
||||
protected internal IBooleanOperation GroupedNotInternal(string[] fields, IExamineValue[] fieldVals, BooleanClause.Occur occurance)
|
||||
{
|
||||
//if there's only 1 query text we want to build up a string like this:
|
||||
//(!field1:query !field2:query !field3:query)
|
||||
//but Lucene will bork if you provide an array of length 1 (which is != to the field length)
|
||||
|
||||
query.Add(GetMultiFieldQuery(fields, fieldVals, BooleanClause.Occur.MUST_NOT), occurance);
|
||||
|
||||
return new LuceneBooleanOperation(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates our own style 'multi field query' used internal for the grouped operations
|
||||
/// </summary>
|
||||
/// <param name="fields"></param>
|
||||
/// <param name="fieldVals"></param>
|
||||
/// <param name="occurance"></param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
protected internal BooleanQuery GetMultiFieldQuery(string[] fields, IExamineValue[] fieldVals, BooleanClause.Occur occurance)
|
||||
{
|
||||
//if there's only 1 query text we want to build up a string like this:
|
||||
//(!field1:query !field2:query !field3:query)
|
||||
//but Lucene will bork if you provide an array of length 1 (which is != to the field length)
|
||||
|
||||
var queryVals = new IExamineValue[fields.Length];
|
||||
if (fieldVals.Length == 1)
|
||||
{
|
||||
for (int i = 0; i < queryVals.Length; i++)
|
||||
queryVals[i] = fieldVals[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
queryVals = fieldVals;
|
||||
}
|
||||
|
||||
var qry = new BooleanQuery();
|
||||
for (int i = 0; i < fields.Length; i++)
|
||||
{
|
||||
qry.Add(this.GetFieldInternalQuery(fields[i], queryVals[i]), occurance);
|
||||
}
|
||||
|
||||
return qry;
|
||||
}
|
||||
|
||||
public IBooleanOperation GroupedFlexible(IEnumerable<string> fields, IEnumerable<BooleanOperation> operations, params string[] query)
|
||||
{
|
||||
Enforcer.ArgumentNotNull(fields, "fields");
|
||||
Enforcer.ArgumentNotNull(query, "query");
|
||||
Enforcer.ArgumentNotNull(operations, "operations");
|
||||
|
||||
var fieldVals = new List<IExamineValue>();
|
||||
foreach (var f in query)
|
||||
{
|
||||
fieldVals.Add(new ExamineValue(Examineness.Explicit, f));
|
||||
}
|
||||
|
||||
return this.GroupedFlexible(fields.ToArray(), operations.ToArray(), fieldVals.ToArray());
|
||||
}
|
||||
|
||||
public IBooleanOperation GroupedFlexible(IEnumerable<string> fields, IEnumerable<BooleanOperation> operations, params IExamineValue[] fieldVals)
|
||||
{
|
||||
Enforcer.ArgumentNotNull(fields, "fields");
|
||||
Enforcer.ArgumentNotNull(query, "query");
|
||||
Enforcer.ArgumentNotNull(operations, "operations");
|
||||
|
||||
return this.GroupedFlexibleInternal(fields.ToArray(), operations.ToArray(), fieldVals, occurance);
|
||||
}
|
||||
|
||||
protected internal IBooleanOperation GroupedFlexibleInternal(string[] fields, BooleanOperation[] operations, IExamineValue[] fieldVals, BooleanClause.Occur occurance)
|
||||
{
|
||||
//if there's only 1 query text we want to build up a string like this:
|
||||
//(field1:query field2:query field3:query)
|
||||
//but Lucene will bork if you provide an array of length 1 (which is != to the field length)
|
||||
|
||||
var flags = new BooleanClause.Occur[operations.Count()];
|
||||
for (int i = 0; i < flags.Length; i++)
|
||||
flags[i] = operations.ElementAt(i).ToLuceneOccurance();
|
||||
|
||||
var queryVals = new IExamineValue[fields.Length];
|
||||
if (fieldVals.Length == 1)
|
||||
{
|
||||
for (int i = 0; i < queryVals.Length; i++)
|
||||
queryVals[i] = fieldVals[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
queryVals = fieldVals;
|
||||
}
|
||||
|
||||
var qry = new BooleanQuery();
|
||||
for (int i = 0; i < fields.Length; i++)
|
||||
{
|
||||
qry.Add(this.GetFieldInternalQuery(fields[i], queryVals[i]), flags[i]);
|
||||
}
|
||||
|
||||
this.query.Add(qry, occurance);
|
||||
|
||||
return new LuceneBooleanOperation(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Passes a raw search query to the provider to handle
|
||||
/// </summary>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public ISearchCriteria RawQuery(string query)
|
||||
{
|
||||
this.query.Add(this.queryParser.Parse(query), this.occurance);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Orders the results by the specified fields
|
||||
/// </summary>
|
||||
/// <param name="fieldNames">The field names.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation OrderBy(params string[] fieldNames)
|
||||
{
|
||||
Enforcer.ArgumentNotNull(fieldNames, "fieldNames");
|
||||
|
||||
return this.OrderByInternal(false, fieldNames);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Orders the results by the specified fields in a descending order
|
||||
/// </summary>
|
||||
/// <param name="fieldNames">The field names.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
public IBooleanOperation OrderByDescending(params string[] fieldNames)
|
||||
{
|
||||
Enforcer.ArgumentNotNull(fieldNames, "fieldNames");
|
||||
|
||||
return this.OrderByInternal(true, fieldNames);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal operation for adding the ordered results
|
||||
/// </summary>
|
||||
/// <param name="descending">if set to <c>true</c> [descending].</param>
|
||||
/// <param name="fieldNames">The field names.</param>
|
||||
/// <returns>A new <see cref="Examine.SearchCriteria.IBooleanOperation"/> with the clause appended</returns>
|
||||
protected internal IBooleanOperation OrderByInternal(bool descending, params string[] fieldNames)
|
||||
{
|
||||
foreach (var fieldName in fieldNames)
|
||||
{
|
||||
this.sortFields.Add(new SortField(LuceneExamineIndexer.SortedFieldNamePrefix + fieldName, SortField.STRING, descending));
|
||||
}
|
||||
|
||||
return new LuceneBooleanOperation(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
164
src/UmbracoExamine/SearchCriteria/LuceneSearchExtensions.cs
Normal file
164
src/UmbracoExamine/SearchCriteria/LuceneSearchExtensions.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
using Examine.SearchCriteria;
|
||||
using Lucene.Net.Search;
|
||||
using Lucene.Net.QueryParsers;
|
||||
using System;
|
||||
|
||||
namespace UmbracoExamine.SearchCriteria
|
||||
{
|
||||
public static class LuceneSearchExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a single character wildcard to the string for Lucene wildcard matching
|
||||
/// </summary>
|
||||
/// <param name="s">The string to wildcard.</param>
|
||||
/// <returns>An IExamineValue for the required operation</returns>
|
||||
/// <exception cref="System.ArgumentException">Thrown when the string is null or empty</exception>
|
||||
public static IExamineValue SingleCharacterWildcard(this string s)
|
||||
{
|
||||
if (System.String.IsNullOrEmpty(s))
|
||||
throw new ArgumentException("Supplied string is null or empty.", "s");
|
||||
|
||||
return new ExamineValue(Examineness.SimpleWildcard, s + "?");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a multi-character wildcard to a string for Lucene wildcard matching
|
||||
/// </summary>
|
||||
/// <param name="s">The string to wildcard.</param>
|
||||
/// <returns>An IExamineValue for the required operation</returns>
|
||||
/// <exception cref="System.ArgumentException">Thrown when the string is null or empty</exception>
|
||||
public static IExamineValue MultipleCharacterWildcard(this string s)
|
||||
{
|
||||
if (String.IsNullOrEmpty(s))
|
||||
throw new ArgumentException("Supplied string is null or empty.", "s");
|
||||
return new ExamineValue(Examineness.ComplexWildcard, s + "*");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the string for fuzzy matching in Lucene using the default fuzziness level
|
||||
/// </summary>
|
||||
/// <param name="s">The string to configure fuzzy matching on.</param>
|
||||
/// <returns>An IExamineValue for the required operation</returns>
|
||||
/// <exception cref="System.ArgumentException">Thrown when the string is null or empty</exception>
|
||||
public static IExamineValue Fuzzy(this string s)
|
||||
{
|
||||
return Fuzzy(s, 0.5f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the string for fuzzy matching in Lucene using the supplied fuzziness level
|
||||
/// </summary>
|
||||
/// <param name="s">The string to configure fuzzy matching on.</param>
|
||||
/// <param name="fuzzieness">The fuzzieness level.</param>
|
||||
/// <returns>
|
||||
/// An IExamineValue for the required operation
|
||||
/// </returns>
|
||||
/// <exception cref="System.ArgumentException">Thrown when the string is null or empty</exception>
|
||||
public static IExamineValue Fuzzy(this string s, float fuzzieness)
|
||||
{
|
||||
if (String.IsNullOrEmpty(s))
|
||||
throw new ArgumentException("Supplied string is null or empty.", "s");
|
||||
return new ExamineValue(Examineness.Fuzzy, s, fuzzieness);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the string for boosting in Lucene
|
||||
/// </summary>
|
||||
/// <param name="s">The string to wildcard.</param>
|
||||
/// <param name="boost">The boost level.</param>
|
||||
/// <returns>
|
||||
/// An IExamineValue for the required operation
|
||||
/// </returns>
|
||||
/// <exception cref="System.ArgumentException">Thrown when the string is null or empty</exception>
|
||||
public static IExamineValue Boost(this string s, float boost)
|
||||
{
|
||||
if (String.IsNullOrEmpty(s))
|
||||
throw new ArgumentException("Supplied string is null or empty.", "s");
|
||||
return new ExamineValue(Examineness.Boosted, s + "^", boost);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the string for proximity matching
|
||||
/// </summary>
|
||||
/// <param name="s">The string to wildcard.</param>
|
||||
/// <param name="proximity">The proximity level.</param>
|
||||
/// <returns>
|
||||
/// An IExamineValue for the required operation
|
||||
/// </returns>
|
||||
/// <exception cref="System.ArgumentException">Thrown when the string is null or empty</exception>
|
||||
public static IExamineValue Proximity(this string s, int proximity)
|
||||
{
|
||||
if (String.IsNullOrEmpty(s))
|
||||
throw new ArgumentException("Supplied string is null or empty.", "s");
|
||||
return new ExamineValue(Examineness.Proximity, s + "~", Convert.ToSingle(proximity));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Escapes the string within Lucene
|
||||
/// </summary>
|
||||
/// <param name="s">The string to wildcard.</param>
|
||||
/// <returns>An IExamineValue for the required operation</returns>
|
||||
/// <exception cref="System.ArgumentException">Thrown when the string is null or empty</exception>
|
||||
public static IExamineValue Escape(this string s)
|
||||
{
|
||||
if (String.IsNullOrEmpty(s))
|
||||
throw new ArgumentException("Supplied string is null or empty.", "s");
|
||||
return new ExamineValue(Examineness.Escaped, QueryParser.Escape(s));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets up an <see cref="IExamineValue"/> for an additional Examiness
|
||||
/// </summary>
|
||||
/// <param name="examineValue">The IExamineValue to continue working with.</param>
|
||||
/// <param name="s">The string to postfix.</param>
|
||||
/// <returns>Combined strings</returns>
|
||||
public static string Then(this IExamineValue examineValue, string s)
|
||||
{
|
||||
if (examineValue == null)
|
||||
throw new ArgumentNullException("examineValue", "examineValue is null.");
|
||||
if (String.IsNullOrEmpty(s))
|
||||
throw new ArgumentException("Supplied string is null or empty.", "s");
|
||||
return examineValue.Value + s;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an Examine boolean operation to a Lucene representation
|
||||
/// </summary>
|
||||
/// <param name="o">The operation.</param>
|
||||
/// <returns>The translated Boolean operation</returns>
|
||||
public static BooleanClause.Occur ToLuceneOccurance(this BooleanOperation o)
|
||||
{
|
||||
switch (o)
|
||||
{
|
||||
case BooleanOperation.And:
|
||||
return BooleanClause.Occur.MUST;
|
||||
case BooleanOperation.Not:
|
||||
return BooleanClause.Occur.MUST_NOT;
|
||||
case BooleanOperation.Or:
|
||||
default:
|
||||
return BooleanClause.Occur.SHOULD;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Lucene boolean occurrence to an Examine representation
|
||||
/// </summary>
|
||||
/// <param name="o">The occurrence to translate.</param>
|
||||
/// <returns>The translated boolean occurrence</returns>
|
||||
public static BooleanOperation ToBooleanOperation(this BooleanClause.Occur o)
|
||||
{
|
||||
if (o == BooleanClause.Occur.MUST)
|
||||
{
|
||||
return BooleanOperation.And;
|
||||
}
|
||||
else if (o == BooleanClause.Occur.MUST_NOT)
|
||||
{
|
||||
return BooleanOperation.Not;
|
||||
}
|
||||
else
|
||||
{
|
||||
return BooleanOperation.Or;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
375
src/UmbracoExamine/UmbracoContentIndexer.cs
Normal file
375
src/UmbracoExamine/UmbracoContentIndexer.cs
Normal file
@@ -0,0 +1,375 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using System.Xml.Linq;
|
||||
using Examine;
|
||||
using Examine.Config;
|
||||
using Examine.Providers;
|
||||
using umbraco.cms.businesslogic;
|
||||
using UmbracoExamine.DataServices;
|
||||
using Examine.LuceneEngine;
|
||||
using Examine.LuceneEngine.Config;
|
||||
using UmbracoExamine.Config;
|
||||
using Examine.LuceneEngine.Providers;
|
||||
using Lucene.Net.Analysis;
|
||||
using umbraco.BasePages;
|
||||
|
||||
|
||||
namespace UmbracoExamine
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UmbracoContentIndexer : BaseUmbracoIndexer
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
public UmbracoContentIndexer()
|
||||
: base() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor to allow for creating an indexer at runtime
|
||||
/// </summary>
|
||||
/// <param name="indexerData"></param>
|
||||
/// <param name="indexPath"></param>
|
||||
/// <param name="dataService"></param>
|
||||
/// <param name="analyzer"></param>
|
||||
[SecuritySafeCritical]
|
||||
public UmbracoContentIndexer(IIndexCriteria indexerData, DirectoryInfo indexPath, IDataService dataService, Analyzer analyzer, bool async)
|
||||
: base(indexerData, indexPath, dataService, analyzer, async) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor to allow for creating an indexer at runtime
|
||||
/// </summary>
|
||||
/// <param name="indexerData"></param>
|
||||
/// <param name="luceneDirectory"></param>
|
||||
/// <param name="dataService"></param>
|
||||
/// <param name="analyzer"></param>
|
||||
/// <param name="async"></param>
|
||||
[SecuritySafeCritical]
|
||||
public UmbracoContentIndexer(IIndexCriteria indexerData, Lucene.Net.Store.Directory luceneDirectory, IDataService dataService, Analyzer analyzer, bool async)
|
||||
: base(indexerData, luceneDirectory, dataService, analyzer, async) { }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constants & Fields
|
||||
|
||||
/// <summary>
|
||||
/// Used to store the path of a content object
|
||||
/// </summary>
|
||||
public const string IndexPathFieldName = "__Path";
|
||||
public const string NodeTypeAliasFieldName = "__NodeTypeAlias";
|
||||
|
||||
/// <summary>
|
||||
/// A type that defines the type of index for each Umbraco field (non user defined fields)
|
||||
/// Alot of standard umbraco fields shouldn't be tokenized or even indexed, just stored into lucene
|
||||
/// for retreival after searching.
|
||||
/// </summary>
|
||||
internal static readonly Dictionary<string, FieldIndexTypes> IndexFieldPolicies
|
||||
= new Dictionary<string, FieldIndexTypes>()
|
||||
{
|
||||
{ "id", FieldIndexTypes.NOT_ANALYZED},
|
||||
{ "version", FieldIndexTypes.NOT_ANALYZED},
|
||||
{ "parentID", FieldIndexTypes.NOT_ANALYZED},
|
||||
{ "level", FieldIndexTypes.NOT_ANALYZED},
|
||||
{ "writerID", FieldIndexTypes.NOT_ANALYZED},
|
||||
{ "creatorID", FieldIndexTypes.NOT_ANALYZED},
|
||||
{ "nodeType", FieldIndexTypes.NOT_ANALYZED},
|
||||
{ "template", FieldIndexTypes.NOT_ANALYZED},
|
||||
{ "sortOrder", FieldIndexTypes.NOT_ANALYZED},
|
||||
{ "createDate", FieldIndexTypes.NOT_ANALYZED},
|
||||
{ "updateDate", FieldIndexTypes.NOT_ANALYZED},
|
||||
{ "nodeName", FieldIndexTypes.ANALYZED},
|
||||
{ "urlName", FieldIndexTypes.NOT_ANALYZED},
|
||||
{ "writerName", FieldIndexTypes.ANALYZED},
|
||||
{ "creatorName", FieldIndexTypes.ANALYZED},
|
||||
{ "nodeTypeAlias", FieldIndexTypes.ANALYZED},
|
||||
{ "path", FieldIndexTypes.NOT_ANALYZED}
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
#region Initialize
|
||||
|
||||
/// <summary>
|
||||
/// Set up all properties for the indexer based on configuration information specified. This will ensure that
|
||||
/// all of the folders required by the indexer are created and exist. This will also create an instruction
|
||||
/// file declaring the computer name that is part taking in the indexing. This file will then be used to
|
||||
/// determine the master indexer machine in a load balanced environment (if one exists).
|
||||
/// </summary>
|
||||
/// <param name="name">The friendly name of the provider.</param>
|
||||
/// <param name="config">A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider.</param>
|
||||
/// <exception cref="T:System.ArgumentNullException">
|
||||
/// The name of the provider is null.
|
||||
/// </exception>
|
||||
/// <exception cref="T:System.ArgumentException">
|
||||
/// The name of the provider has a length of zero.
|
||||
/// </exception>
|
||||
/// <exception cref="T:System.InvalidOperationException">
|
||||
/// An attempt is made to call <see cref="M:System.Configuration.Provider.ProviderBase.Initialize(System.String,System.Collections.Specialized.NameValueCollection)"/> on a provider after the provider has already been initialized.
|
||||
/// </exception>
|
||||
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
|
||||
{
|
||||
|
||||
//check if there's a flag specifying to support unpublished content,
|
||||
//if not, set to false;
|
||||
bool supportUnpublished;
|
||||
if (config["supportUnpublished"] != null && bool.TryParse(config["supportUnpublished"], out supportUnpublished))
|
||||
SupportUnpublishedContent = supportUnpublished;
|
||||
else
|
||||
SupportUnpublishedContent = false;
|
||||
|
||||
|
||||
//check if there's a flag specifying to support protected content,
|
||||
//if not, set to false;
|
||||
bool supportProtected;
|
||||
if (config["supportProtected"] != null && bool.TryParse(config["supportProtected"], out supportProtected))
|
||||
SupportProtectedContent = supportProtected;
|
||||
else
|
||||
SupportProtectedContent = false;
|
||||
|
||||
|
||||
base.Initialize(name, config);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// By default this is false, if set to true then the indexer will include indexing content that is flagged as publicly protected.
|
||||
/// This property is ignored if SupportUnpublishedContent is set to true.
|
||||
/// </summary>
|
||||
public bool SupportProtectedContent { get; protected internal set; }
|
||||
|
||||
protected override IEnumerable<string> SupportedTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
return new string[] { IndexTypes.Content, IndexTypes.Media };
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event handlers
|
||||
|
||||
protected override void OnIndexingError(IndexingErrorEventArgs e)
|
||||
{
|
||||
DataService.LogService.AddErrorLog(e.NodeId, string.Format("{0},{1}, IndexSet: {2}", e.Message, e.InnerException != null ? e.InnerException.Message : "", this.IndexSetName));
|
||||
base.OnIndexingError(e);
|
||||
}
|
||||
|
||||
//protected override void OnDocumentWriting(DocumentWritingEventArgs docArgs)
|
||||
//{
|
||||
// DataService.LogService.AddVerboseLog(docArgs.NodeId, string.Format("({0}) DocumentWriting event for node ({1})", this.Name, LuceneIndexFolder.FullName));
|
||||
// base.OnDocumentWriting(docArgs);
|
||||
//}
|
||||
|
||||
protected override void OnNodeIndexed(IndexedNodeEventArgs e)
|
||||
{
|
||||
DataService.LogService.AddVerboseLog(e.NodeId, string.Format("Index created for node"));
|
||||
base.OnNodeIndexed(e);
|
||||
}
|
||||
|
||||
protected override void OnIndexDeleted(DeleteIndexEventArgs e)
|
||||
{
|
||||
DataService.LogService.AddVerboseLog(-1, string.Format("Index deleted for term: {0} with value {1}", e.DeletedTerm.Key, e.DeletedTerm.Value));
|
||||
base.OnIndexDeleted(e);
|
||||
}
|
||||
|
||||
protected override void OnIndexOptimizing(EventArgs e)
|
||||
{
|
||||
DataService.LogService.AddInfoLog(-1, string.Format("Index is being optimized"));
|
||||
base.OnIndexOptimizing(e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public methods
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Overridden for logging
|
||||
/// </summary>
|
||||
/// <param name="node"></param>
|
||||
/// <param name="type"></param>
|
||||
public override void ReIndexNode(XElement node, string type)
|
||||
{
|
||||
if (!SupportedTypes.Contains(type))
|
||||
return;
|
||||
|
||||
DataService.LogService.AddVerboseLog((int)node.Attribute("id"), string.Format("ReIndexNode with type: {0}", type));
|
||||
base.ReIndexNode(node, type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a node from the index.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When a content node is deleted, we also need to delete it's children from the index so we need to perform a
|
||||
/// custom Lucene search to find all decendents and create Delete item queues for them too.
|
||||
/// </remarks>
|
||||
/// <param name="nodeId">ID of the node to delete</param>
|
||||
public override void DeleteFromIndex(string nodeId)
|
||||
{
|
||||
//find all descendants based on path
|
||||
var descendantPath = string.Format(@"\-1\,*{0}\,*", nodeId);
|
||||
var rawQuery = string.Format("{0}:{1}", IndexPathFieldName, descendantPath);
|
||||
var c = InternalSearcher.CreateSearchCriteria();
|
||||
var filtered = c.RawQuery(rawQuery);
|
||||
var results = InternalSearcher.Search(filtered);
|
||||
|
||||
DataService.LogService.AddVerboseLog(int.Parse(nodeId), string.Format("DeleteFromIndex with query: {0} (found {1} results)", rawQuery, results.Count()));
|
||||
|
||||
//need to create a delete queue item for each one found
|
||||
foreach (var r in results)
|
||||
{
|
||||
EnqueueIndexOperation(new IndexOperation()
|
||||
{
|
||||
Operation = IndexOperationType.Delete,
|
||||
Item = new IndexItem(null, "", r.Id.ToString())
|
||||
});
|
||||
//SaveDeleteIndexQueueItem(new KeyValuePair<string, string>(IndexNodeIdFieldName, r.Id.ToString()));
|
||||
}
|
||||
|
||||
base.DeleteFromIndex(nodeId);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Protected
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Overridden for logging.
|
||||
/// </summary>
|
||||
/// <param name="node"></param>
|
||||
/// <param name="type"></param>
|
||||
protected override void AddSingleNodeToIndex(XElement node, string type)
|
||||
{
|
||||
DataService.LogService.AddVerboseLog((int)node.Attribute("id"), string.Format("AddSingleNodeToIndex with type: {0}", type));
|
||||
base.AddSingleNodeToIndex(node, type);
|
||||
}
|
||||
|
||||
public override void RebuildIndex()
|
||||
{
|
||||
DataService.LogService.AddVerboseLog(-1, "Rebuilding index");
|
||||
base.RebuildIndex();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override this method to strip all html from all user fields before raising the event, then after the event
|
||||
/// ensure our special Path field is added to the collection
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
protected override void OnGatheringNodeData(IndexingNodeDataEventArgs e)
|
||||
{
|
||||
//strip html of all users fields
|
||||
// Get all user data that we want to index and store into a dictionary
|
||||
foreach (var field in IndexerData.UserFields)
|
||||
{
|
||||
if (e.Fields.ContainsKey(field.Name))
|
||||
{
|
||||
e.Fields[field.Name] = DataService.ContentService.StripHtml(e.Fields[field.Name]);
|
||||
}
|
||||
}
|
||||
|
||||
base.OnGatheringNodeData(e);
|
||||
|
||||
//ensure the special path and node type alis fields is added to the dictionary to be saved to file
|
||||
var path = e.Node.Attribute("path").Value;
|
||||
if (!e.Fields.ContainsKey(IndexPathFieldName))
|
||||
e.Fields.Add(IndexPathFieldName, path);
|
||||
|
||||
//this needs to support both schemas so get the nodeTypeAlias if it exists, otherwise the name
|
||||
var nodeTypeAlias = e.Node.Attribute("nodeTypeAlias") == null ? e.Node.Name.LocalName : e.Node.Attribute("nodeTypeAlias").Value;
|
||||
if (!e.Fields.ContainsKey(NodeTypeAliasFieldName))
|
||||
e.Fields.Add(NodeTypeAliasFieldName, nodeTypeAlias);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a duplicate field is detected in the dictionary that is getting indexed.
|
||||
/// </summary>
|
||||
/// <param name="nodeId"></param>
|
||||
/// <param name="indexSetName"></param>
|
||||
/// <param name="fieldName"></param>
|
||||
protected override void OnDuplicateFieldWarning(int nodeId, string indexSetName, string fieldName)
|
||||
{
|
||||
base.OnDuplicateFieldWarning(nodeId, indexSetName, fieldName);
|
||||
|
||||
this.DataService.LogService.AddInfoLog(nodeId, "Field \"" + fieldName + "\" is listed multiple times in the index set \"" + indexSetName + "\". Please ensure all names are unique");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overridden to add the path property to the special fields to index
|
||||
/// </summary>
|
||||
/// <param name="allValuesForIndexing"></param>
|
||||
/// <returns></returns>
|
||||
protected override Dictionary<string, string> GetSpecialFieldsToIndex(Dictionary<string, string> allValuesForIndexing)
|
||||
{
|
||||
var fields = base.GetSpecialFieldsToIndex(allValuesForIndexing);
|
||||
|
||||
//adds the special path property to the index
|
||||
fields.Add(IndexPathFieldName, allValuesForIndexing[IndexPathFieldName]);
|
||||
|
||||
//adds the special node type alias property to the index
|
||||
fields.Add(NodeTypeAliasFieldName, allValuesForIndexing[NodeTypeAliasFieldName]);
|
||||
|
||||
return fields;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an IIndexCriteria object based on the indexSet passed in and our DataService
|
||||
/// </summary>
|
||||
/// <param name="indexSet"></param>
|
||||
/// <returns></returns>
|
||||
protected override IIndexCriteria GetIndexerData(IndexSet indexSet)
|
||||
{
|
||||
return indexSet.ToIndexCriteria(DataService);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// return the index policy for the field name passed in, if not found, return normal
|
||||
/// </summary>
|
||||
/// <param name="fieldName"></param>
|
||||
/// <returns></returns>
|
||||
protected override FieldIndexTypes GetPolicy(string fieldName)
|
||||
{
|
||||
var def = IndexFieldPolicies.Where(x => x.Key == fieldName);
|
||||
return (def.Count() == 0 ? FieldIndexTypes.ANALYZED : def.Single().Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure that the content of this node is available for indexing (i.e. don't allow protected
|
||||
/// content to be indexed when this is disabled).
|
||||
/// <returns></returns>
|
||||
/// </summary>
|
||||
protected override bool ValidateDocument(XElement node)
|
||||
{
|
||||
var nodeId = int.Parse(node.Attribute("id").Value);
|
||||
// Test for access if we're only indexing published content
|
||||
// return nothing if we're not supporting protected content and it is protected, and we're not supporting unpublished content
|
||||
if (!SupportUnpublishedContent
|
||||
&& (!SupportProtectedContent
|
||||
&& DataService.ContentService.IsProtected(nodeId, node.Attribute("path").Value)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.ValidateDocument(node);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
223
src/UmbracoExamine/UmbracoEventManager.cs
Normal file
223
src/UmbracoExamine/UmbracoEventManager.cs
Normal file
@@ -0,0 +1,223 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using Examine.Config;
|
||||
using umbraco;
|
||||
using umbraco.BusinessLogic;
|
||||
using umbraco.cms.businesslogic;
|
||||
using umbraco.cms.businesslogic.media;
|
||||
using umbraco.cms.businesslogic.web;
|
||||
using Examine;
|
||||
using Examine.Providers;
|
||||
using umbraco.cms.businesslogic.member;
|
||||
using Examine.LuceneEngine;
|
||||
using Examine.LuceneEngine.Providers;
|
||||
using Lucene.Net.Index;
|
||||
using Lucene.Net.Store;
|
||||
|
||||
namespace UmbracoExamine
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="umbraco.BusinessLogic.ApplicationBase"/> instance for wiring up Examine to the Umbraco events system
|
||||
/// </summary>
|
||||
public class UmbracoEventManager : ApplicationBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the class
|
||||
/// </summary>
|
||||
[SecuritySafeCritical]
|
||||
public UmbracoEventManager()
|
||||
{
|
||||
var registeredProviders = ExamineManager.Instance.IndexProviderCollection.OfType<BaseUmbracoIndexer>()
|
||||
.Where(x => x.EnableDefaultEventHandler)
|
||||
.Count();
|
||||
|
||||
Log.Add(LogTypes.Custom, -1, "[UmbracoExamine] Adding examine event handlers for index providers: " + registeredProviders.ToString());
|
||||
|
||||
//don't bind event handlers if we're not suppose to listen
|
||||
if (registeredProviders == 0)
|
||||
return;
|
||||
|
||||
Media.AfterSave += Media_AfterSave;
|
||||
Media.AfterDelete += Media_AfterDelete;
|
||||
CMSNode.AfterMove += Media_AfterMove;
|
||||
|
||||
//These should only fire for providers that DONT have SupportUnpublishedContent set to true
|
||||
content.AfterUpdateDocumentCache += content_AfterUpdateDocumentCache;
|
||||
content.AfterClearDocumentCache += content_AfterClearDocumentCache;
|
||||
|
||||
//These should only fire for providers that have SupportUnpublishedContent set to true
|
||||
Document.AfterSave += Document_AfterSave;
|
||||
Document.AfterDelete += Document_AfterDelete;
|
||||
|
||||
Member.AfterSave += Member_AfterSave;
|
||||
Member.AfterDelete += Member_AfterDelete;
|
||||
}
|
||||
|
||||
//This does work, however we need to lock down the httphandler and thats an issue... so i've removed this
|
||||
//if people don't want to rebuild on app startup then they can disable it and reindex manually.
|
||||
|
||||
[SecuritySafeCritical]
|
||||
private void Member_AfterSave(Member sender, SaveEventArgs e)
|
||||
{
|
||||
//ensure that only the providers are flagged to listen execute
|
||||
var xml = sender.ToXml(new System.Xml.XmlDocument(), false).ToXElement();
|
||||
var providers = ExamineManager.Instance.IndexProviderCollection.OfType<BaseUmbracoIndexer>()
|
||||
.Where(x => x.EnableDefaultEventHandler);
|
||||
ExamineManager.Instance.ReIndexNode(xml, IndexTypes.Member, providers);
|
||||
}
|
||||
|
||||
[SecuritySafeCritical]
|
||||
private void Member_AfterDelete(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<BaseUmbracoIndexer>()
|
||||
.Where(x => x.EnableDefaultEventHandler));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only index using providers that SupportUnpublishedContent
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
[SecuritySafeCritical]
|
||||
private void Document_AfterSave(Document sender, SaveEventArgs e)
|
||||
{
|
||||
//ensure that only the providers that have unpublishing support enabled
|
||||
//that are also flagged to listen
|
||||
|
||||
//there's a bug in 4.0.x that fires the Document saving event handler for media when media is moved,
|
||||
//therefore, we need to try to figure out if this is media or content. Currently one way to do this
|
||||
//is by checking the creator ID property as it will be null if it is media. We then need to try to
|
||||
//create the media object, see if it exists, and pass it to the media save event handler... yeah i know,
|
||||
//pretty horrible but has to be done.
|
||||
|
||||
try
|
||||
{
|
||||
var creator = sender.Creator;
|
||||
if (creator != null)
|
||||
{
|
||||
//it's def a Document
|
||||
ExamineManager.Instance.ReIndexNode(sender.ToXDocument(false).Root, IndexTypes.Content,
|
||||
ExamineManager.Instance.IndexProviderCollection.OfType<BaseUmbracoIndexer>()
|
||||
.Where(x => x.SupportUnpublishedContent
|
||||
&& x.EnableDefaultEventHandler));
|
||||
|
||||
return; //exit, we've indexed the content
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//if we get this exception, it means it's most likely media, so well do our check next.
|
||||
}
|
||||
|
||||
//this is most likely media, not sure what kind of exception might get thrown in 4.0.x or 4.1 if accessing a null
|
||||
//creator, so we catch everything.
|
||||
|
||||
var m = new Media(sender.Id);
|
||||
if (!string.IsNullOrEmpty(m.Path))
|
||||
{
|
||||
//this is a media item, no exception occurred on access to path and it's not empty which means it was found
|
||||
Media_AfterSave(m, e);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only remove indexes using providers that SupportUnpublishedContent
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
[SecuritySafeCritical]
|
||||
private void Document_AfterDelete(Document sender, DeleteEventArgs e)
|
||||
{
|
||||
var nodeId = sender.Id.ToString();
|
||||
|
||||
//ensure that only the providers that have unpublishing support enabled
|
||||
//that are also flagged to listen
|
||||
ExamineManager.Instance.DeleteFromIndex(nodeId,
|
||||
ExamineManager.Instance.IndexProviderCollection.OfType<BaseUmbracoIndexer>()
|
||||
.Where(x => x.SupportUnpublishedContent
|
||||
&& x.EnableDefaultEventHandler));
|
||||
}
|
||||
|
||||
[SecuritySafeCritical]
|
||||
private void Media_AfterDelete(Media 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<BaseUmbracoIndexer>()
|
||||
.Where(x => x.EnableDefaultEventHandler));
|
||||
}
|
||||
|
||||
private void Media_AfterSave(Media sender, umbraco.cms.businesslogic.SaveEventArgs e)
|
||||
{
|
||||
//ensure that only the providers are flagged to listen execute
|
||||
IndexMedia(sender);
|
||||
}
|
||||
|
||||
private void IndexMedia(Media sender)
|
||||
{
|
||||
ExamineManager.Instance.ReIndexNode(sender.ToXDocument(true).Root, IndexTypes.Media,
|
||||
ExamineManager.Instance.IndexProviderCollection.OfType<BaseUmbracoIndexer>()
|
||||
.Where(x => x.EnableDefaultEventHandler));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When media is moved, re-index
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Media_AfterMove(object sender, MoveEventArgs e)
|
||||
{
|
||||
if (sender is Media)
|
||||
{
|
||||
Media m = (Media)sender;
|
||||
IndexMedia(m);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only Update indexes for providers that dont SupportUnpublishedContent
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void content_AfterUpdateDocumentCache(Document sender, umbraco.cms.businesslogic.DocumentCacheEventArgs e)
|
||||
{
|
||||
//ensure that only the providers that have DONT unpublishing support enabled
|
||||
//that are also flagged to listen
|
||||
ExamineManager.Instance.ReIndexNode(sender.ToXDocument(true).Root, IndexTypes.Content,
|
||||
ExamineManager.Instance.IndexProviderCollection.OfType<BaseUmbracoIndexer>()
|
||||
.Where(x => !x.SupportUnpublishedContent
|
||||
&& x.EnableDefaultEventHandler));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only update indexes for providers that don't SupportUnpublishedContnet
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
[SecuritySafeCritical]
|
||||
private void content_AfterClearDocumentCache(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<BaseUmbracoIndexer>()
|
||||
.Where(x => !x.SupportUnpublishedContent
|
||||
&& x.EnableDefaultEventHandler));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
179
src/UmbracoExamine/UmbracoExamine.csproj
Normal file
179
src/UmbracoExamine/UmbracoExamine.csproj
Normal file
@@ -0,0 +1,179 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>9.0.21022</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{07FBC26B-2927-4A22-8D96-D644C667FECC}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>UmbracoExamine</RootNamespace>
|
||||
<AssemblyName>UmbracoExamine</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<SccProjectName>
|
||||
</SccProjectName>
|
||||
<SccLocalPath>
|
||||
</SccLocalPath>
|
||||
<SccAuxPath>
|
||||
</SccAuxPath>
|
||||
<SccProvider>
|
||||
</SccProvider>
|
||||
<FileUpgradeFlags>
|
||||
</FileUpgradeFlags>
|
||||
<UpgradeBackupLocation>
|
||||
</UpgradeBackupLocation>
|
||||
<OldToolsVersion>3.5</OldToolsVersion>
|
||||
<PublishUrl>publish\</PublishUrl>
|
||||
<Install>true</Install>
|
||||
<InstallFrom>Disk</InstallFrom>
|
||||
<UpdateEnabled>false</UpdateEnabled>
|
||||
<UpdateMode>Foreground</UpdateMode>
|
||||
<UpdateInterval>7</UpdateInterval>
|
||||
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
|
||||
<UpdatePeriodically>false</UpdatePeriodically>
|
||||
<UpdateRequired>false</UpdateRequired>
|
||||
<MapFileExtensions>true</MapFileExtensions>
|
||||
<ApplicationRevision>0</ApplicationRevision>
|
||||
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
|
||||
<IsWebBootstrapper>false</IsWebBootstrapper>
|
||||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<BootstrapperEnabled>true</BootstrapperEnabled>
|
||||
<TargetFrameworkProfile />
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
||||
<RestorePackages>true</RestorePackages>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<CodeAnalysisRuleSet>SecurityRules.ruleset</CodeAnalysisRuleSet>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
<DocumentationFile>
|
||||
</DocumentationFile>
|
||||
<RunCodeAnalysis>false</RunCodeAnalysis>
|
||||
<CodeAnalysisIgnoreGeneratedCode>false</CodeAnalysisIgnoreGeneratedCode>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<DocumentationFile>bin\Release\UmbracoExamine.XML</DocumentationFile>
|
||||
<CodeAnalysisIgnoreGeneratedCode>false</CodeAnalysisIgnoreGeneratedCode>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SignAssembly>false</SignAssembly>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<DelaySign>true</DelaySign>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<AssemblyOriginatorKeyFile>..\Solution Items\TheFARM-Public.snk</AssemblyOriginatorKeyFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Examine">
|
||||
<HintPath>..\packages\Examine.0.1.42.2941\lib\Examine.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ICSharpCode.SharpZipLib, Version=0.86.0.518, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Lucene.Net, Version=2.9.4.1, Culture=neutral, PublicKeyToken=85089178b9ac3181, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.configuration" />
|
||||
<Reference Include="System.Core">
|
||||
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
||||
</Reference>
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Web.Abstractions" />
|
||||
<Reference Include="System.Xml.Linq">
|
||||
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="BaseUmbracoIndexer.cs" />
|
||||
<Compile Include="Config\IndexSetExtensions.cs" />
|
||||
<Compile Include="DataServices\IContentService.cs" />
|
||||
<Compile Include="DataServices\IDataService.cs" />
|
||||
<Compile Include="DataServices\ILogService.cs" />
|
||||
<Compile Include="DataServices\IMediaService.cs" />
|
||||
<Compile Include="DataServices\UmbracoDataService.cs" />
|
||||
<Compile Include="DataServices\UmbracoContentService.cs" />
|
||||
<Compile Include="DataServices\UmbracoMediaService.cs" />
|
||||
<Compile Include="IndexTypes.cs" />
|
||||
<Compile Include="LoggingLevel.cs" />
|
||||
<Compile Include="UmbracoMemberIndexer.cs" />
|
||||
<Compile Include="ContentExtensions.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="UmbracoContentIndexer.cs" />
|
||||
<Compile Include="UmbracoExamineSearcher.cs" />
|
||||
<Compile Include="DataServices\UmbracoLogService.cs" />
|
||||
<Compile Include="UmbracoEventManager.cs" />
|
||||
<Compile Include="XsltExtensions.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
|
||||
<Install>false</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework 3.5 SP1</ProductName>
|
||||
<Install>true</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>Windows Installer 3.1</ProductName>
|
||||
<Install>true</Install>
|
||||
</BootstrapperPackage>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\umbraco.businesslogic\umbraco.businesslogic.csproj">
|
||||
<Project>{e469a9ce-1bec-423f-ac44-713cd72457ea}</Project>
|
||||
<Name>umbraco.businesslogic</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\umbraco.cms\umbraco.cms.csproj">
|
||||
<Project>{ccd75ec3-63db-4184-b49d-51c1dd337230}</Project>
|
||||
<Name>umbraco.cms</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\umbraco.datalayer\umbraco.datalayer.csproj">
|
||||
<Project>{c7cb79f0-1c97-4b33-bfa7-00731b579ae2}</Project>
|
||||
<Name>umbraco.datalayer</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\umbraco.interfaces\umbraco.interfaces.csproj">
|
||||
<Project>{511f6d8d-7717-440a-9a57-a507e9a8b27f}</Project>
|
||||
<Name>umbraco.interfaces</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Umbraco.Web\Umbraco.Web.csproj">
|
||||
<Project>{651e1350-91b6-44b7-bd60-7207006d7003}</Project>
|
||||
<Name>Umbraco.Web</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
83
src/UmbracoExamine/UmbracoExamineSearcher.cs
Normal file
83
src/UmbracoExamine/UmbracoExamineSearcher.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
using Examine;
|
||||
using Examine.Providers;
|
||||
using Examine.SearchCriteria;
|
||||
using UmbracoExamine.Config;
|
||||
using Examine.LuceneEngine;
|
||||
using Examine.LuceneEngine.Providers;
|
||||
using Examine.LuceneEngine.SearchCriteria;
|
||||
using Lucene.Net.Analysis;
|
||||
|
||||
|
||||
namespace UmbracoExamine
|
||||
{
|
||||
/// <summary>
|
||||
/// An Examine searcher which uses Lucene.Net as the
|
||||
/// </summary>
|
||||
public class UmbracoExamineSearcher : LuceneSearcher
|
||||
{
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
public UmbracoExamineSearcher()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor to allow for creating an indexer at runtime
|
||||
/// </summary>
|
||||
/// <param name="indexPath"></param>
|
||||
/// <param name="analyzer"></param>
|
||||
[SecuritySafeCritical]
|
||||
public UmbracoExamineSearcher(DirectoryInfo indexPath, Analyzer analyzer)
|
||||
: base(indexPath, analyzer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor to allow for creating an indexer at runtime
|
||||
/// </summary>
|
||||
/// <param name="luceneDirectory"></param>
|
||||
/// <param name="analyzer"></param>
|
||||
[SecuritySafeCritical]
|
||||
public UmbracoExamineSearcher(Lucene.Net.Store.Directory luceneDirectory, Analyzer analyzer)
|
||||
: base(luceneDirectory, analyzer)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Override in order to set the nodeTypeAlias field name of the underlying SearchCriteria to __NodeTypeAlias
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="defaultOperation"></param>
|
||||
/// <returns></returns>
|
||||
public override ISearchCriteria CreateSearchCriteria(string type, BooleanOperation defaultOperation)
|
||||
{
|
||||
var criteria = base.CreateSearchCriteria(type, defaultOperation) as LuceneSearchCriteria;
|
||||
criteria.NodeTypeAliasField = UmbracoContentIndexer.NodeTypeAliasFieldName;
|
||||
return criteria;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of fields to search on, this will also exclude the IndexPathFieldName and node type alias
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected internal override string[] GetSearchFields()
|
||||
{
|
||||
var fields = base.GetSearchFields();
|
||||
return fields
|
||||
.Where(x => x != UmbracoContentIndexer.IndexPathFieldName)
|
||||
.Where(x => x != UmbracoContentIndexer.NodeTypeAliasFieldName)
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
87
src/UmbracoExamine/UmbracoMemberIndexer.cs
Normal file
87
src/UmbracoExamine/UmbracoMemberIndexer.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.XPath;
|
||||
using umbraco.cms.businesslogic.member;
|
||||
using Examine.LuceneEngine;
|
||||
using System.Collections.Generic;
|
||||
using Examine;
|
||||
using System.IO;
|
||||
using UmbracoExamine.DataServices;
|
||||
using Lucene.Net.Analysis;
|
||||
|
||||
namespace UmbracoExamine
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class UmbracoMemberIndexer : UmbracoContentIndexer
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
public UmbracoMemberIndexer()
|
||||
: base() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor to allow for creating an indexer at runtime
|
||||
/// </summary>
|
||||
/// <param name="indexerData"></param>
|
||||
/// <param name="indexPath"></param>
|
||||
/// <param name="dataService"></param>
|
||||
/// <param name="analyzer"></param>
|
||||
[SecuritySafeCritical]
|
||||
public UmbracoMemberIndexer(IIndexCriteria indexerData, DirectoryInfo indexPath, IDataService dataService, Analyzer analyzer, bool async)
|
||||
: base(indexerData, indexPath, dataService, analyzer, async) { }
|
||||
|
||||
/// <summary>
|
||||
/// The supported types for this indexer
|
||||
/// </summary>
|
||||
protected override IEnumerable<string> SupportedTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
return new string[] { IndexTypes.Member };
|
||||
}
|
||||
}
|
||||
|
||||
[SecuritySafeCritical]
|
||||
protected override XDocument GetXDocument(string xPath, string type)
|
||||
{
|
||||
if (type == IndexTypes.Member)
|
||||
{
|
||||
Member[] rootMembers = Member.GetAll;
|
||||
var xmlMember = XDocument.Parse("<member></member>");
|
||||
foreach (Member member in rootMembers)
|
||||
{
|
||||
xmlMember.Root.Add(GetMemberItem(member.Id));
|
||||
}
|
||||
var result = ((IEnumerable)xmlMember.XPathEvaluate(xPath)).Cast<XElement>();
|
||||
return result.ToXDocument();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override System.Collections.Generic.Dictionary<string, string> GetDataToIndex(XElement node, string type)
|
||||
{
|
||||
var data = base.GetDataToIndex(node, type);
|
||||
|
||||
if (data.ContainsKey("email"))
|
||||
{
|
||||
data.Add("__email",data["email"].Replace("."," ").Replace("@"," "));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
[SecuritySafeCritical]
|
||||
private XElement GetMemberItem(int nodeId)
|
||||
{
|
||||
var nodes = umbraco.library.GetMember(nodeId);
|
||||
return XElement.Parse(nodes.Current.OuterXml);
|
||||
}
|
||||
}
|
||||
}
|
||||
265
src/UmbracoExamine/XsltExtensions.cs
Normal file
265
src/UmbracoExamine/XsltExtensions.cs
Normal file
@@ -0,0 +1,265 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.XPath;
|
||||
using Examine;
|
||||
using Examine.LuceneEngine.Providers;
|
||||
using Examine.LuceneEngine.SearchCriteria;
|
||||
using Examine.SearchCriteria;
|
||||
using Examine.Providers;
|
||||
using UmbracoExamine.DataServices;
|
||||
|
||||
namespace UmbracoExamine
|
||||
{
|
||||
/// <summary>
|
||||
/// Methods to support Umbraco XSLT extensions.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// XSLT extensions will ONLY work for provider that have a base class of BaseUmbracoIndexer
|
||||
/// </remarks>
|
||||
public class XsltExtensions
|
||||
{
|
||||
///<summary>
|
||||
/// Uses the provider specified to search, returning an XPathNodeIterator
|
||||
///</summary>
|
||||
///<param name="searchText"></param>
|
||||
///<param name="useWildcards"></param>
|
||||
///<param name="provider"></param>
|
||||
///<param name="indexType"></param>
|
||||
///<returns></returns>
|
||||
internal static XPathNodeIterator Search(string searchText, bool useWildcards, LuceneSearcher provider, string indexType)
|
||||
{
|
||||
if (provider == null) throw new ArgumentNullException("provider");
|
||||
|
||||
var results = provider.Search(searchText, useWildcards, indexType);
|
||||
return GetResultsAsXml(results);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses the provider specified to search, returning an XPathNodeIterator
|
||||
/// </summary>
|
||||
/// <param name="searchText">The search text.</param>
|
||||
/// <param name="useWildcards">if set to <c>true</c> [use wildcards].</param>
|
||||
/// <param name="providerName">Name of the provider.</param>
|
||||
/// <param name="indexType">Type of the index.</param>
|
||||
/// <returns></returns>
|
||||
public static XPathNodeIterator Search(string searchText, bool useWildcards, string providerName, string indexType)
|
||||
{
|
||||
EnsureProvider(ExamineManager.Instance.SearchProviderCollection[providerName]);
|
||||
|
||||
var provider = ExamineManager.Instance.SearchProviderCollection[providerName] as LuceneSearcher;
|
||||
|
||||
return Search(searchText, useWildcards, provider, indexType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses the provider specified to search, returning an XPathNodeIterator
|
||||
/// </summary>
|
||||
/// <param name="searchText"></param>
|
||||
/// <param name="useWildcards"></param>
|
||||
/// <param name="providerName"></param>
|
||||
/// <returns></returns>
|
||||
public static XPathNodeIterator Search(string searchText, bool useWildcards, string providerName)
|
||||
{
|
||||
return Search(searchText, useWildcards, providerName, string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses the default provider specified to search, returning an XPathNodeIterator
|
||||
/// </summary>
|
||||
/// <param name="searchText">The search query</param>
|
||||
/// <param name="useWildcards">Enable a wildcard search query</param>
|
||||
/// <returns>A node-set of the search results</returns>
|
||||
public static XPathNodeIterator Search(string searchText, bool useWildcards)
|
||||
{
|
||||
return Search(searchText, useWildcards, ExamineManager.Instance.DefaultSearchProvider.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses the default provider specified to search, returning an XPathNodeIterator
|
||||
/// </summary>
|
||||
/// <param name="searchText">The search query</param>
|
||||
/// <returns>A node-set of the search results</returns>
|
||||
public static XPathNodeIterator Search(string searchText)
|
||||
{
|
||||
return Search(searchText, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will perform a search against the media index type only
|
||||
/// </summary>
|
||||
/// <param name="searchText"></param>
|
||||
/// <param name="useWildcards"></param>
|
||||
/// <param name="providerName"></param>
|
||||
/// <returns></returns>
|
||||
public static XPathNodeIterator SearchMediaOnly(string searchText, bool useWildcards, string providerName)
|
||||
{
|
||||
return Search(searchText, useWildcards, providerName, IndexTypes.Media);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will perform a search against the media index type only
|
||||
/// </summary>
|
||||
/// <param name="searchText"></param>
|
||||
/// <param name="useWildcards"></param>
|
||||
/// <returns></returns>
|
||||
public static XPathNodeIterator SearchMediaOnly(string searchText, bool useWildcards)
|
||||
{
|
||||
return SearchMediaOnly(searchText, useWildcards, ExamineManager.Instance.DefaultSearchProvider.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will perform a search against the media index type only
|
||||
/// </summary>
|
||||
/// <param name="searchText"></param>
|
||||
/// <returns></returns>
|
||||
public static XPathNodeIterator SearchMediaOnly(string searchText)
|
||||
{
|
||||
return SearchMediaOnly(searchText, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches the member only.
|
||||
/// </summary>
|
||||
/// <param name="searchText">The search text.</param>
|
||||
/// <param name="useWildcards">if set to <c>true</c> [use wildcards].</param>
|
||||
/// <param name="providerName">Name of the provider.</param>
|
||||
/// <returns></returns>
|
||||
public static XPathNodeIterator SearchMemberOnly(string searchText, bool useWildcards, string providerName)
|
||||
{
|
||||
return Search(searchText, useWildcards, providerName, IndexTypes.Member);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches the member only.
|
||||
/// </summary>
|
||||
/// <param name="searchText">The search text.</param>
|
||||
/// <param name="useWildcards">if set to <c>true</c> [use wildcards].</param>
|
||||
/// <returns></returns>
|
||||
public static XPathNodeIterator SearchMemberOnly(string searchText, bool useWildcards)
|
||||
{
|
||||
return SearchMemberOnly(searchText, useWildcards, ExamineManager.Instance.DefaultSearchProvider.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches the member only.
|
||||
/// </summary>
|
||||
/// <param name="searchText">The search text.</param>
|
||||
/// <returns></returns>
|
||||
public static XPathNodeIterator SearchMemberOnly(string searchText)
|
||||
{
|
||||
return SearchMemberOnly(searchText, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will perform a search against the content index type only
|
||||
/// </summary>
|
||||
/// <param name="searchText"></param>
|
||||
/// <param name="useWildcards"></param>
|
||||
/// <param name="providerName"></param>
|
||||
/// <returns></returns>
|
||||
public static XPathNodeIterator SearchContentOnly(string searchText, bool useWildcards, string providerName)
|
||||
{
|
||||
return Search(searchText, useWildcards, providerName, IndexTypes.Content);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Will perform a search against the content index type only
|
||||
/// </summary>
|
||||
/// <param name="searchText"></param>
|
||||
/// <param name="useWildcards"></param>
|
||||
/// <returns></returns>
|
||||
public static XPathNodeIterator SearchContentOnly(string searchText, bool useWildcards)
|
||||
{
|
||||
return SearchContentOnly(searchText, useWildcards, ExamineManager.Instance.DefaultSearchProvider.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will perform a search against the content index type only
|
||||
/// </summary>
|
||||
/// <param name="searchText"></param>
|
||||
/// <returns></returns>
|
||||
public static XPathNodeIterator SearchContentOnly(string searchText)
|
||||
{
|
||||
return SearchContentOnly(searchText, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the provider.
|
||||
/// </summary>
|
||||
/// <param name="p">The provider.</param>
|
||||
private static void EnsureProvider(BaseSearchProvider p)
|
||||
{
|
||||
if (!(p is LuceneSearcher))
|
||||
{
|
||||
throw new NotSupportedException("XSLT Extensions are only support for providers of type LuceneSearcher");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the results as XML.
|
||||
/// </summary>
|
||||
/// <param name="results">The results.</param>
|
||||
/// <returns></returns>
|
||||
private static XPathNodeIterator GetResultsAsXml(ISearchResults results)
|
||||
{
|
||||
// create the XDocument
|
||||
XDocument doc = new XDocument();
|
||||
|
||||
// check there are any search results
|
||||
if (results.TotalItemCount > 0)
|
||||
{
|
||||
// create the root element
|
||||
XElement root = new XElement("nodes");
|
||||
|
||||
// iterate through the search results
|
||||
foreach (SearchResult result in results)
|
||||
{
|
||||
// create a new <node> element
|
||||
XElement node = new XElement("node");
|
||||
|
||||
// create the @id attribute
|
||||
XAttribute nodeId = new XAttribute("id", result.Id);
|
||||
|
||||
// create the @score attribute
|
||||
XAttribute nodeScore = new XAttribute("score", result.Score);
|
||||
|
||||
// add the content
|
||||
node.Add(nodeId, nodeScore);
|
||||
|
||||
foreach (KeyValuePair<String, String> field in result.Fields)
|
||||
{
|
||||
// create a new <data> element
|
||||
XElement data = new XElement("data");
|
||||
|
||||
// create the @alias attribute
|
||||
XAttribute alias = new XAttribute("alias", field.Key);
|
||||
|
||||
// assign the value to a CDATA section
|
||||
XCData value = new XCData(field.Value);
|
||||
|
||||
// append the content
|
||||
data.Add(alias, value);
|
||||
|
||||
// append the <data> element
|
||||
node.Add(data);
|
||||
}
|
||||
|
||||
// add the node
|
||||
root.Add(node);
|
||||
}
|
||||
|
||||
// add the root node
|
||||
doc.Add(root);
|
||||
}
|
||||
else
|
||||
{
|
||||
doc.Add(new XElement("error", "There were no search results."));
|
||||
}
|
||||
|
||||
return doc.CreateNavigator().Select("/");
|
||||
}
|
||||
}
|
||||
}
|
||||
5
src/UmbracoExamine/packages.config
Normal file
5
src/UmbracoExamine/packages.config
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Lucene.Net" version="2.9.4.1" targetFramework="net40" />
|
||||
<package id="SharpZipLib" version="0.86.0" targetFramework="net40" />
|
||||
</packages>
|
||||
@@ -12,4 +12,5 @@
|
||||
<repository path="..\Umbraco.Tests\packages.config" />
|
||||
<repository path="..\Umbraco.Web.UI\packages.config" />
|
||||
<repository path="..\Umbraco.Web\packages.config" />
|
||||
<repository path="..\UmbracoExamine\packages.config" />
|
||||
</repositories>
|
||||
@@ -59,6 +59,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{66AB04
|
||||
.nuget\NuGet.targets = .nuget\NuGet.targets
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UmbracoExamine", "UmbracoExamine\UmbracoExamine.csproj", "{07FBC26B-2927-4A22-8D96-D644C667FECC}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UmbracoExamineLibs", "UmbracoExamineLibs", "{DD32977B-EF54-475B-9A1B-B97A502C6E58}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -125,11 +129,16 @@ Global
|
||||
{5D3B8245-ADA6-453F-A008-50ED04BFE770}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5D3B8245-ADA6-453F-A008-50ED04BFE770}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5D3B8245-ADA6-453F-A008-50ED04BFE770}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{07FBC26B-2927-4A22-8D96-D644C667FECC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{07FBC26B-2927-4A22-8D96-D644C667FECC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{07FBC26B-2927-4A22-8D96-D644C667FECC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{07FBC26B-2927-4A22-8D96-D644C667FECC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{5D3B8245-ADA6-453F-A008-50ED04BFE770} = {B5BD12C1-A454-435E-8A46-FF4A364C0382}
|
||||
{07FBC26B-2927-4A22-8D96-D644C667FECC} = {DD32977B-EF54-475B-9A1B-B97A502C6E58}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
Reference in New Issue
Block a user