mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 19:54:57 +08:00
Added search fields administration
--HG-- branch : dev
This commit is contained in:
@@ -10,7 +10,7 @@ namespace Orchard.Core.Common.Handlers {
|
||||
Filters.Add(StorageFilter.For(bodyRepository));
|
||||
|
||||
OnIndexing<BodyAspect>((context, bodyAspect) => context.DocumentIndex
|
||||
.Add("body", bodyAspect.Record.Text, true).Analyze()
|
||||
.Add("body", bodyAspect.Record.Text).RemoveTags().Analyze()
|
||||
.Add("format", bodyAspect.Record.Format).Store());
|
||||
}
|
||||
}
|
||||
|
@@ -37,8 +37,8 @@ namespace Orchard.Core.Common.Handlers {
|
||||
OnCreated<RoutableAspect>((context, ra) => routableService.ProcessSlug(ra));
|
||||
|
||||
OnIndexing<RoutableAspect>((context, part) => context.DocumentIndex
|
||||
.Add("slug", part.Slug)
|
||||
.Add("title", part.Title)
|
||||
.Add("slug", part.Slug).Analyze().Store()
|
||||
.Add("title", part.Title).Analyze()
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Lucene.Net.Documents;
|
||||
using Orchard.Utility.Extensions;
|
||||
|
||||
@@ -8,34 +9,33 @@ namespace Orchard.Indexing.Models {
|
||||
public class LuceneDocumentIndex : IDocumentIndex {
|
||||
|
||||
public List<AbstractField> Fields { get; private set; }
|
||||
private AbstractField _previousField;
|
||||
|
||||
public int Id { get; private set; }
|
||||
private string _name;
|
||||
private string _stringValue;
|
||||
private int _intValue;
|
||||
private float _floatValue;
|
||||
private bool _analyze;
|
||||
private bool _store;
|
||||
private bool _removeTags;
|
||||
private TypeCode _typeCode;
|
||||
|
||||
public int ContentItemId { get; private set; }
|
||||
|
||||
public LuceneDocumentIndex(int documentId) {
|
||||
Fields = new List<AbstractField>();
|
||||
SetContentItemId(documentId);
|
||||
IsDirty = false;
|
||||
|
||||
_typeCode = TypeCode.Empty;
|
||||
}
|
||||
|
||||
public bool IsDirty { get; private set; }
|
||||
|
||||
public IDocumentIndex Add(string name, string value) {
|
||||
return Add(name, value, false);
|
||||
}
|
||||
|
||||
public IDocumentIndex Add(string name, string value, bool removeTags) {
|
||||
AppendPreviousField();
|
||||
|
||||
if(value == null) {
|
||||
value = String.Empty;
|
||||
}
|
||||
|
||||
if(removeTags) {
|
||||
value = value.RemoveTags();
|
||||
}
|
||||
|
||||
_previousField = new Field(name, value, Field.Store.NO, Field.Index.NOT_ANALYZED);
|
||||
PrepareForIndexing();
|
||||
_name = name;
|
||||
_stringValue = value;
|
||||
_typeCode = TypeCode.String;
|
||||
IsDirty = true;
|
||||
return this;
|
||||
}
|
||||
@@ -45,8 +45,10 @@ namespace Orchard.Indexing.Models {
|
||||
}
|
||||
|
||||
public IDocumentIndex Add(string name, int value) {
|
||||
AppendPreviousField();
|
||||
_previousField = new NumericField(name, Field.Store.NO, true).SetIntValue(value);
|
||||
PrepareForIndexing();
|
||||
_name = name;
|
||||
_intValue = value;
|
||||
_typeCode = TypeCode.Int32;
|
||||
IsDirty = true;
|
||||
return this;
|
||||
}
|
||||
@@ -56,8 +58,10 @@ namespace Orchard.Indexing.Models {
|
||||
}
|
||||
|
||||
public IDocumentIndex Add(string name, float value) {
|
||||
AppendPreviousField();
|
||||
_previousField = new NumericField(name, Field.Store.NO, true).SetFloatValue(value);
|
||||
PrepareForIndexing();
|
||||
_name = name;
|
||||
_floatValue = value;
|
||||
_typeCode = TypeCode.Single;
|
||||
IsDirty = true;
|
||||
return this;
|
||||
}
|
||||
@@ -66,46 +70,57 @@ namespace Orchard.Indexing.Models {
|
||||
return Add(name, value.ToString());
|
||||
}
|
||||
|
||||
public IDocumentIndex RemoveTags() {
|
||||
_removeTags = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IDocumentIndex Store() {
|
||||
EnsurePreviousField();
|
||||
var index = _previousField.IsTokenized() ? Field.Index.ANALYZED : Field.Index.NOT_ANALYZED;
|
||||
_previousField = new Field(_previousField.Name(), _previousField.StringValue(), Field.Store.YES, index);
|
||||
_store = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IDocumentIndex Analyze() {
|
||||
EnsurePreviousField();
|
||||
|
||||
var index = Field.Index.ANALYZED;
|
||||
var store = _previousField.IsStored() ? Field.Store.YES : Field.Store.NO;
|
||||
_previousField = new Field(_previousField.Name(), _previousField.StringValue(), store, index);
|
||||
_analyze = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IDocumentIndex SetContentItemId(int id) {
|
||||
Id = id;
|
||||
Fields.Add(new Field("id", id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
|
||||
public IDocumentIndex SetContentItemId(int contentItemId) {
|
||||
ContentItemId = contentItemId;
|
||||
Fields.Add(new Field("id", contentItemId.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
|
||||
return this;
|
||||
}
|
||||
|
||||
private void AppendPreviousField() {
|
||||
if (_previousField == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Fields.Add(_previousField);
|
||||
_previousField = null;
|
||||
}
|
||||
|
||||
|
||||
public void PrepareForIndexing() {
|
||||
AppendPreviousField();
|
||||
}
|
||||
|
||||
private void EnsurePreviousField() {
|
||||
if(_previousField == null) {
|
||||
throw new ApplicationException("Operation can't be applied in this context.");
|
||||
switch(_typeCode) {
|
||||
case TypeCode.String:
|
||||
if(_removeTags) {
|
||||
_stringValue = _stringValue.RemoveTags();
|
||||
}
|
||||
Fields.Add(new Field(_name, _stringValue ?? String.Empty,
|
||||
_store ? Field.Store.YES : Field.Store.NO,
|
||||
_analyze ? Field.Index.ANALYZED : Field.Index.NOT_ANALYZED));
|
||||
break;
|
||||
case TypeCode.Int32:
|
||||
Fields.Add(new NumericField(_name,
|
||||
_store ? Field.Store.YES : Field.Store.NO,
|
||||
true).SetIntValue(_intValue));
|
||||
break;
|
||||
case TypeCode.Single:
|
||||
Fields.Add(new NumericField(_name,
|
||||
_store ? Field.Store.YES : Field.Store.NO,
|
||||
true).SetFloatValue(_floatValue));
|
||||
break;
|
||||
case TypeCode.Empty:
|
||||
break;
|
||||
default:
|
||||
throw new OrchardException("Unexpected index type");
|
||||
}
|
||||
|
||||
_removeTags = false;
|
||||
_analyze = false;
|
||||
_store = false;
|
||||
_typeCode = TypeCode.Empty;
|
||||
}
|
||||
}
|
||||
}
|
@@ -144,11 +144,11 @@ namespace Orchard.Indexing.Services {
|
||||
current = indexDocument;
|
||||
var doc = CreateDocument(indexDocument);
|
||||
writer.AddDocument(doc);
|
||||
Logger.Debug("Document [{0}] indexed", indexDocument.Id);
|
||||
Logger.Debug("Document [{0}] indexed", indexDocument.ContentItemId);
|
||||
}
|
||||
}
|
||||
catch ( Exception ex ) {
|
||||
Logger.Error(ex, "An unexpected error occured while add the document [{0}] from the index [{1}].", current.Id, indexName);
|
||||
Logger.Error(ex, "An unexpected error occured while add the document [{0}] from the index [{1}].", current.ContentItemId, indexName);
|
||||
}
|
||||
finally {
|
||||
writer.Optimize();
|
||||
@@ -229,5 +229,19 @@ namespace Orchard.Indexing.Services {
|
||||
doc.Save(settingsFileName);
|
||||
}
|
||||
|
||||
public string[] GetFields(string indexName) {
|
||||
if ( !Exists(indexName) ) {
|
||||
return new string[0];
|
||||
}
|
||||
|
||||
var reader = IndexReader.Open(GetDirectory(indexName), true);
|
||||
|
||||
try {
|
||||
return reader.GetFieldNames(IndexReader.FieldOption.ALL).ToArray();
|
||||
}
|
||||
finally {
|
||||
reader.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Lucene.Net.Analysis;
|
||||
using Lucene.Net.Analysis.Tokenattributes;
|
||||
using Lucene.Net.Index;
|
||||
using Lucene.Net.Search;
|
||||
using Lucene.Net.Store;
|
||||
@@ -20,15 +19,12 @@ namespace Orchard.Indexing.Services {
|
||||
private readonly Directory _directory;
|
||||
|
||||
private readonly List<BooleanClause> _clauses;
|
||||
private readonly List<BooleanClause> _filters;
|
||||
private int _count;
|
||||
private int _skip;
|
||||
private readonly Dictionary<string, DateTime> _before;
|
||||
private readonly Dictionary<string, DateTime> _after;
|
||||
private string _sort;
|
||||
private bool _sortDescending;
|
||||
private string _parse;
|
||||
private readonly Analyzer _analyzer;
|
||||
private string[] _defaultFields;
|
||||
private bool _asFilter;
|
||||
|
||||
// pending clause attributes
|
||||
private BooleanClause.Occur _occur;
|
||||
@@ -44,13 +40,10 @@ namespace Orchard.Indexing.Services {
|
||||
|
||||
_count = MaxResults;
|
||||
_skip = 0;
|
||||
_before = new Dictionary<string, DateTime>();
|
||||
_after = new Dictionary<string, DateTime>();
|
||||
_clauses = new List<BooleanClause>();
|
||||
_filters = new List<BooleanClause>();
|
||||
_sort = String.Empty;
|
||||
_sortDescending = true;
|
||||
_parse = String.Empty;
|
||||
_analyzer = LuceneIndexProvider.CreateAnalyzer();
|
||||
|
||||
InitPendingClause();
|
||||
}
|
||||
@@ -67,8 +60,13 @@ namespace Orchard.Indexing.Services {
|
||||
throw new ArgumentException("Query can't be empty");
|
||||
}
|
||||
|
||||
_defaultFields = defaultFields;
|
||||
_parse = query;
|
||||
var analyzer = LuceneIndexProvider.CreateAnalyzer();
|
||||
foreach ( var defaultField in defaultFields ) {
|
||||
var clause = new BooleanClause(new QueryParser(LuceneIndexProvider.LuceneVersion, defaultField, analyzer).Parse(query), BooleanClause.Occur.SHOULD);
|
||||
_clauses.Add(clause);
|
||||
}
|
||||
|
||||
_query = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -155,6 +153,7 @@ namespace Orchard.Indexing.Services {
|
||||
_exactMatch = false;
|
||||
_query = null;
|
||||
_boost = 0;
|
||||
_asFilter = false;
|
||||
}
|
||||
|
||||
private void CreatePendingClause() {
|
||||
@@ -172,8 +171,12 @@ namespace Orchard.Indexing.Services {
|
||||
_query = new PrefixQuery(termQuery.GetTerm());
|
||||
}
|
||||
}
|
||||
|
||||
_clauses.Add(new BooleanClause(_query, _occur));
|
||||
if ( _asFilter ) {
|
||||
_filters.Add(new BooleanClause(_query, _occur));
|
||||
}
|
||||
else {
|
||||
_clauses.Add(new BooleanClause(_query, _occur));
|
||||
}
|
||||
}
|
||||
|
||||
public ISearchBuilder SortBy(string name) {
|
||||
@@ -186,6 +189,11 @@ namespace Orchard.Indexing.Services {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ISearchBuilder AsFilter() {
|
||||
_asFilter = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ISearchBuilder Slice(int skip, int count) {
|
||||
if ( skip < 0 ) {
|
||||
throw new ArgumentException("Skip must be greater or equal to zero");
|
||||
@@ -206,23 +214,27 @@ namespace Orchard.Indexing.Services {
|
||||
|
||||
var query = new BooleanQuery();
|
||||
|
||||
if(!String.IsNullOrWhiteSpace(_parse)) {
|
||||
|
||||
foreach ( var defaultField in _defaultFields ) {
|
||||
var clause = new BooleanClause(new QueryParser(LuceneIndexProvider.LuceneVersion, defaultField, LuceneIndexProvider.CreateAnalyzer()).Parse(_parse), BooleanClause.Occur.SHOULD);
|
||||
query.Add(clause);
|
||||
}
|
||||
}
|
||||
|
||||
foreach( var clause in _clauses)
|
||||
query.Add(clause);
|
||||
|
||||
|
||||
if ( query.Clauses().Count == 0 ) { // get all documents ?
|
||||
query.Add(new TermRangeQuery("id", "0", "9", true, true), BooleanClause.Occur.SHOULD);
|
||||
}
|
||||
|
||||
Logger.Debug("New search query: {0}", query.ToString());
|
||||
return query;
|
||||
Query finalQuery = query;
|
||||
|
||||
if(_filters.Count > 0) {
|
||||
var filter = new BooleanQuery();
|
||||
foreach( var clause in _filters)
|
||||
filter.Add(clause);
|
||||
var queryFilter = new QueryWrapperFilter(filter);
|
||||
|
||||
finalQuery = new FilteredQuery(query, queryFilter);
|
||||
}
|
||||
|
||||
Logger.Debug("New search query: {0}", finalQuery.ToString());
|
||||
return finalQuery;
|
||||
}
|
||||
|
||||
public IEnumerable<ISearchHit> Search() {
|
||||
|
@@ -5,6 +5,7 @@ using Orchard.Search.Services;
|
||||
using Orchard.Search.ViewModels;
|
||||
using Orchard.Settings;
|
||||
using Orchard.Search.Models;
|
||||
using System;
|
||||
|
||||
namespace Orchard.Search.Controllers {
|
||||
[ValidateInput(false)]
|
||||
@@ -20,13 +21,18 @@ namespace Orchard.Search.Controllers {
|
||||
protected virtual ISite CurrentSite { get; [UsedImplicitly] private set; }
|
||||
|
||||
public ActionResult Index(string q, int page = 1, int pageSize = 10) {
|
||||
var searchFields = CurrentSite.As<SearchSettings>().Record.SearchedFields.Split(new[] {',', ' '}, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
var searchViewModel = new SearchViewModel {
|
||||
Query = q,
|
||||
DefaultPageSize = 10, // <- yeah, I know :|
|
||||
PageOfResults = _searchService.Query(q, page, pageSize, CurrentSite.As<SearchSettings>().Record.FilterCulture, searchHit => new SearchResultViewModel {
|
||||
Content = _contentManager.BuildDisplayModel(_contentManager.Get(searchHit.ContentItemId), "SummaryForSearch"),
|
||||
SearchHit = searchHit
|
||||
})
|
||||
PageOfResults = _searchService.Query(q, page, pageSize,
|
||||
CurrentSite.As<SearchSettings>().Record.FilterCulture,
|
||||
searchFields,
|
||||
searchHit => new SearchResultViewModel {
|
||||
Content = _contentManager.BuildDisplayModel(_contentManager.Get(searchHit.ContentItemId), "SummaryForSearch"),
|
||||
SearchHit = searchHit
|
||||
})
|
||||
};
|
||||
|
||||
//todo: deal with page requests beyond result count
|
||||
|
@@ -3,9 +3,11 @@ using Orchard.ContentManagement.Records;
|
||||
namespace Orchard.Search.Models {
|
||||
public class SearchSettingsRecord : ContentPartRecord {
|
||||
public virtual bool FilterCulture { get; set; }
|
||||
|
||||
public virtual string SearchedFields { get; set; }
|
||||
|
||||
public SearchSettingsRecord() {
|
||||
FilterCulture = true;
|
||||
SearchedFields = "body, title";
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,7 +5,7 @@ using Orchard.Indexing;
|
||||
namespace Orchard.Search.Services {
|
||||
public interface ISearchService : IDependency {
|
||||
bool HasIndexToManage { get; }
|
||||
IPageOfItems<T> Query<T>(string query, int skip, int? take, bool filterCulture, Func<ISearchHit, T> shapeResult);
|
||||
IPageOfItems<T> Query<T>(string query, int skip, int? take, bool filterCulture, string[] searchFields, Func<ISearchHit, T> shapeResult);
|
||||
void RebuildIndex();
|
||||
void UpdateIndex();
|
||||
DateTime GetIndexUpdatedUtc();
|
||||
|
@@ -33,19 +33,20 @@ namespace Orchard.Search.Services
|
||||
get { return _indexManager.HasIndexProvider(); }
|
||||
}
|
||||
|
||||
IPageOfItems<T> ISearchService.Query<T>(string query, int page, int? pageSize, bool filterCulture, Func<ISearchHit, T> shapeResult) {
|
||||
IPageOfItems<T> ISearchService.Query<T>(string query, int page, int? pageSize, bool filterCulture, string[] searchFields, Func<ISearchHit, T> shapeResult) {
|
||||
if (string.IsNullOrWhiteSpace(query) || !_indexManager.HasIndexProvider())
|
||||
return null;
|
||||
|
||||
var searchBuilder = _indexManager.GetSearchIndexProvider().CreateSearchBuilder(SearchIndexName)
|
||||
.Parse(new[] {"title", "body"}, query);
|
||||
.Parse(searchFields, query);
|
||||
|
||||
if ( filterCulture ) {
|
||||
var culture = _cultureManager.GetSiteCulture();
|
||||
|
||||
// use LCID as the text representation gets analyzed by the query parser
|
||||
searchBuilder
|
||||
.WithField("culture", CultureInfo.GetCultureInfo(culture).LCID);
|
||||
.WithField("culture", CultureInfo.GetCultureInfo(culture).LCID)
|
||||
.AsFilter();
|
||||
}
|
||||
|
||||
var totalCount = searchBuilder.Count();
|
||||
|
@@ -7,4 +7,9 @@
|
||||
<label class="forcheckbox" for="SearchSettings_FilterCulture"><%: T("Search results must be filtered with current culture")%></label>
|
||||
<%: Html.ValidationMessage("FilterCulture", "*")%>
|
||||
</div>
|
||||
<div>
|
||||
<label for="SearchSettings_SearchedFields"><%: T("Searched fields")%></label>
|
||||
<%: Html.EditorFor(m => m.SearchedFields)%>
|
||||
<%: Html.ValidationMessage("SearchedFields", "*")%>
|
||||
</div>
|
||||
</fieldset>
|
Reference in New Issue
Block a user