diff --git a/src/Orchard.Core.Tests/Indexing/DefaultIndexProviderTests.cs b/src/Orchard.Core.Tests/Indexing/DefaultIndexProviderTests.cs
index 443f8dad6..f92333d91 100644
--- a/src/Orchard.Core.Tests/Indexing/DefaultIndexProviderTests.cs
+++ b/src/Orchard.Core.Tests/Indexing/DefaultIndexProviderTests.cs
@@ -58,7 +58,11 @@ namespace Orchard.Tests.Indexing {
[Test]
public void IndexProviderShouldOverwriteAlreadyExistingIndex() {
_provider.CreateIndex("default");
- _provider.CreateIndex("default");
+ _provider.Store("default", _provider.New(1).Add("body", null));
+ Assert.That(_provider.IsEmpty("default"), Is.False);
+
+ _provider.CreateIndex("default");
+ Assert.That(_provider.IsEmpty("default"), Is.True);
}
[Test]
@@ -181,5 +185,36 @@ namespace Orchard.Tests.Indexing {
Assert.That(searchBuilder.Get(2).Id, Is.EqualTo(2));
Assert.That(searchBuilder.Get(3).Id, Is.EqualTo(3));
}
+
+ [Test]
+ public void ProviderShouldStoreSettings() {
+ _provider.CreateIndex("default");
+ Assert.That(_provider.GetLastIndexUtc("default"), Is.EqualTo(DefaultIndexProvider.DefaultMinDateTime));
+
+ _provider.SetLastIndexUtc("default", new DateTime(2010, 1, 1, 1, 1, 1, 1));
+ Assert.That(_provider.GetLastIndexUtc("default"), Is.EqualTo(new DateTime(2010, 1, 1, 1, 1, 1, 0)));
+
+ _provider.SetLastIndexUtc("default", new DateTime(1901, 1, 1, 1, 1, 1, 1));
+ Assert.That(_provider.GetLastIndexUtc("default"), Is.EqualTo(DefaultIndexProvider.DefaultMinDateTime));
+ }
+
+ [Test]
+ public void IsEmptyShouldBeTrueForNoneExistingIndexes() {
+ _provider.IsEmpty("dummy");
+ Assert.That(_provider.IsEmpty("default"), Is.True);
+ }
+
+ [Test]
+ public void IsEmptyShouldBeTrueForJustNewIndexes() {
+ _provider.CreateIndex("default");
+ Assert.That(_provider.IsEmpty("default"), Is.True);
+ }
+
+ [Test]
+ public void IsEmptyShouldBeFalseWhenThereIsADocument() {
+ _provider.CreateIndex("default");
+ _provider.Store("default", _provider.New(1).Add("body", null));
+ Assert.That(_provider.IsEmpty("default"), Is.False);
+ }
}
}
diff --git a/src/Orchard.Core.Tests/Indexing/DefaultSearchBuilderTests.cs b/src/Orchard.Core.Tests/Indexing/DefaultSearchBuilderTests.cs
index 9ffa62d6c..b28036c57 100644
--- a/src/Orchard.Core.Tests/Indexing/DefaultSearchBuilderTests.cs
+++ b/src/Orchard.Core.Tests/Indexing/DefaultSearchBuilderTests.cs
@@ -177,5 +177,19 @@ namespace Orchard.Tests.Indexing {
Assert.That(date[0].GetDateTime("date") < date[1].GetDateTime("date"), Is.True);
Assert.That(date[1].GetDateTime("date") < date[2].GetDateTime("date"), Is.True);
}
+
+ [Test]
+ public void ShouldEscapeSpecialChars() {
+ _provider.CreateIndex("default");
+ _provider.Store("default", _provider.New(1).Add("body", "Orchard has been developped in C#"));
+ _provider.Store("default", _provider.New(2).Add("body", "Windows has been developped in C++"));
+
+ var cs = _searchBuilder.WithField("body", "C#").Search().ToList();
+ Assert.That(cs.Count(), Is.EqualTo(2));
+
+ var cpp = _searchBuilder.WithField("body", "C++").Search().ToList();
+ Assert.That(cpp.Count(), Is.EqualTo(2));
+
+ }
}
}
diff --git a/src/Orchard.Specs/Modules.feature b/src/Orchard.Specs/Modules.feature
index 31482a274..2509b5ddd 100644
--- a/src/Orchard.Specs/Modules.feature
+++ b/src/Orchard.Specs/Modules.feature
@@ -7,15 +7,9 @@ Scenario: Installed modules are listed
Given I have installed Orchard
When I go to "admin/modules"
Then I should see "
Installed Modules
"
- And I should see "
Themes
"
+ And I should see "
Themes"
And the status should be 200 OK
-Scenario: Edit module shows its features
- Given I have installed Orchard
- When I go to "admin/modules/Edit/Orchard.Themes"
- Then I should see "
Edit Module: Themes
"
- And the status should be 200 OK
-
Scenario: Features of installed modules are listed
Given I have installed Orchard
When I go to "admin/modules/features"
diff --git a/src/Orchard.Specs/MultiTenancy.feature b/src/Orchard.Specs/MultiTenancy.feature
index c8602abb4..b6cabfd97 100644
--- a/src/Orchard.Specs/MultiTenancy.feature
+++ b/src/Orchard.Specs/MultiTenancy.feature
@@ -7,7 +7,7 @@ Scenario: Default site is listed
Given I have installed Orchard
And I have installed "Orchard.MultiTenancy"
When I go to "Admin/MultiTenancy"
- Then I should see "List of Site's Tenants"
+ Then I should see "List of Site's Tenants"
And I should see "Default"
And the status should be 200 OK
diff --git a/src/Orchard.Web/Core/Common/Views/DisplayTemplates/Parts/Common.Body.ManageWrapperPost.ascx b/src/Orchard.Web/Core/Common/Views/DisplayTemplates/Parts/Common.Body.ManageWrapperPost.ascx
index 18093dde3..86cbb66fa 100644
--- a/src/Orchard.Web/Core/Common/Views/DisplayTemplates/Parts/Common.Body.ManageWrapperPost.ascx
+++ b/src/Orchard.Web/Core/Common/Views/DisplayTemplates/Parts/Common.Body.ManageWrapperPost.ascx
@@ -1,2 +1,7 @@
<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl" %>
<%@ Import Namespace="Orchard.Core.Common.ViewModels"%>
+<%-- begin: knowingly broken HTML (hence the ManageWrapperPre and ManageWrapperPost templates)
+we need "wrapper templates" (among other functionality) in the future of UI composition
+please do not delete or the front end will be broken when the user is authenticated. --%>
+
+<%-- begin: knowingly broken HTML --%>
\ No newline at end of file
diff --git a/src/Orchard.Web/Core/Indexing/Commands/IndexingCommands.cs b/src/Orchard.Web/Core/Indexing/Commands/IndexingCommands.cs
new file mode 100644
index 000000000..2cb83c3e6
--- /dev/null
+++ b/src/Orchard.Web/Core/Indexing/Commands/IndexingCommands.cs
@@ -0,0 +1,142 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Orchard.Commands;
+using Orchard.ContentManagement;
+using Orchard.Indexing;
+using Orchard.Security;
+using Orchard.Tasks.Indexing;
+
+namespace Orchard.Core.Indexing.Commands {
+ public class IndexingCommands : DefaultOrchardCommandHandler {
+ private readonly IEnumerable _indexNotifierHandlers;
+ private readonly IIndexManager _indexManager;
+ private readonly IIndexingTaskManager _indexingTaskManager;
+ private readonly IContentManager _contentManager;
+ private const string SearchIndexName = "Search";
+
+ public IndexingCommands(
+ IEnumerable indexNotifierHandlers,
+ IIndexManager indexManager,
+ IIndexingTaskManager indexingTaskManager,
+ IContentManager contentManager) {
+ _indexNotifierHandlers = indexNotifierHandlers;
+ _indexingTaskManager = indexingTaskManager;
+ _contentManager = contentManager;
+ _indexManager = indexManager;
+ }
+
+ [OrchardSwitch]
+ public string IndexName { get; set; }
+
+ [OrchardSwitch]
+ public string Query { get; set; }
+
+ [OrchardSwitch]
+ public string ContentItemId { get; set; }
+
+ [CommandName("index update")]
+ [CommandHelp("index update [/IndexName:]\r\n\t" + "Updates the index with the specified , or the search index if not specified")]
+ [OrchardSwitches("IndexName")]
+ public string Update() {
+ if ( !_indexManager.HasIndexProvider() ) {
+ return "No index available";
+ }
+
+ var indexName = String.IsNullOrWhiteSpace(IndexName) ? SearchIndexName : IndexName;
+ foreach ( var handler in _indexNotifierHandlers ) {
+ handler.UpdateIndex(indexName);
+ }
+
+ return "Index is now being updated...";
+ }
+
+ [CommandName("index rebuild")]
+ [CommandHelp("index rebuild [/IndexName:]\r\n\t" + "Rebuilds the index with the specified , or the search index if not specified")]
+ [OrchardSwitches("IndexName")]
+ public string Rebuild() {
+ if ( !_indexManager.HasIndexProvider() ) {
+ return "No index available";
+ }
+
+ var indexName = String.IsNullOrWhiteSpace(IndexName) ? SearchIndexName : IndexName;
+ var searchProvider = _indexManager.GetSearchIndexProvider();
+ if ( searchProvider.Exists(indexName) )
+ searchProvider.DeleteIndex(indexName);
+
+ searchProvider.CreateIndex(indexName);
+ return "Index is now being rebuilt...";
+ }
+
+ [CommandName("index search")]
+ [CommandHelp("index search /Query: [/IndexName:]\r\n\t" + "Searches the specified terms in the index with the specified , or in the search index if not specified")]
+ [OrchardSwitches("Query,IndexName")]
+ public string Search() {
+ if ( !_indexManager.HasIndexProvider() ) {
+ return "No index available";
+ }
+ var indexName = String.IsNullOrWhiteSpace(IndexName) ? SearchIndexName : IndexName;
+ var searchBuilder = _indexManager.GetSearchIndexProvider().CreateSearchBuilder(indexName);
+ var results = searchBuilder.WithField("body", Query).WithField("title", Query).Search();
+
+ Context.Output.WriteLine("{0} result{1}\r\n-----------------\r\n", results.Count(), results.Count() > 0 ? "s" : "");
+
+ Context.Output.WriteLine("┌──────────────────────────────────────────────────────────────┬────────┐");
+ Context.Output.WriteLine("│ {0} │ {1,6} │", "Title" + new string(' ', 60 - "Title".Length), "Score");
+ Context.Output.WriteLine("├──────────────────────────────────────────────────────────────┼────────┤");
+ foreach ( var searchHit in results ) {
+ var title = searchHit.GetString("title");
+ title = title.Substring(0, Math.Min(60, title.Length)) ?? "- no title -";
+ var score = searchHit.Score;
+ Context.Output.WriteLine("│ {0} │ {1,6} │", title + new string(' ', 60 - title.Length), score);
+ }
+ Context.Output.WriteLine("└──────────────────────────────────────────────────────────────┴────────┘");
+
+ Context.Output.WriteLine();
+ return "End of search results";
+ }
+
+ [CommandName("index stats")]
+ [CommandHelp("index stats [/IndexName:]\r\n\t" + "Displays some statistics about the index with the specified , or in the search index if not specified")]
+ [OrchardSwitches("IndexName")]
+ public string Stats() {
+ if ( !_indexManager.HasIndexProvider() ) {
+ return "No index available";
+ }
+ var indexName = String.IsNullOrWhiteSpace(IndexName) ? SearchIndexName : IndexName;
+ Context.Output.WriteLine("Number of indexed documents: {0}", _indexManager.GetSearchIndexProvider().NumDocs(indexName));
+ return "";
+ }
+
+ [CommandName("index refresh")]
+ [CommandHelp("index refresh /ContenItem: \r\n\t" + "Refreshes the index for the specifed ")]
+ [OrchardSwitches("ContentItem")]
+ public string Refresh() {
+ int contenItemId;
+ if ( !int.TryParse(ContentItemId, out contenItemId) ) {
+ return "Invalid content item id. Not an integer.";
+ }
+
+ var contentItem = _contentManager.Get(contenItemId);
+ _indexingTaskManager.CreateUpdateIndexTask(contentItem);
+
+ return "Content Item marked for reindexing";
+ }
+
+ [CommandName("index delete")]
+ [CommandHelp("index delete /ContenItem:\r\n\t" + "Deletes the specifed fromthe index")]
+ [OrchardSwitches("ContentItem")]
+ public string Delete() {
+ int contenItemId;
+ if(!int.TryParse(ContentItemId, out contenItemId)) {
+ return "Invalid content item id. Not an integer.";
+ }
+
+ var contentItem = _contentManager.Get(contenItemId);
+ _indexingTaskManager.CreateDeleteIndexTask(contentItem);
+
+ return "Content Item marked for deletion";
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Core/Indexing/Lucene/DefaultIndexProvider.cs b/src/Orchard.Web/Core/Indexing/Lucene/DefaultIndexProvider.cs
index 9a5e01174..8132e1a54 100644
--- a/src/Orchard.Web/Core/Indexing/Lucene/DefaultIndexProvider.cs
+++ b/src/Orchard.Web/Core/Indexing/Lucene/DefaultIndexProvider.cs
@@ -13,6 +13,7 @@ using Orchard.Indexing;
using Directory = Lucene.Net.Store.Directory;
using Version = Lucene.Net.Util.Version;
using Orchard.Logging;
+using System.Xml.Linq;
namespace Orchard.Core.Indexing.Lucene {
///
@@ -22,14 +23,18 @@ namespace Orchard.Core.Indexing.Lucene {
private readonly IAppDataFolder _appDataFolder;
private readonly ShellSettings _shellSettings;
public static readonly Version LuceneVersion = Version.LUCENE_29;
- private readonly Analyzer _analyzer = new StandardAnalyzer(LuceneVersion);
+ private readonly Analyzer _analyzer ;
private readonly string _basePath;
+ public static readonly DateTime DefaultMinDateTime = new DateTime(1980, 1, 1);
+ public static readonly string Settings = "Settings";
+ public static readonly string LastIndexUtc = "LastIndexedUtc";
public ILogger Logger { get; set; }
public DefaultIndexProvider(IAppDataFolder appDataFolder, ShellSettings shellSettings) {
_appDataFolder = appDataFolder;
_shellSettings = shellSettings;
+ _analyzer = CreateAnalyzer();
// TODO: (sebros) Find a common way to get where tenant's specific files should go. "Sites/Tenant" is hard coded in multiple places
_basePath = Path.Combine("Sites", _shellSettings.Name, "Indexes");
@@ -37,6 +42,15 @@ namespace Orchard.Core.Indexing.Lucene {
Logger = NullLogger.Instance;
// Ensures the directory exists
+ EnsureDirectoryExists();
+ }
+
+ public static Analyzer CreateAnalyzer() {
+ // StandardAnalyzer does lower-case and stop-word filtering. It also removes punctuation
+ return new StandardAnalyzer(LuceneVersion);
+ }
+
+ private void EnsureDirectoryExists() {
var directory = new DirectoryInfo(_appDataFolder.MapPath(_basePath));
if(!directory.Exists) {
directory.Create();
@@ -62,6 +76,36 @@ namespace Orchard.Core.Indexing.Lucene {
return new DirectoryInfo(_appDataFolder.MapPath(Path.Combine(_basePath, indexName))).Exists;
}
+ public bool IsEmpty(string indexName) {
+ if ( !Exists(indexName) ) {
+ return true;
+ }
+
+ var reader = IndexReader.Open(GetDirectory(indexName), true);
+
+ try {
+ return reader.NumDocs() == 0;
+ }
+ finally {
+ reader.Close();
+ }
+ }
+
+ public int NumDocs(string indexName) {
+ if ( !Exists(indexName) ) {
+ return 0;
+ }
+
+ var reader = IndexReader.Open(GetDirectory(indexName), true);
+
+ try {
+ return reader.NumDocs();
+ }
+ finally {
+ reader.Close();
+ }
+ }
+
public void CreateIndex(string indexName) {
var writer = new IndexWriter(GetDirectory(indexName), _analyzer, true, IndexWriter.MaxFieldLength.UNLIMITED);
writer.Close();
@@ -72,6 +116,11 @@ namespace Orchard.Core.Indexing.Lucene {
public void DeleteIndex(string indexName) {
new DirectoryInfo(Path.Combine(_appDataFolder.MapPath(Path.Combine(_basePath, indexName))))
.Delete(true);
+
+ var settingsFileName = GetSettingsFileName(indexName);
+ if(File.Exists(settingsFileName)) {
+ File.Delete(settingsFileName);
+ }
}
public void Store(string indexName, IIndexDocument indexDocument) {
@@ -105,7 +154,6 @@ namespace Orchard.Core.Indexing.Lucene {
writer.Optimize();
writer.Close();
}
-
}
public void Delete(string indexName, int documentId) {
@@ -148,5 +196,38 @@ namespace Orchard.Core.Indexing.Lucene {
return new DefaultSearchBuilder(GetDirectory(indexName));
}
+ private string GetSettingsFileName(string indexName) {
+ return Path.Combine(_appDataFolder.MapPath(_basePath), indexName + ".settings.xml");
+ }
+
+ public DateTime GetLastIndexUtc(string indexName) {
+ var settingsFileName = GetSettingsFileName(indexName);
+
+ return File.Exists(settingsFileName)
+ ? DateTime.Parse(XDocument.Load(settingsFileName).Descendants(LastIndexUtc).First().Value)
+ : DefaultMinDateTime;
+ }
+
+ public void SetLastIndexUtc(string indexName, DateTime lastIndexUtc) {
+ if ( lastIndexUtc < DefaultMinDateTime ) {
+ lastIndexUtc = DefaultMinDateTime;
+ }
+
+ XDocument doc;
+ var settingsFileName = GetSettingsFileName(indexName);
+ if ( !File.Exists(settingsFileName) ) {
+ EnsureDirectoryExists();
+ doc = new XDocument(
+ new XElement(Settings,
+ new XElement(LastIndexUtc, lastIndexUtc.ToString("s"))));
+ }
+ else {
+ doc = XDocument.Load(settingsFileName);
+ doc.Element(Settings).Element(LastIndexUtc).Value = lastIndexUtc.ToString("s");
+ }
+
+ doc.Save(settingsFileName);
+ }
+
}
}
diff --git a/src/Orchard.Web/Core/Indexing/Lucene/DefaultSearchBuilder.cs b/src/Orchard.Web/Core/Indexing/Lucene/DefaultSearchBuilder.cs
index c73192c52..93a084672 100644
--- a/src/Orchard.Web/Core/Indexing/Lucene/DefaultSearchBuilder.cs
+++ b/src/Orchard.Web/Core/Indexing/Lucene/DefaultSearchBuilder.cs
@@ -2,12 +2,15 @@
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;
using Orchard.Logging;
using Lucene.Net.Documents;
using Orchard.Indexing;
+using Lucene.Net.QueryParsers;
namespace Orchard.Core.Indexing.Lucene {
public class DefaultSearchBuilder : ISearchBuilder {
@@ -23,6 +26,9 @@ namespace Orchard.Core.Indexing.Lucene {
private readonly Dictionary _after;
private string _sort;
private bool _sortDescending;
+ private string _parse;
+ private readonly Analyzer _analyzer;
+ private string _defaultField;
public ILogger Logger { get; set; }
@@ -37,9 +43,21 @@ namespace Orchard.Core.Indexing.Lucene {
_fields = new Dictionary();
_sort = String.Empty;
_sortDescending = true;
+ _parse = String.Empty;
+ _analyzer = DefaultIndexProvider.CreateAnalyzer();
}
- public ISearchBuilder Parse(string query) {
+ public ISearchBuilder Parse(string defaultField, string query) {
+ if ( String.IsNullOrWhiteSpace(defaultField) ) {
+ throw new ArgumentException("Default field can't be empty");
+ }
+
+ if ( String.IsNullOrWhiteSpace(query) ) {
+ throw new ArgumentException("Query can't be empty");
+ }
+
+ _defaultField = defaultField;
+ _parse = query;
return this;
}
@@ -49,8 +67,17 @@ namespace Orchard.Core.Indexing.Lucene {
public ISearchBuilder WithField(string field, string value, bool wildcardSearch) {
- _fields[field] = value.Split(' ')
+ var tokens = new List();
+ using(var sr = new System.IO.StringReader(value)) {
+ var stream = _analyzer.TokenStream(field, sr);
+ while(stream.IncrementToken()) {
+ tokens.Add(((TermAttribute)stream.GetAttribute(typeof(TermAttribute))).Term());
+ }
+ }
+
+ _fields[field] = tokens
.Where(k => !String.IsNullOrWhiteSpace(k))
+ .Select(QueryParser.Escape)
.Select(k => wildcardSearch ? (Query)new PrefixQuery(new Term(field, k)) : new TermQuery(new Term(k)))
.ToArray();
@@ -93,6 +120,10 @@ namespace Orchard.Core.Indexing.Lucene {
}
private Query CreateQuery() {
+ if(!String.IsNullOrWhiteSpace(_parse)) {
+ return new QueryParser(DefaultIndexProvider.LuceneVersion, _defaultField, DefaultIndexProvider.CreateAnalyzer()).Parse(_parse);
+ }
+
var query = new BooleanQuery();
if ( _fields.Keys.Count > 0 ) { // apply specific filters if defined
diff --git a/src/Orchard.Web/Core/Indexing/Models/IndexingSettingsRecord.cs b/src/Orchard.Web/Core/Indexing/Models/IndexingSettingsRecord.cs
deleted file mode 100644
index 28cd06b9d..000000000
--- a/src/Orchard.Web/Core/Indexing/Models/IndexingSettingsRecord.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using System;
-
-namespace Orchard.Core.Indexing.Models {
- public class IndexingSettingsRecord {
- public virtual int Id { get; set; }
- public virtual DateTime? LatestIndexingUtc { get; set; }
- }
-}
\ No newline at end of file
diff --git a/src/Orchard.Web/Core/Indexing/Services/IndexingTaskExecutor.cs b/src/Orchard.Web/Core/Indexing/Services/IndexingTaskExecutor.cs
index 1b4d53035..baa9b1b8d 100644
--- a/src/Orchard.Web/Core/Indexing/Services/IndexingTaskExecutor.cs
+++ b/src/Orchard.Web/Core/Indexing/Services/IndexingTaskExecutor.cs
@@ -10,134 +10,165 @@ using Orchard.Logging;
using Orchard.Services;
using Orchard.Tasks;
using Orchard.Core.Indexing.Models;
+using Orchard.Tasks.Indexing;
+using Orchard.Indexing;
namespace Orchard.Core.Indexing.Services {
///
/// Contains the logic which is regularly executed to retrieve index information from multiple content handlers.
///
[UsedImplicitly]
- public class IndexingTaskExecutor : IBackgroundTask {
+ public class IndexingTaskExecutor : IBackgroundTask, IIndexNotifierHandler {
private readonly IClock _clock;
private readonly IRepository _repository;
- private readonly IRepository _settings;
private readonly IEnumerable _handlers;
private IIndexProvider _indexProvider;
private readonly IIndexManager _indexManager;
+ private readonly IIndexingTaskManager _indexingTaskManager;
private readonly IContentManager _contentManager;
private const string SearchIndexName = "Search";
+
+ private readonly object _synLock = new object();
public IndexingTaskExecutor(
IClock clock,
IRepository repository,
- IRepository settings,
IEnumerable handlers,
IIndexManager indexManager,
+ IIndexingTaskManager indexingTaskManager,
IContentManager contentManager) {
_clock = clock;
_repository = repository;
- _settings = settings;
_indexManager = indexManager;
_handlers = handlers;
+ _indexingTaskManager = indexingTaskManager;
_contentManager = contentManager;
Logger = NullLogger.Instance;
}
public ILogger Logger { get; set; }
+ public void UpdateIndex(string indexName) {
+ if (indexName == SearchIndexName) {
+ Sweep();
+ }
+ }
+
public void Sweep() {
- if ( !_indexManager.HasIndexProvider() ) {
+ if ( !System.Threading.Monitor.TryEnter(_synLock) ) {
+ Logger.Information("Index was requested but was already running");
return;
}
- _indexProvider = _indexManager.GetSearchIndexProvider();
-
- // retrieve last processed index time
- var settingsRecord = _settings.Table.FirstOrDefault();
-
- if ( settingsRecord == null ) {
- _settings.Create(settingsRecord = new IndexingSettingsRecord { LatestIndexingUtc = new DateTime(1980, 1, 1) });
- }
-
- var lastIndexing = settingsRecord.LatestIndexingUtc;
- settingsRecord.LatestIndexingUtc = _clock.UtcNow;
-
- // retrieved not yet processed tasks
- var taskRecords = _repository.Fetch(x => x.CreatedUtc >= lastIndexing)
- .ToArray();
-
- if ( taskRecords.Length == 0 )
- return;
-
- Logger.Information("Processing {0} indexing tasks", taskRecords.Length);
-
-
- if ( !_indexProvider.Exists(SearchIndexName) ) {
- _indexProvider.CreateIndex(SearchIndexName);
- }
-
- var updateIndexDocuments = new List();
- var deleteIndexDocuments = new List();
-
- // process Delete tasks
- foreach ( var taskRecord in taskRecords.Where(t => t.Action == IndexingTaskRecord.Delete) ) {
- var task = new IndexingTask(_contentManager, taskRecord);
- deleteIndexDocuments.Add(taskRecord.ContentItemRecord.Id);
-
- try {
- _repository.Delete(taskRecord);
- }
- catch ( Exception ex ) {
- Logger.Error(ex, "Could not delete task #{0}", taskRecord.Id);
- }
- }
-
-
try {
- if ( deleteIndexDocuments.Count > 0 ) {
- _indexProvider.Delete(SearchIndexName, deleteIndexDocuments);
+
+ if (!_indexManager.HasIndexProvider()) {
+ return;
}
- }
- catch ( Exception ex ) {
- Logger.Warning(ex, "An error occured while remove a document from the index");
- }
- // process Update tasks
- foreach ( var taskRecord in taskRecords.Where(t => t.Action == IndexingTaskRecord.Update) ) {
- var task = new IndexingTask(_contentManager, taskRecord);
+ _indexProvider = _indexManager.GetSearchIndexProvider();
+ var updateIndexDocuments = new List();
+ var lastIndexing = DateTime.UtcNow;
- try {
- var context = new IndexContentContext {
- ContentItem = task.ContentItem,
- IndexDocument = _indexProvider.New(task.ContentItem.Id)
- };
+ // Do we need to rebuild the full index (first time module is used, or rebuild index requested) ?
+ if (_indexProvider.IsEmpty(SearchIndexName)) {
+ Logger.Information("Rebuild index started");
- // dispatch to handlers to retrieve index information
- foreach ( var handler in _handlers ) {
- handler.Indexing(context);
+ // mark current last task, as we should process older ones (in case of rebuild index only)
+ lastIndexing = _indexingTaskManager.GetLastTaskDateTime();
+
+ // get every existing content item to index it
+ foreach (var contentItem in _contentManager.Query(VersionOptions.Published).List()) {
+ try {
+ var context = new IndexContentContext {
+ ContentItem = contentItem,
+ IndexDocument = _indexProvider.New(contentItem.Id)
+ };
+
+ // dispatch to handlers to retrieve index information
+ foreach (var handler in _handlers) {
+ handler.Indexing(context);
+ }
+
+ updateIndexDocuments.Add(context.IndexDocument);
+
+ foreach (var handler in _handlers) {
+ handler.Indexed(context);
+ }
+ }
+ catch (Exception ex) {
+ Logger.Warning(ex, "Unable to index content item #{0} during rebuild", contentItem.Id);
+ }
}
- updateIndexDocuments.Add(context.IndexDocument);
+ }
+ else {
+ // retrieve last processed index time
+ lastIndexing = _indexProvider.GetLastIndexUtc(SearchIndexName);
+ }
- foreach ( var handler in _handlers ) {
- handler.Indexed(context);
+ _indexProvider.SetLastIndexUtc(SearchIndexName, _clock.UtcNow);
+
+ // retrieve not yet processed tasks
+ var taskRecords = _repository.Fetch(x => x.CreatedUtc >= lastIndexing)
+ .ToArray();
+
+ if (taskRecords.Length == 0)
+ return;
+
+ Logger.Information("Processing {0} indexing tasks", taskRecords.Length);
+
+ if (!_indexProvider.Exists(SearchIndexName)) {
+ _indexProvider.CreateIndex(SearchIndexName);
+ }
+
+ // process Delete tasks
+ try {
+ _indexProvider.Delete(SearchIndexName, taskRecords.Where(t => t.Action == IndexingTaskRecord.Delete).Select(t => t.Id));
+ }
+ catch (Exception ex) {
+ Logger.Warning(ex, "An error occured while removing a document from the index");
+ }
+
+ // process Update tasks
+ foreach (var taskRecord in taskRecords.Where(t => t.Action == IndexingTaskRecord.Update)) {
+ var task = new IndexingTask(_contentManager, taskRecord);
+
+ try {
+ var context = new IndexContentContext {
+ ContentItem = task.ContentItem,
+ IndexDocument = _indexProvider.New(task.ContentItem.Id)
+ };
+
+ // dispatch to handlers to retrieve index information
+ foreach (var handler in _handlers) {
+ handler.Indexing(context);
+ }
+
+ updateIndexDocuments.Add(context.IndexDocument);
+
+ foreach (var handler in _handlers) {
+ handler.Indexed(context);
+ }
+ }
+ catch (Exception ex) {
+ Logger.Warning(ex, "Unable to process indexing task #{0}", taskRecord.Id);
}
}
- catch ( Exception ex ) {
- Logger.Warning(ex, "Unable to process indexing task #{0}", taskRecord.Id);
+
+ if (updateIndexDocuments.Count > 0) {
+ try {
+ _indexProvider.Store(SearchIndexName, updateIndexDocuments);
+ }
+ catch (Exception ex) {
+ Logger.Warning(ex, "An error occured while adding a document to the index");
+ }
}
}
-
- try {
- if ( updateIndexDocuments.Count > 0 ) {
- _indexProvider.Store(SearchIndexName, updateIndexDocuments);
- }
+ finally {
+ System.Threading.Monitor.Exit(_synLock);
}
- catch ( Exception ex ) {
- Logger.Warning(ex, "An error occured while adding a document to the index");
- }
-
- _settings.Update(settingsRecord);
}
}
}
diff --git a/src/Orchard.Web/Core/Indexing/Services/IndexingTaskManager.cs b/src/Orchard.Web/Core/Indexing/Services/IndexingTaskManager.cs
index 1dde1d257..9969920c3 100644
--- a/src/Orchard.Web/Core/Indexing/Services/IndexingTaskManager.cs
+++ b/src/Orchard.Web/Core/Indexing/Services/IndexingTaskManager.cs
@@ -16,18 +16,15 @@ namespace Orchard.Core.Indexing.Services {
public class IndexingTaskManager : IIndexingTaskManager {
private readonly IContentManager _contentManager;
private readonly IRepository _repository;
- private readonly IRepository _settings;
private readonly IClock _clock;
public IndexingTaskManager(
IContentManager contentManager,
IRepository repository,
- IRepository settings,
IClock clock) {
_clock = clock;
_repository = repository;
_contentManager = contentManager;
- _settings = settings;
Logger = NullLogger.Instance;
}
@@ -38,14 +35,7 @@ namespace Orchard.Core.Indexing.Services {
throw new ArgumentNullException("contentItem");
}
- // remove previous tasks for the same content item
- var tasks = _repository
- .Fetch(x => x.ContentItemRecord.Id == contentItem.Id)
- .ToArray();
-
- foreach ( var task in tasks ) {
- _repository.Delete(task);
- }
+ DeleteTasks(contentItem);
var taskRecord = new IndexingTaskRecord {
CreatedUtc = _clock.UtcNow,
@@ -69,45 +59,20 @@ namespace Orchard.Core.Indexing.Services {
Logger.Information("Deleting index task created for [{0}:{1}]", contentItem.ContentType, contentItem.Id);
}
- public IEnumerable GetTasks(DateTime? createdAfter) {
- return _repository
- .Fetch(x => x.CreatedUtc > createdAfter)
- .Select(x => new IndexingTask(_contentManager, x))
- .Cast()
- .ToReadOnlyCollection();
- }
-
- public void DeleteTasks(DateTime? createdBefore) {
- Logger.Debug("Deleting Indexing tasks created before {0}", createdBefore);
-
- var tasks = _repository
- .Fetch(x => x.CreatedUtc <= createdBefore);
-
- foreach (var task in tasks) {
- _repository.Delete(task);
- }
+ public DateTime GetLastTaskDateTime() {
+ return _repository.Table.Max(t => t.CreatedUtc) ?? DateTime.MinValue;
}
+ ///
+ /// Removes existing tasks for the specified content item
+ ///
public void DeleteTasks(ContentItem contentItem) {
- Logger.Debug("Deleting Indexing tasks for ContentItem [{0}:{1}]", contentItem.ContentType, contentItem.Id);
-
var tasks = _repository
- .Fetch(x => x.Id == contentItem.Id);
-
+ .Fetch(x => x.ContentItemRecord.Id == contentItem.Id)
+ .ToArray();
foreach (var task in tasks) {
_repository.Delete(task);
}
}
-
- public void RebuildIndex() {
- var settingsRecord = _settings.Table.FirstOrDefault();
- if (settingsRecord == null) {
- _settings.Create(settingsRecord = new IndexingSettingsRecord() );
- }
-
- settingsRecord.LatestIndexingUtc = new DateTime(1980, 1, 1);
- _settings.Update(settingsRecord);
- }
-
}
}
diff --git a/src/Orchard.Web/Core/Localization/Models/Localized.cs b/src/Orchard.Web/Core/Localization/Models/Localized.cs
new file mode 100644
index 000000000..897b5bc99
--- /dev/null
+++ b/src/Orchard.Web/Core/Localization/Models/Localized.cs
@@ -0,0 +1,20 @@
+using System.Web.Mvc;
+using Orchard.ContentManagement;
+
+namespace Orchard.Core.Localization.Models {
+ public sealed class Localized : ContentPart {
+ [HiddenInput(DisplayValue = false)]
+ public int Id { get { return ContentItem.Id; } }
+
+ public int CultureId {
+ get { return Record.CultureId; }
+ set { Record.CultureId = value; }
+ }
+
+ public int MasterContentItemId {
+ get { return Record.MasterContentItemId; }
+ set { Record.MasterContentItemId = value; }
+ }
+
+ }
+}
diff --git a/src/Orchard.Web/Core/Localization/Models/LocalizedRecord.cs b/src/Orchard.Web/Core/Localization/Models/LocalizedRecord.cs
new file mode 100644
index 000000000..cab717d5f
--- /dev/null
+++ b/src/Orchard.Web/Core/Localization/Models/LocalizedRecord.cs
@@ -0,0 +1,8 @@
+using Orchard.ContentManagement.Records;
+
+namespace Orchard.Core.Localization.Models {
+ public class LocalizedRecord : ContentPartRecord {
+ public virtual int CultureId { get; set; }
+ public virtual int MasterContentItemId { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Core/Localization/Module.txt b/src/Orchard.Web/Core/Localization/Module.txt
new file mode 100644
index 000000000..6cb082dda
--- /dev/null
+++ b/src/Orchard.Web/Core/Localization/Module.txt
@@ -0,0 +1,11 @@
+name: Localization
+antiforgery: enabled
+author: The Orchard Team
+website: http://orchardproject.net
+version: 0.1
+orchardversion: 0.1.2010.0312
+description: Support for localizing content items for cultures.
+features:
+ Localization:
+ Description: Localize content items.
+ Category: Core
\ No newline at end of file
diff --git a/src/Orchard.Web/Core/Localization/Services/ContentItemLocalizationService.cs b/src/Orchard.Web/Core/Localization/Services/ContentItemLocalizationService.cs
new file mode 100644
index 000000000..e89f0820e
--- /dev/null
+++ b/src/Orchard.Web/Core/Localization/Services/ContentItemLocalizationService.cs
@@ -0,0 +1,40 @@
+using System.Collections.Generic;
+using System.Linq;
+using JetBrains.Annotations;
+using Orchard.ContentManagement;
+using Orchard.Core.Localization.Models;
+using Orchard.Localization.Services;
+using Localized = Orchard.Core.Localization.Models.Localized;
+
+namespace Orchard.Core.Localization.Services {
+ [UsedImplicitly]
+ public class ContentItemLocalizationService : IContentItemLocalizationService {
+ private readonly IContentManager _contentManager;
+ private readonly ICultureManager _cultureManager;
+
+ public ContentItemLocalizationService(IContentManager contentManager, ICultureManager cultureManager) {
+ _contentManager = contentManager;
+ _cultureManager = cultureManager;
+ }
+
+ public IEnumerable Get() {
+ return _contentManager.Query().List();
+ }
+
+ public Localized Get(int localizedId) {
+ return _contentManager.Get(localizedId);
+ }
+
+ public Localized GetLocalizationForCulture(int masterId, string cultureName) {
+ var cultures = _cultureManager.ListCultures();
+ if (cultures.Contains(cultureName)) {
+ int cultureId = _cultureManager.GetCultureIdByName(cultureName);
+ if (cultureId != 0) {
+ return _contentManager.Query()
+ .Where(x => x.MasterContentItemId == masterId && x.CultureId == cultureId).List().FirstOrDefault();
+ }
+ }
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Core/Localization/Services/IContentItemLocalizationService.cs b/src/Orchard.Web/Core/Localization/Services/IContentItemLocalizationService.cs
new file mode 100644
index 000000000..87a5eb993
--- /dev/null
+++ b/src/Orchard.Web/Core/Localization/Services/IContentItemLocalizationService.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+using Orchard.Core.Localization.Models;
+
+namespace Orchard.Core.Localization.Services {
+ public interface IContentItemLocalizationService : IDependency {
+ IEnumerable Get();
+ Localized Get(int localizedId);
+ Localized GetLocalizationForCulture(int masterId, string cultureName);
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Core/Orchard.Core.csproj b/src/Orchard.Web/Core/Orchard.Core.csproj
index 79cf03728..d593473a2 100644
--- a/src/Orchard.Web/Core/Orchard.Core.csproj
+++ b/src/Orchard.Web/Core/Orchard.Core.csproj
@@ -115,16 +115,20 @@
+
-
+
+
+
+
@@ -193,6 +197,7 @@
+
diff --git a/src/Orchard.Web/Modules/Orchard.Search/AdminMenu.cs b/src/Orchard.Web/Modules/Orchard.Search/AdminMenu.cs
new file mode 100644
index 000000000..369fcea1c
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Search/AdminMenu.cs
@@ -0,0 +1,16 @@
+using Orchard.Localization;
+using Orchard.UI.Navigation;
+
+namespace Orchard.Search {
+ public class AdminMenu : INavigationProvider {
+ public Localizer T { get; set; }
+ public string MenuName { get { return "admin"; } }
+
+ public void GetNavigation(NavigationBuilder builder) {
+ builder.Add(T("Site"), "11",
+ menu => menu
+ .Add(T("Search Index"), "10.0", item => item.Action("Index", "Admin", new {area = "Orchard.Search"})
+ .Permission(Permissions.ManageSearchIndex)));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Search/Controllers/AdminController.cs b/src/Orchard.Web/Modules/Orchard.Search/Controllers/AdminController.cs
new file mode 100644
index 000000000..217e1db53
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Search/Controllers/AdminController.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Web.Mvc;
+using Orchard.Localization;
+using Orchard.Search.Services;
+using Orchard.Search.ViewModels;
+using Orchard.UI.Notify;
+
+namespace Orchard.Search.Controllers {
+ public class AdminController : Controller {
+ private readonly ISearchService _searchService;
+
+ public AdminController(ISearchService searchService, IOrchardServices services) {
+ _searchService = searchService;
+ Services = services;
+ T = NullLocalizer.Instance;
+ }
+
+ public IOrchardServices Services { get; private set; }
+ public Localizer T { get; set; }
+
+ public ActionResult Index() {
+ var viewModel = new SearchIndexViewModel {HasIndexToManage = _searchService.HasIndexToManage, IndexUpdatedUtc = _searchService.GetIndexUpdatedUtc()};
+
+ if (!viewModel.HasIndexToManage)
+ Services.Notifier.Information(T("There is not search index to manage for this site."));
+
+ return View(viewModel);
+ }
+
+ [HttpPost]
+ public ActionResult Update() {
+ if (!Services.Authorizer.Authorize(Permissions.ManageSearchIndex, T("Not allowed to manage the search index.")))
+ return new HttpUnauthorizedResult();
+
+ _searchService.UpdateIndex();
+
+ return RedirectToAction("Index");
+ }
+
+ [HttpPost]
+ public ActionResult Rebuild() {
+ if (!Services.Authorizer.Authorize(Permissions.ManageSearchIndex, T("Not allowed to manage the search index.")))
+ return new HttpUnauthorizedResult();
+
+ _searchService.RebuildIndex();
+
+ return RedirectToAction("Index");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Search/Controllers/SearchController.cs b/src/Orchard.Web/Modules/Orchard.Search/Controllers/SearchController.cs
index 8e4e4e45a..ba7f8f63d 100644
--- a/src/Orchard.Web/Modules/Orchard.Search/Controllers/SearchController.cs
+++ b/src/Orchard.Web/Modules/Orchard.Search/Controllers/SearchController.cs
@@ -1,4 +1,5 @@
-using System.Linq;
+using System;
+using System.Linq;
using System.Web.Mvc;
using Orchard.ContentManagement;
using Orchard.Search.Services;
@@ -14,14 +15,17 @@ namespace Orchard.Search.Controllers {
_contentManager = contentManager;
}
- public ActionResult Index(string q) {
- var searchViewModel = new SearchViewModel {Query = q};
+ public ActionResult Index(string q, int page = 1, int pageSize = 10) {
+ var searchViewModel = new SearchViewModel {
+ Query = q,
+ DefaultPageSize = 10, // <- yeah, I know :|
+ PageOfResults = _searchService.Query(q, page, pageSize, searchHit => new SearchResultViewModel {
+ Content = _contentManager.BuildDisplayModel(_contentManager.Get(searchHit.Id), "SummaryForSearch"),
+ SearchHit = searchHit
+ })
+ };
- var results = _searchService.Query(q);
- searchViewModel.Results = results.Select(result => new SearchResultViewModel {
- Content = _contentManager.BuildDisplayModel(_contentManager.Get(result.Id), "SummaryForSearch"),
- SearchHit = result
- }).ToList();
+ //todo: deal with page requests beyond result count
return View(searchViewModel);
}
diff --git a/src/Orchard.Web/Modules/Orchard.Search/Models/ISearchResult.cs b/src/Orchard.Web/Modules/Orchard.Search/Models/ISearchResult.cs
new file mode 100644
index 000000000..73ecec10e
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Search/Models/ISearchResult.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+using Orchard.Indexing;
+
+namespace Orchard.Search.Models {
+ public interface ISearchResult {
+ IEnumerable Page { get; set; }
+ int TotalCount { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Search/Models/SearchResult.cs b/src/Orchard.Web/Modules/Orchard.Search/Models/SearchResult.cs
new file mode 100644
index 000000000..77f86c7c3
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Search/Models/SearchResult.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+using Orchard.Indexing;
+
+namespace Orchard.Search.Models {
+ public class SearchResult : ISearchResult {
+ public IEnumerable Page { get; set; }
+ public int TotalCount { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Search/Orchard.Search.csproj b/src/Orchard.Web/Modules/Orchard.Search/Orchard.Search.csproj
index 770b58c82..bd2162dbb 100644
--- a/src/Orchard.Web/Modules/Orchard.Search/Orchard.Search.csproj
+++ b/src/Orchard.Web/Modules/Orchard.Search/Orchard.Search.csproj
@@ -65,11 +65,17 @@
+
+
+
+
+
+
@@ -82,7 +88,9 @@
+
+
diff --git a/src/Orchard.Web/Modules/Orchard.Search/Permissions.cs b/src/Orchard.Web/Modules/Orchard.Search/Permissions.cs
new file mode 100644
index 000000000..901f4ae65
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Search/Permissions.cs
@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+using Orchard.Security.Permissions;
+
+namespace Orchard.Search {
+ public class Permissions : IPermissionProvider {
+ public static readonly Permission ManageSearchIndex = new Permission { Description = "Manage Search Index", Name = "ManageSearchIndex" };
+
+ public string ModuleName {
+ get {
+ return "Search";
+ }
+ }
+
+ public IEnumerable GetPermissions() {
+ return new Permission[] {
+ ManageSearchIndex,
+ };
+ }
+
+ public IEnumerable GetDefaultStereotypes() {
+ return new[] {
+ new PermissionStereotype {
+ Name = "Administrator",
+ Permissions = new[] {ManageSearchIndex}
+ },
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Search/Services/ISearchService.cs b/src/Orchard.Web/Modules/Orchard.Search/Services/ISearchService.cs
index 490fcc31c..0437e73ba 100644
--- a/src/Orchard.Web/Modules/Orchard.Search/Services/ISearchService.cs
+++ b/src/Orchard.Web/Modules/Orchard.Search/Services/ISearchService.cs
@@ -1,8 +1,13 @@
-using System.Collections.Generic;
+using System;
+using Orchard.Collections;
using Orchard.Indexing;
namespace Orchard.Search.Services {
public interface ISearchService : IDependency {
- IEnumerable Query(string term);
+ bool HasIndexToManage { get; }
+ IPageOfItems Query(string query, int skip, int? take, Func shapeResult);
+ void RebuildIndex();
+ void UpdateIndex();
+ DateTime GetIndexUpdatedUtc();
}
}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Search/Services/SearchService.cs b/src/Orchard.Web/Modules/Orchard.Search/Services/SearchService.cs
index 27c7b6fc7..f68d0919e 100644
--- a/src/Orchard.Web/Modules/Orchard.Search/Services/SearchService.cs
+++ b/src/Orchard.Web/Modules/Orchard.Search/Services/SearchService.cs
@@ -1,25 +1,84 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Linq;
+using Orchard.Collections;
using Orchard.Indexing;
+using Orchard.Localization;
+using Orchard.Search.Models;
+using Orchard.UI.Notify;
namespace Orchard.Search.Services
{
public class SearchService : ISearchService
{
+ private const string SearchIndexName = "Search";
private readonly IIndexManager _indexManager;
+ private readonly IEnumerable _indexNotifierHandlers;
- public SearchService(IIndexManager indexManager) {
+ public SearchService(IOrchardServices services, IIndexManager indexManager, IEnumerable indexNotifierHandlers) {
+ Services = services;
_indexManager = indexManager;
+ _indexNotifierHandlers = indexNotifierHandlers;
+ T = NullLocalizer.Instance;
}
- public IEnumerable Query(string term) {
- if (string.IsNullOrWhiteSpace(term) || !_indexManager.HasIndexProvider())
- return Enumerable.Empty();
+ public IOrchardServices Services { get; set; }
+ public Localizer T { get; set; }
- return _indexManager.GetSearchIndexProvider().CreateSearchBuilder("search")
- .WithField("title", term)
- .WithField("body", term)
- .Search();
+ public bool HasIndexToManage {
+ get { return _indexManager.HasIndexProvider(); }
+ }
+
+ IPageOfItems ISearchService.Query(string query, int page, int? pageSize, Func shapeResult) {
+ if (string.IsNullOrWhiteSpace(query) || !_indexManager.HasIndexProvider())
+ return null;
+
+ var searchBuilder = _indexManager.GetSearchIndexProvider().CreateSearchBuilder(SearchIndexName)
+ .WithField("title", query)
+ .WithField("body", query);
+
+ var totalCount = searchBuilder.Count();
+ if (pageSize != null)
+ searchBuilder = searchBuilder
+ .Slice((page > 0 ? page - 1 : 0) * (int)pageSize, (int)pageSize);
+
+
+ var pageOfItems = new PageOfItems(searchBuilder.Search().Select(shapeResult)) {
+ PageNumber = page,
+ PageSize = pageSize != null ? (int) pageSize : totalCount,
+ TotalItemCount = totalCount
+ };
+
+ return pageOfItems;
+ }
+
+ void ISearchService.RebuildIndex() {
+ if (!_indexManager.HasIndexProvider()) {
+ Services.Notifier.Warning(T("There is no search index to rebuild."));
+ return;
+ }
+
+ var searchProvider = _indexManager.GetSearchIndexProvider();
+ if (searchProvider.Exists(SearchIndexName))
+ searchProvider.DeleteIndex(SearchIndexName);
+
+ searchProvider.CreateIndex(SearchIndexName); // or just reset the updated date and let the background process recreate the index
+ Services.Notifier.Information(T("The search index has been rebuilt."));
+ }
+
+ void ISearchService.UpdateIndex() {
+
+ foreach(var handler in _indexNotifierHandlers) {
+ handler.UpdateIndex(SearchIndexName);
+ }
+
+ Services.Notifier.Information(T("The search index has been updated."));
+ }
+
+ DateTime ISearchService.GetIndexUpdatedUtc() {
+ return !HasIndexToManage
+ ? DateTime.MinValue
+ : _indexManager.GetSearchIndexProvider().GetLastIndexUtc(SearchIndexName);
}
}
}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Search/Styles/admin.css b/src/Orchard.Web/Modules/Orchard.Search/Styles/admin.css
new file mode 100644
index 000000000..057874152
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Search/Styles/admin.css
@@ -0,0 +1,3 @@
+#main button {
+ display:block;
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Search/ViewModels/SearchIndexViewModel.cs b/src/Orchard.Web/Modules/Orchard.Search/ViewModels/SearchIndexViewModel.cs
new file mode 100644
index 000000000..2a0637cda
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Search/ViewModels/SearchIndexViewModel.cs
@@ -0,0 +1,10 @@
+using System;
+using Orchard.Mvc.ViewModels;
+
+namespace Orchard.Search.ViewModels {
+ public class SearchIndexViewModel : BaseViewModel {
+ public bool HasIndexToManage { get; set; }
+ //todo: hang the index updated date off here to show in the admin UI (e.g. -> index updated: June 4, 2010 [update index])
+ public DateTime IndexUpdatedUtc { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Search/ViewModels/SearchViewModel.cs b/src/Orchard.Web/Modules/Orchard.Search/ViewModels/SearchViewModel.cs
index 0ccfa956e..3da6fc7cb 100644
--- a/src/Orchard.Web/Modules/Orchard.Search/ViewModels/SearchViewModel.cs
+++ b/src/Orchard.Web/Modules/Orchard.Search/ViewModels/SearchViewModel.cs
@@ -1,9 +1,10 @@
-using System.Collections.Generic;
+using Orchard.Collections;
using Orchard.Mvc.ViewModels;
namespace Orchard.Search.ViewModels {
public class SearchViewModel : BaseViewModel {
- public IEnumerable Results { get; set; }
public string Query { get; set; }
+ public int DefaultPageSize { get; set; }
+ public IPageOfItems PageOfResults { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Search/Views/Admin/Index.ascx b/src/Orchard.Web/Modules/Orchard.Search/Views/Admin/Index.ascx
new file mode 100644
index 000000000..f68e7e59b
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Search/Views/Admin/Index.ascx
@@ -0,0 +1,16 @@
+<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl" %>
+<%@ Import Namespace="Orchard.Mvc.Html" %><%
+Html.RegisterStyle("admin.css"); %>
+
<%=Html.TitleForPage(T("Search Index Mangement").ToString()) %>
<%
+using (Html.BeginForm("update", "admin", FormMethod.Post, new {area = "Orchard.Search"})) { %>
+ <%
+}
+using (Html.BeginForm("rebuild", "admin", FormMethod.Post, new {area = "Orchard.Search"})) { %>
+ <%
+} %>
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Search/Views/Search/Index.ascx b/src/Orchard.Web/Modules/Orchard.Search/Views/Search/Index.ascx
index 415dab029..472083772 100644
--- a/src/Orchard.Web/Modules/Orchard.Search/Views/Search/Index.ascx
+++ b/src/Orchard.Web/Modules/Orchard.Search/Views/Search/Index.ascx
@@ -1,14 +1,17 @@
<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl" %>
<%@ Import Namespace="Orchard.Mvc.Html" %><%
Html.RegisterStyle("search.css"); %>
-
<%=_Encoded("Please enter your username and password.")%> <%= Html.ActionLink(T("Register"), "Register")%><%=_Encoded(" if you don't have an account.")%>
+
<%=_Encoded("Please enter your username and password.")%> <%= Html.ActionLink(T("Register").Text, "Register")%><%=_Encoded(" if you don't have an account.")%>
<%= Html.ValidationSummary(T("Login was unsuccessful. Please correct the errors and try again.").ToString())%>
<%
using (Html.BeginFormAntiForgeryPost(Url.Action("LogOn", new {ReturnUrl = Request.QueryString["ReturnUrl"]}))) { %>
diff --git a/src/Orchard.Web/Themes/TheAdmin/Styles/site.css b/src/Orchard.Web/Themes/TheAdmin/Styles/site.css
index 59cb915bd..e692f8aeb 100644
--- a/src/Orchard.Web/Themes/TheAdmin/Styles/site.css
+++ b/src/Orchard.Web/Themes/TheAdmin/Styles/site.css
@@ -57,7 +57,6 @@ body {
}
button {
font-family:Segoe UI,Trebuchet,Arial,Sans-Serif;
- font-size:1.01em;
}
body#preview {
min-width:0;
@@ -157,6 +156,7 @@ table.items th, table.items td, table.items caption { font-size:1.4em; line-heig
table.items p, table.items label, table.items input, table.items .button { font-size:1em; line-height:1em; }
p .button { font-size:inherit; }
.meta, .hint { font-size:1.2em; } /* 12px */
+form.link button { font-size:1.01em; }
/* Links
----------------------------------------------------------*/
@@ -450,9 +450,6 @@ button, .button, .button:link, .button:visited {
text-align:center;
padding:0 .8em .1em;
}
-button {
- padding-top:.08em;
-}
form.link button {
background:inherit;
border:0;
@@ -681,7 +678,7 @@ table .button {
.contentItems .properties .icon {
margin:0 .2em -.2em;
}
-.contentItems .related{
+.contentItems .related {
float:right;
font-size:1.4em;
text-align:right;
diff --git a/src/Orchard.Web/Web.config b/src/Orchard.Web/Web.config
index 8cef8bee4..df511b385 100644
--- a/src/Orchard.Web/Web.config
+++ b/src/Orchard.Web/Web.config
@@ -8,7 +8,8 @@
\Windows\Microsoft.Net\Framework\v2.x\Config
-->
-
+
+
-
+
+
+
+
@@ -114,8 +118,9 @@
-
-
+
+
+
diff --git a/src/Orchard/Collections/IPageOfItems.cs b/src/Orchard/Collections/IPageOfItems.cs
new file mode 100644
index 000000000..4ed9362bd
--- /dev/null
+++ b/src/Orchard/Collections/IPageOfItems.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+
+namespace Orchard.Collections {
+ public interface IPageOfItems : IEnumerable {
+ int PageNumber { get; set; }
+ int PageSize { get; set; }
+ int TotalItemCount { get; set; }
+ int TotalPageCount { get; }
+ int StartPosition { get; }
+ int EndPosition { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard/Collections/PageOfItems.cs b/src/Orchard/Collections/PageOfItems.cs
new file mode 100644
index 000000000..3e69aa696
--- /dev/null
+++ b/src/Orchard/Collections/PageOfItems.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+
+namespace Orchard.Collections {
+ public class PageOfItems : List, IPageOfItems {
+ public PageOfItems(IEnumerable items) {
+ AddRange(items);
+ }
+
+ #region IPageOfItems Members
+
+ public int PageNumber { get; set; }
+ public int PageSize { get; set; }
+ public int TotalItemCount { get; set; }
+
+ public int TotalPageCount {
+ get { return (int) Math.Ceiling((double) TotalItemCount/PageSize); }
+ }
+ public int StartPosition {
+ get { return (PageNumber - 1)*PageSize + 1; }
+ }
+ public int EndPosition {
+ get { return PageNumber * PageSize > TotalItemCount ? TotalItemCount : PageNumber * PageSize; }
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard/Commands/DefaultOrchardCommandHandler.cs b/src/Orchard/Commands/DefaultOrchardCommandHandler.cs
index 1242227e4..5e591d205 100644
--- a/src/Orchard/Commands/DefaultOrchardCommandHandler.cs
+++ b/src/Orchard/Commands/DefaultOrchardCommandHandler.cs
@@ -45,7 +45,7 @@ namespace Orchard.Commands {
propertyInfo.SetValue(this, stringValue, null);
}
else {
- throw new InvalidOperationException(T("No property named {0} found of type bool, int or string.", commandSwitch));
+ throw new InvalidOperationException(T("No property named {0} found of type bool, int or string.", commandSwitch).ToString());
}
}
}
@@ -55,7 +55,7 @@ namespace Orchard.Commands {
CheckMethodForSwitches(context.CommandDescriptor.MethodInfo, context.Switches);
object[] invokeParameters = GetInvokeParametersForMethod(context.CommandDescriptor.MethodInfo, (context.Arguments ?? Enumerable.Empty()).ToArray());
if (invokeParameters == null) {
- throw new InvalidOperationException(T("Command arguments don't match"));
+ throw new InvalidOperationException(T("Command arguments don't match").ToString());
}
this.Context = context;
@@ -105,7 +105,7 @@ namespace Orchard.Commands {
}
foreach (var commandSwitch in switches.Keys) {
if (!supportedSwitches.Contains(commandSwitch)) {
- throw new InvalidOperationException(T("Method {0} does not support switch {1}.", methodInfo.Name, commandSwitch));
+ throw new InvalidOperationException(T("Method {0} does not support switch {1}.", methodInfo.Name, commandSwitch).ToString());
}
}
}
diff --git a/src/Orchard/Environment/Extensions/Compilers/CSharpExtensionBuildProvider.cs b/src/Orchard/Environment/Extensions/Compilers/CSharpExtensionBuildProvider.cs
new file mode 100644
index 000000000..826eccc64
--- /dev/null
+++ b/src/Orchard/Environment/Extensions/Compilers/CSharpExtensionBuildProvider.cs
@@ -0,0 +1,21 @@
+using System.Web.Compilation;
+
+namespace Orchard.Environment.Extensions.Compilers {
+ public class CSharpExtensionBuildProvider : BuildProvider {
+ private readonly CompilerType _codeCompilerType;
+
+ public CSharpExtensionBuildProvider() {
+ _codeCompilerType = GetDefaultCompilerTypeForLanguage("C#");
+ }
+
+ public override CompilerType CodeCompilerType { get { return _codeCompilerType; } }
+
+ public override void GenerateCode(AssemblyBuilder assemblyBuilder) {
+ var virtualPathProvider = new DefaultVirtualPathProvider();
+ var compiler = new CSharpProjectMediumTrustCompiler(virtualPathProvider);
+
+ var aspNetAssemblyBuilder = new AspNetAssemblyBuilder(assemblyBuilder, this);
+ compiler.CompileProject(this.VirtualPath, aspNetAssemblyBuilder);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard/Environment/Extensions/Compilers/CSharpExtensionCompiler.cs b/src/Orchard/Environment/Extensions/Compilers/CSharpExtensionCompiler.cs
new file mode 100644
index 000000000..b267831db
--- /dev/null
+++ b/src/Orchard/Environment/Extensions/Compilers/CSharpExtensionCompiler.cs
@@ -0,0 +1,46 @@
+using System.CodeDom.Compiler;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Web.Compilation;
+
+namespace Orchard.Environment.Extensions.Compilers {
+ ///
+ /// Compile a C# extension into an assembly given a directory location
+ ///
+ public class CSharpExtensionCompiler {
+ public CompilerResults CompileProject(string location) {
+ var codeProvider = CodeDomProvider.CreateProvider("cs");
+
+ var references = GetAssemblyReferenceNames();
+ var options = new CompilerParameters(references.ToArray());
+
+ var fileNames = GetSourceFileNames(location);
+ var results = codeProvider.CompileAssemblyFromFile(options, fileNames.ToArray());
+ return results;
+ }
+
+ private IEnumerable GetAssemblyReferenceNames() {
+ return Enumerable.Distinct(BuildManager.GetReferencedAssemblies()
+ .OfType()
+ .Select(x => x.Location)
+ .Where(x => !string.IsNullOrEmpty(x)));
+ }
+
+ private IEnumerable GetSourceFileNames(string path) {
+ foreach (var file in Directory.GetFiles(path, "*.cs")) {
+ yield return file;
+ }
+
+ foreach (var folder in Directory.GetDirectories(path)) {
+ if (Path.GetFileName(folder).StartsWith("."))
+ continue;
+
+ foreach (var file in GetSourceFileNames(folder)) {
+ yield return file;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard/Environment/Extensions/Compilers/CSharpProjectFullTrustCompiler.cs b/src/Orchard/Environment/Extensions/Compilers/CSharpProjectFullTrustCompiler.cs
new file mode 100644
index 000000000..d8d0ced8c
--- /dev/null
+++ b/src/Orchard/Environment/Extensions/Compilers/CSharpProjectFullTrustCompiler.cs
@@ -0,0 +1,51 @@
+using System.CodeDom.Compiler;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace Orchard.Environment.Extensions.Compilers {
+ ///
+ /// Compile a C# extension into an assembly given a directory location
+ ///
+ public class CSharpProjectFullTrustCompiler {
+ private readonly IVirtualPathProvider _virtualPathProvider;
+ private readonly IBuildManager _buildManager;
+
+ public CSharpProjectFullTrustCompiler(IVirtualPathProvider virtualPathProvider, IBuildManager buildManager) {
+ _virtualPathProvider = virtualPathProvider;
+ _buildManager = buildManager;
+ }
+
+ ///
+ /// Compile a csproj file given its virtual path. Use the CSharp CodeDomProvider
+ /// class, which is only available in full trust.
+ ///
+ public CompilerResults CompileProject(string virtualPath, string outputDirectory) {
+ var codeProvider = CodeDomProvider.CreateProvider("cs");
+ var directory = _virtualPathProvider.GetDirectoryName(virtualPath);
+
+ using (var stream = _virtualPathProvider.OpenFile(virtualPath)) {
+ var descriptor = new CSharpProjectParser().Parse(stream);
+
+ var references = GetReferencedAssembliesLocation();
+ var options = new CompilerParameters(references.ToArray());
+ options.GenerateExecutable = false;
+ options.OutputAssembly = Path.Combine(outputDirectory, descriptor.AssemblyName + ".dll");
+
+ var fileNames = descriptor.SourceFilenames
+ .Select(f => _virtualPathProvider.Combine(directory, f))
+ .Select(f => _virtualPathProvider.MapPath(f));
+
+ var results = codeProvider.CompileAssemblyFromFile(options, fileNames.ToArray());
+ return results;
+ }
+ }
+
+ private IEnumerable GetReferencedAssembliesLocation() {
+ return _buildManager.GetReferencedAssemblies()
+ .Select(a => a.Location)
+ .Where(a => !string.IsNullOrEmpty(a))
+ .Distinct();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard/Environment/Extensions/Compilers/CSharpProjectMediumTrustCompiler.cs b/src/Orchard/Environment/Extensions/Compilers/CSharpProjectMediumTrustCompiler.cs
new file mode 100644
index 000000000..1ab94e637
--- /dev/null
+++ b/src/Orchard/Environment/Extensions/Compilers/CSharpProjectMediumTrustCompiler.cs
@@ -0,0 +1,50 @@
+using System.CodeDom;
+using System.IO;
+using System.Linq;
+
+namespace Orchard.Environment.Extensions.Compilers {
+ ///
+ /// Compile a C# extension into an assembly given a directory location
+ ///
+ public class CSharpProjectMediumTrustCompiler {
+ private readonly IVirtualPathProvider _virtualPathProvider;
+
+ public CSharpProjectMediumTrustCompiler(IVirtualPathProvider virtualPathProvider) {
+ _virtualPathProvider = virtualPathProvider;
+ }
+ ///
+ /// Compile a csproj file given its virtual path, a build provider and an assembly builder.
+ /// This method works in medium trust.
+ ///
+ public void CompileProject(string virtualPath, IAssemblyBuilder assemblyBuilder) {
+ using (var stream = _virtualPathProvider.OpenFile(virtualPath)) {
+ var descriptor = new CSharpProjectParser().Parse(stream);
+
+ var directory = _virtualPathProvider.GetDirectoryName(virtualPath);
+ foreach (var filename in descriptor.SourceFilenames.Select(f => _virtualPathProvider.Combine(directory, f))) {
+ assemblyBuilder.AddCodeCompileUnit(CreateCompileUnit(filename));
+ }
+ }
+ }
+
+ private CodeCompileUnit CreateCompileUnit(string virtualPath) {
+ var contents = GetContents(virtualPath);
+ var unit = new CodeSnippetCompileUnit(contents);
+ var physicalPath = _virtualPathProvider.MapPath(virtualPath);
+ if (!string.IsNullOrEmpty(physicalPath)) {
+ unit.LinePragma = new CodeLinePragma(physicalPath, 1);
+ }
+ return unit;
+ }
+
+ private string GetContents(string virtualPath) {
+ string contents;
+ using (var stream = _virtualPathProvider.OpenFile(virtualPath)) {
+ using (var reader = new StreamReader(stream)) {
+ contents = reader.ReadToEnd();
+ }
+ }
+ return contents;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard/Environment/Extensions/Compilers/CSharpProjectParser.cs b/src/Orchard/Environment/Extensions/Compilers/CSharpProjectParser.cs
new file mode 100644
index 000000000..1c0288149
--- /dev/null
+++ b/src/Orchard/Environment/Extensions/Compilers/CSharpProjectParser.cs
@@ -0,0 +1,63 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Xml;
+using System.Xml.Linq;
+
+namespace Orchard.Environment.Extensions.Compilers {
+ public class CSharpProjectDescriptor {
+ public string AssemblyName { get; set; }
+ public IEnumerable SourceFilenames { get; set; }
+ public IEnumerable References { get; set; }
+ }
+
+ public class ReferenceDescriptor {
+ public string AssemblyName { get; set; }
+
+ public override string ToString() {
+ return "{" + (AssemblyName ?? "") + "}";
+ }
+ }
+
+ public class CSharpProjectParser {
+ public CSharpProjectDescriptor Parse(Stream stream) {
+ var document = XDocument.Load(XmlReader.Create(stream));
+ return new CSharpProjectDescriptor {
+ AssemblyName = GetAssemblyName(document),
+ SourceFilenames = GetSourceFilenames(document).ToArray(),
+ References = GetReferences(document).ToArray()
+ };
+ }
+
+ private string GetAssemblyName(XDocument document) {
+ return document
+ .Elements(ns("Project"))
+ .Elements(ns("PropertyGroup"))
+ .Elements(ns("AssemblyName"))
+ .Single()
+ .Value;
+ }
+
+ private IEnumerable GetSourceFilenames(XDocument document) {
+ return document
+ .Elements(ns("Project"))
+ .Elements(ns("ItemGroup"))
+ .Elements(ns("Compile"))
+ .Attributes("Include")
+ .Select(c => c.Value);
+ }
+
+ private IEnumerable GetReferences(XDocument document) {
+ return document
+ .Elements(ns("Project"))
+ .Elements(ns("ItemGroup"))
+ .Elements(ns("Reference"))
+ .Attributes("Include")
+ .Select(c => new ReferenceDescriptor { AssemblyName = c.Value });
+ }
+
+ private static XName ns(string name) {
+ return XName.Get(name, "http://schemas.microsoft.com/developer/msbuild/2003");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard/Environment/Extensions/ExtensionManager.cs b/src/Orchard/Environment/Extensions/ExtensionManager.cs
index 7fba8f53e..bf05612ba 100644
--- a/src/Orchard/Environment/Extensions/ExtensionManager.cs
+++ b/src/Orchard/Environment/Extensions/ExtensionManager.cs
@@ -53,7 +53,7 @@ namespace Orchard.Environment.Extensions {
private Feature LoadFeature(FeatureDescriptor featureDescriptor) {
var featureName = featureDescriptor.Name;
string extensionName = GetExtensionForFeature(featureName);
- if (extensionName == null) throw new ArgumentException(T("Feature {0} was not found in any of the installed extensions", featureName));
+ if (extensionName == null) throw new ArgumentException(T("Feature {0} was not found in any of the installed extensions", featureName).ToString());
var extension = BuildActiveExtensions().Where(x => x.Descriptor.Name == extensionName).FirstOrDefault();
if (extension == null) throw new InvalidOperationException(T("Extension ") + extensionName + T(" is not active"));
diff --git a/src/Orchard/Environment/Extensions/Loaders/AreaExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/AreaExtensionLoader.cs
index 7168eb034..5323212b7 100644
--- a/src/Orchard/Environment/Extensions/Loaders/AreaExtensionLoader.cs
+++ b/src/Orchard/Environment/Extensions/Loaders/AreaExtensionLoader.cs
@@ -5,17 +5,17 @@ using Orchard.Environment.Extensions.Models;
namespace Orchard.Environment.Extensions.Loaders {
public class AreaExtensionLoader : IExtensionLoader {
- public int Order { get { return 5; } }
+ public int Order { get { return 50; } }
public ExtensionEntry Load(ExtensionDescriptor descriptor) {
if (descriptor.Location == "~/Areas") {
var assembly = Assembly.Load("Orchard.Web");
return new ExtensionEntry {
- Descriptor = descriptor,
- Assembly = assembly,
- ExportedTypes = assembly.GetExportedTypes().Where(x => IsTypeFromModule(x, descriptor))
- };
+ Descriptor = descriptor,
+ Assembly = assembly,
+ ExportedTypes = assembly.GetExportedTypes().Where(x => IsTypeFromModule(x, descriptor))
+ };
}
return null;
}
diff --git a/src/Orchard/Environment/Extensions/Loaders/CoreExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/CoreExtensionLoader.cs
index 856df10f0..ba36c7f6c 100644
--- a/src/Orchard/Environment/Extensions/Loaders/CoreExtensionLoader.cs
+++ b/src/Orchard/Environment/Extensions/Loaders/CoreExtensionLoader.cs
@@ -4,18 +4,20 @@ using System.Reflection;
using Orchard.Environment.Extensions.Models;
namespace Orchard.Environment.Extensions.Loaders {
+ ///
+ /// Load an extension by looking into specific namespaces of the "Orchard.Core" assembly
+ ///
public class CoreExtensionLoader : IExtensionLoader {
- public int Order { get { return 1; } }
+ public int Order { get { return 10; } }
public ExtensionEntry Load(ExtensionDescriptor descriptor) {
if (descriptor.Location == "~/Core") {
-
var assembly = Assembly.Load("Orchard.Core");
return new ExtensionEntry {
- Descriptor = descriptor,
- Assembly = assembly,
- ExportedTypes = assembly.GetExportedTypes().Where(x => IsTypeFromModule(x, descriptor))
- };
+ Descriptor = descriptor,
+ Assembly = assembly,
+ ExportedTypes = assembly.GetExportedTypes().Where(x => IsTypeFromModule(x, descriptor))
+ };
}
return null;
}
diff --git a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs
index 3178d2780..fd000783c 100644
--- a/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs
+++ b/src/Orchard/Environment/Extensions/Loaders/DynamicExtensionLoader.cs
@@ -1,59 +1,41 @@
-using System.CodeDom.Compiler;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Web.Compilation;
-using System.Web.Hosting;
+using System;
using Orchard.Environment.Extensions.Models;
+using Orchard.FileSystems.Dependencies;
namespace Orchard.Environment.Extensions.Loaders {
public class DynamicExtensionLoader : IExtensionLoader {
- public int Order { get { return 10; } }
+ private readonly IHostEnvironment _hostEnvironment;
+ private readonly IBuildManager _buildManager;
+ private readonly IVirtualPathProvider _virtualPathProvider;
+ private readonly IDependenciesFolder _dependenciesFolder;
+
+ public DynamicExtensionLoader(IHostEnvironment hostEnvironment, IBuildManager buildManager, IVirtualPathProvider virtualPathProvider, IDependenciesFolder dependenciesFolder) {
+ _hostEnvironment = hostEnvironment;
+ _buildManager = buildManager;
+ _virtualPathProvider = virtualPathProvider;
+ _dependenciesFolder = dependenciesFolder;
+ }
+
+ public int Order { get { return 100; } }
public ExtensionEntry Load(ExtensionDescriptor descriptor) {
- if (HostingEnvironment.IsHosted == false)
+ string projectPath = _virtualPathProvider.Combine(descriptor.Location, descriptor.Name,
+ descriptor.Name + ".csproj");
+ if (!_virtualPathProvider.FileExists(projectPath)) {
return null;
+ }
- var codeProvider = CodeDomProvider.CreateProvider("cs");
+ var assembly = _buildManager.GetCompiledAssembly(projectPath);
- var references = GetAssemblyReferenceNames();
- var options = new CompilerParameters(references.ToArray());
-
- var locationPath = HostingEnvironment.MapPath(descriptor.Location);
- var extensionPath = Path.Combine(locationPath, descriptor.Name);
-
- var fileNames = GetSourceFileNames(extensionPath);
- var results = codeProvider.CompileAssemblyFromFile(options, fileNames.ToArray());
+ if (_hostEnvironment.IsFullTrust) {
+ _dependenciesFolder.StoreAssemblyFile(descriptor.Name, assembly.Location);
+ }
return new ExtensionEntry {
- Descriptor = descriptor,
- Assembly = results.CompiledAssembly,
- ExportedTypes = results.CompiledAssembly.GetExportedTypes(),
- };
- }
-
- private IEnumerable GetAssemblyReferenceNames() {
- return BuildManager.GetReferencedAssemblies()
- .OfType()
- .Select(x => x.Location)
- .Where(x => !string.IsNullOrEmpty(x))
- .Distinct();
- }
-
- private IEnumerable GetSourceFileNames(string path) {
- foreach (var file in Directory.GetFiles(path, "*.cs")) {
- yield return file;
- }
-
- foreach (var folder in Directory.GetDirectories(path)) {
- if (Path.GetFileName(folder).StartsWith("."))
- continue;
-
- foreach (var file in GetSourceFileNames(folder)) {
- yield return file;
- }
- }
+ Descriptor = descriptor,
+ Assembly = assembly,
+ ExportedTypes = assembly.GetExportedTypes(),
+ };
}
}
}
\ No newline at end of file
diff --git a/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs
index 3f7a30ee2..8e65e2183 100644
--- a/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs
+++ b/src/Orchard/Environment/Extensions/Loaders/PrecompiledExtensionLoader.cs
@@ -1,17 +1,39 @@
using Orchard.Environment.Extensions.Models;
+using Orchard.FileSystems.Dependencies;
namespace Orchard.Environment.Extensions.Loaders {
+ ///
+ /// Load an extension by looking into the "bin" subdirectory of an
+ /// extension directory.
+ ///
public class PrecompiledExtensionLoader : IExtensionLoader {
- public int Order { get { return 3; } }
+ private readonly IDependenciesFolder _folder;
+ private readonly IVirtualPathProvider _virtualPathProvider;
+
+ public PrecompiledExtensionLoader(IDependenciesFolder folder, IVirtualPathProvider virtualPathProvider) {
+ _folder = folder;
+ _virtualPathProvider = virtualPathProvider;
+ }
+
+ public int Order { get { return 30; } }
public ExtensionEntry Load(ExtensionDescriptor descriptor) {
- //var assembly = Assembly.Load(descriptor.Name);
- //return new ModuleEntry {
- // Descriptor = descriptor,
- // Assembly = assembly,
- // ExportedTypes = assembly.GetExportedTypes()
- //};
- return null;
+ var extensionPath = _virtualPathProvider.Combine(descriptor.Location, descriptor.Name, "bin",
+ descriptor.Name + ".dll");
+ if (!_virtualPathProvider.FileExists(extensionPath))
+ return null;
+
+ _folder.StoreAssemblyFile(descriptor.Name, _virtualPathProvider.MapPath(extensionPath));
+
+ var assembly = _folder.LoadAssembly(descriptor.Name);
+ if (assembly == null)
+ return null;
+
+ return new ExtensionEntry {
+ Descriptor = descriptor,
+ Assembly = assembly,
+ ExportedTypes = assembly.GetExportedTypes()
+ };
}
}
}
\ No newline at end of file
diff --git a/src/Orchard/Environment/Extensions/Loaders/ProbingExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/ProbingExtensionLoader.cs
new file mode 100644
index 000000000..6f83506a2
--- /dev/null
+++ b/src/Orchard/Environment/Extensions/Loaders/ProbingExtensionLoader.cs
@@ -0,0 +1,30 @@
+using Orchard.Environment.Extensions.Models;
+using Orchard.FileSystems.Dependencies;
+
+namespace Orchard.Environment.Extensions.Loaders {
+ ///
+ /// Load an extension using the "Assembly.Load" method if the
+ /// file can be found in the "App_Data/Dependencies" folder.
+ ///
+ public class ProbingExtensionLoader : IExtensionLoader {
+ private readonly IDependenciesFolder _folder;
+
+ public ProbingExtensionLoader(IDependenciesFolder folder) {
+ _folder = folder;
+ }
+
+ public int Order { get { return 40; } }
+
+ public ExtensionEntry Load(ExtensionDescriptor descriptor) {
+ var assembly = _folder.LoadAssembly(descriptor.Name);
+ if (assembly == null)
+ return null;
+
+ return new ExtensionEntry {
+ Descriptor = descriptor,
+ Assembly = assembly,
+ ExportedTypes = assembly.GetExportedTypes()
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard/Environment/Extensions/Loaders/ReferencedExtensionLoader.cs b/src/Orchard/Environment/Extensions/Loaders/ReferencedExtensionLoader.cs
index 4aa7ce345..39b8a19f0 100644
--- a/src/Orchard/Environment/Extensions/Loaders/ReferencedExtensionLoader.cs
+++ b/src/Orchard/Environment/Extensions/Loaders/ReferencedExtensionLoader.cs
@@ -5,8 +5,11 @@ using System.Web.Hosting;
using Orchard.Environment.Extensions.Models;
namespace Orchard.Environment.Extensions.Loaders {
+ ///
+ /// Load an extension by looking through the BuildManager referenced assemblies
+ ///
public class ReferencedExtensionLoader : IExtensionLoader {
- public int Order { get { return 2; } }
+ public int Order { get { return 20; } }
public ExtensionEntry Load(ExtensionDescriptor descriptor) {
if (HostingEnvironment.IsHosted == false)
@@ -20,10 +23,10 @@ namespace Orchard.Environment.Extensions.Loaders {
return null;
return new ExtensionEntry {
- Descriptor = descriptor,
- Assembly = assembly,
- ExportedTypes = assembly.GetExportedTypes()
- };
+ Descriptor = descriptor,
+ Assembly = assembly,
+ ExportedTypes = assembly.GetExportedTypes()
+ };
}
}
}
\ No newline at end of file
diff --git a/src/Orchard/Environment/IAssemblyBuilder.cs b/src/Orchard/Environment/IAssemblyBuilder.cs
new file mode 100644
index 000000000..055fb1caa
--- /dev/null
+++ b/src/Orchard/Environment/IAssemblyBuilder.cs
@@ -0,0 +1,22 @@
+using System.CodeDom;
+using System.Web.Compilation;
+
+namespace Orchard.Environment {
+ public interface IAssemblyBuilder {
+ void AddCodeCompileUnit(CodeCompileUnit compileUnit);
+ }
+
+ public class AspNetAssemblyBuilder : IAssemblyBuilder {
+ private readonly AssemblyBuilder _assemblyBuilder;
+ private readonly BuildProvider _buildProvider;
+
+ public AspNetAssemblyBuilder(AssemblyBuilder assemblyBuilder, BuildProvider buildProvider) {
+ _assemblyBuilder = assemblyBuilder;
+ _buildProvider = buildProvider;
+ }
+
+ public void AddCodeCompileUnit(CodeCompileUnit compileUnit) {
+ _assemblyBuilder.AddCodeCompileUnit(_buildProvider, compileUnit);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard/Environment/IBuildManager.cs b/src/Orchard/Environment/IBuildManager.cs
new file mode 100644
index 000000000..625e7190a
--- /dev/null
+++ b/src/Orchard/Environment/IBuildManager.cs
@@ -0,0 +1,21 @@
+using System.Linq;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Web.Compilation;
+
+namespace Orchard.Environment {
+ public interface IBuildManager : IDependency {
+ IEnumerable GetReferencedAssemblies();
+ Assembly GetCompiledAssembly(string virtualPath);
+ }
+
+ public class DefaultBuildManager : IBuildManager {
+ public IEnumerable GetReferencedAssemblies() {
+ return BuildManager.GetReferencedAssemblies().OfType();
+ }
+
+ public Assembly GetCompiledAssembly(string virtualPath) {
+ return BuildManager.GetCompiledAssembly(virtualPath);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard/Environment/IHostEnvironment.cs b/src/Orchard/Environment/IHostEnvironment.cs
new file mode 100644
index 000000000..694bc5cc0
--- /dev/null
+++ b/src/Orchard/Environment/IHostEnvironment.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Web.Hosting;
+
+namespace Orchard.Environment {
+ ///
+ /// Abstraction of the running environment
+ ///
+ public interface IHostEnvironment : IDependency {
+ bool IsFullTrust { get; }
+ string MapPath(string virtualPath);
+ }
+
+ public class DefaultHostEnvironment : IHostEnvironment {
+ public bool IsFullTrust {
+ get { return AppDomain.CurrentDomain.IsFullyTrusted; }
+ }
+
+ public string MapPath(string virtualPath) {
+ return HostingEnvironment.MapPath(virtualPath);
+ }
+ }
+}
diff --git a/src/Orchard/Environment/IVirtualPathProvider.cs b/src/Orchard/Environment/IVirtualPathProvider.cs
new file mode 100644
index 000000000..37b824e39
--- /dev/null
+++ b/src/Orchard/Environment/IVirtualPathProvider.cs
@@ -0,0 +1,50 @@
+using System.IO;
+using System.Web.Hosting;
+using Orchard.Caching;
+
+namespace Orchard.Environment {
+ public interface IVirtualPathProvider : IVolatileProvider {
+ string GetDirectoryName(string virtualPath);
+ string Combine(params string[] paths);
+ Stream OpenFile(string virtualPath);
+ StreamWriter CreateText(string virtualPath);
+ string MapPath(string virtualPath);
+ bool FileExists(string virtualPath);
+ bool DirectoryExists(string virtualPath);
+ void CreateDirectory(string virtualPath);
+ }
+
+ public class DefaultVirtualPathProvider : IVirtualPathProvider {
+ public string GetDirectoryName(string virtualPath) {
+ return Path.GetDirectoryName(virtualPath).Replace('\\', '/');
+ }
+
+ public string Combine(params string[] paths) {
+ return Path.Combine(paths).Replace('\\', '/');
+ }
+
+ public Stream OpenFile(string virtualPath) {
+ return HostingEnvironment.VirtualPathProvider.GetFile(virtualPath).Open();
+ }
+
+ public StreamWriter CreateText(string virtualPath) {
+ return File.CreateText(MapPath(virtualPath));
+ }
+
+ public string MapPath(string virtualPath) {
+ return HostingEnvironment.MapPath(virtualPath);
+ }
+
+ public bool FileExists(string virtualPath) {
+ return HostingEnvironment.VirtualPathProvider.FileExists(virtualPath);
+ }
+
+ public bool DirectoryExists(string virtualPath) {
+ return HostingEnvironment.VirtualPathProvider.DirectoryExists(virtualPath);
+ }
+
+ public void CreateDirectory(string virtualPath) {
+ Directory.CreateDirectory(MapPath(virtualPath));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard/Environment/OrchardStarter.cs b/src/Orchard/Environment/OrchardStarter.cs
index f8e6ec8f4..a86204858 100644
--- a/src/Orchard/Environment/OrchardStarter.cs
+++ b/src/Orchard/Environment/OrchardStarter.cs
@@ -15,6 +15,7 @@ using Orchard.Environment.State;
using Orchard.Environment.Topology;
using Orchard.Events;
using Orchard.FileSystems.AppData;
+using Orchard.FileSystems.Dependencies;
using Orchard.FileSystems.WebSite;
using Orchard.Logging;
using Orchard.Services;
@@ -30,10 +31,14 @@ namespace Orchard.Environment {
// a single default host implementation is needed for bootstrapping a web app domain
builder.RegisterType().As().SingleInstance();
builder.RegisterType().As().SingleInstance();
+ builder.RegisterType().As().SingleInstance();
+ builder.RegisterType().As().SingleInstance();
RegisterVolatileProvider(builder);
RegisterVolatileProvider(builder);
RegisterVolatileProvider(builder);
+ RegisterVolatileProvider(builder);
+ RegisterVolatileProvider(builder);
builder.RegisterType().As().As().SingleInstance();
{
@@ -63,6 +68,7 @@ namespace Orchard.Environment {
builder.RegisterType().As().SingleInstance();
builder.RegisterType().As().SingleInstance();
builder.RegisterType().As().SingleInstance();
+ builder.RegisterType().As().SingleInstance();
builder.RegisterType().As().SingleInstance();
}
}
diff --git a/src/Orchard/Environment/State/ShellStateCoordinator.cs b/src/Orchard/Environment/State/ShellStateCoordinator.cs
index 1ef1d1854..3d408998f 100644
--- a/src/Orchard/Environment/State/ShellStateCoordinator.cs
+++ b/src/Orchard/Environment/State/ShellStateCoordinator.cs
@@ -148,13 +148,13 @@ namespace Orchard.Environment.State {
}));
// lower enabled states in reverse order
- foreach (var entry in allEntries.Where(entry => entry.FeatureState.EnableState == ShellFeatureState.State.Falling)) {
+ foreach (var entry in allEntries.Reverse().Where(entry => entry.FeatureState.EnableState == ShellFeatureState.State.Falling)) {
_featureEvents.Disable(entry.Feature);
_stateManager.UpdateEnabledState(entry.FeatureState, ShellFeatureState.State.Down);
}
// lower installed states in reverse order
- foreach (var entry in allEntries.Where(entry => entry.FeatureState.InstallState == ShellFeatureState.State.Falling)) {
+ foreach (var entry in allEntries.Reverse().Where(entry => entry.FeatureState.InstallState == ShellFeatureState.State.Falling)) {
_featureEvents.Uninstall(entry.Feature);
_stateManager.UpdateInstalledState(entry.FeatureState, ShellFeatureState.State.Down);
}
diff --git a/src/Orchard/FileSystems/Dependencies/IDependenciesFolder.cs b/src/Orchard/FileSystems/Dependencies/IDependenciesFolder.cs
new file mode 100644
index 000000000..64ffa7578
--- /dev/null
+++ b/src/Orchard/FileSystems/Dependencies/IDependenciesFolder.cs
@@ -0,0 +1,124 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Xml.Linq;
+using Orchard.Caching;
+using Orchard.Environment;
+
+namespace Orchard.FileSystems.Dependencies {
+ public interface IDependenciesFolder : IVolatileProvider {
+ void StoreAssemblyFile(string assemblyName, string assemblyFileName);
+ Assembly LoadAssembly(string assemblyName);
+ }
+
+ public class DefaultDependenciesFolder : IDependenciesFolder {
+ private const string _basePath = "~/App_Data/Dependencies";
+ private readonly IVirtualPathProvider _virtualPathProvider;
+
+ public DefaultDependenciesFolder(IVirtualPathProvider virtualPathProvider) {
+ _virtualPathProvider = virtualPathProvider;
+ }
+
+ private string BasePath {
+ get {
+ return _basePath;
+ }
+ }
+
+ private string PersistencePath {
+ get {
+ return _virtualPathProvider.Combine(BasePath, "dependencies.xml");
+ }
+ }
+
+ public void StoreAssemblyFile(string assemblyName, string assemblyFileName) {
+ _virtualPathProvider.CreateDirectory(BasePath);
+
+ // Only store assembly if it's more recent that what we have stored already (if anything)
+ if (IsNewerAssembly(assemblyName, assemblyFileName)) {
+ var destinationFileName = Path.GetFileName(assemblyFileName);
+ var destinationPath = _virtualPathProvider.MapPath(_virtualPathProvider.Combine(BasePath, destinationFileName));
+ File.Copy(assemblyFileName, destinationPath);
+
+ StoreDepencyInformation(assemblyName, destinationFileName);
+ }
+ }
+
+ private bool IsNewerAssembly(string assemblyName, string assemblyFileName) {
+ var dependency = ReadDependencies().SingleOrDefault(d => d.Name == assemblyName);
+ if (dependency == null) {
+ return true;
+ }
+
+ var existingFileName = _virtualPathProvider.MapPath(_virtualPathProvider.Combine(BasePath, dependency.FileName));
+ if (!File.Exists(existingFileName)) {
+ return true;
+ }
+
+ return (File.GetCreationTimeUtc(existingFileName) <= File.GetCreationTimeUtc(assemblyFileName));
+ }
+
+ private void StoreDepencyInformation(string name, string fileName) {
+ var dependencies = ReadDependencies().ToList();
+
+ var dependency = dependencies.SingleOrDefault(d => d.Name == name);
+ if (dependency == null) {
+ dependency = new DependencyDescritpor { Name = name, FileName = fileName };
+ dependencies.Add(dependency);
+ }
+ dependency.FileName = fileName;
+
+ WriteDependencies(dependencies);
+ }
+
+ public Assembly LoadAssembly(string assemblyName) {
+ _virtualPathProvider.CreateDirectory(BasePath);
+
+ var dependency = ReadDependencies().SingleOrDefault(d => d.Name == assemblyName);
+ if (dependency == null)
+ return null;
+
+ if (!_virtualPathProvider.FileExists(_virtualPathProvider.Combine(BasePath, dependency.FileName)))
+ return null;
+
+ return Assembly.Load(Path.GetFileNameWithoutExtension(dependency.FileName));
+ }
+
+ private class DependencyDescritpor {
+ public string Name { get; set; }
+ public string FileName { get; set; }
+ }
+
+ private IEnumerable ReadDependencies() {
+ if (!_virtualPathProvider.FileExists(PersistencePath))
+ return Enumerable.Empty();
+
+ using (var stream = _virtualPathProvider.OpenFile(PersistencePath)) {
+ XDocument document = XDocument.Load(stream);
+ return document
+ .Elements(ns("Dependencies"))
+ .Elements(ns("Dependency"))
+ .Select(e => new DependencyDescritpor { Name = e.Element("Name").Value, FileName = e.Element("FileName").Value })
+ .ToList();
+ }
+ }
+
+ private void WriteDependencies(IEnumerable dependencies) {
+ var document = new XDocument();
+ document.Add(new XElement(ns("Dependencies")));
+ var elements = dependencies.Select(d => new XElement("Dependency",
+ new XElement(ns("Name"), d.Name),
+ new XElement(ns("FileName"), d.FileName)));
+ document.Root.Add(elements);
+
+ using (var stream = _virtualPathProvider.CreateText(PersistencePath)) {
+ document.Save(stream, SaveOptions.None);
+ }
+ }
+
+ private static XName ns(string name) {
+ return XName.Get(name/*, "http://schemas.microsoft.com/developer/msbuild/2003"*/);
+ }
+ }
+}
diff --git a/src/Orchard/Indexing/IIndexNotifierHandler.cs b/src/Orchard/Indexing/IIndexNotifierHandler.cs
new file mode 100644
index 000000000..f115544d9
--- /dev/null
+++ b/src/Orchard/Indexing/IIndexNotifierHandler.cs
@@ -0,0 +1,5 @@
+namespace Orchard.Indexing {
+ public interface IIndexNotifierHandler : IEvents {
+ void UpdateIndex(string indexName);
+ }
+}
diff --git a/src/Orchard/Indexing/IIndexProvider.cs b/src/Orchard/Indexing/IIndexProvider.cs
index 4045931e8..d873b1a13 100644
--- a/src/Orchard/Indexing/IIndexProvider.cs
+++ b/src/Orchard/Indexing/IIndexProvider.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
namespace Orchard.Indexing {
public interface IIndexProvider : IDependency {
@@ -17,6 +18,16 @@ namespace Orchard.Indexing {
///
void DeleteIndex(string name);
+ ///
+ /// Whether an index is empty or not
+ ///
+ bool IsEmpty(string indexName);
+
+ ///
+ /// Gets the number of indexed documents
+ ///
+ int NumDocs(string indexName);
+
///
/// Creates an empty document
///
@@ -48,5 +59,16 @@ namespace Orchard.Indexing {
///
/// A search builder instance
ISearchBuilder CreateSearchBuilder(string indexName);
+
+ ///
+ /// Returns the date and time when the index was last processed
+ ///
+ DateTime GetLastIndexUtc(string indexName);
+
+ ///
+ /// Sets the date and time when the index was last processed
+ ///
+ void SetLastIndexUtc(string indexName, DateTime lastIndexUtc);
+
}
}
\ No newline at end of file
diff --git a/src/Orchard/Indexing/ISearchBuilder.cs b/src/Orchard/Indexing/ISearchBuilder.cs
index 3994a37bd..83e3ffda4 100644
--- a/src/Orchard/Indexing/ISearchBuilder.cs
+++ b/src/Orchard/Indexing/ISearchBuilder.cs
@@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace Orchard.Indexing {
public interface ISearchBuilder {
- ISearchBuilder Parse(string query);
+ ISearchBuilder Parse(string defaultField, string query);
ISearchBuilder WithField(string field, string value);
ISearchBuilder WithField(string field, string value, bool wildcardSearch);
diff --git a/src/Orchard/Localization/LocalizedString.cs b/src/Orchard/Localization/LocalizedString.cs
index a9bd2d934..8197f4219 100644
--- a/src/Orchard/Localization/LocalizedString.cs
+++ b/src/Orchard/Localization/LocalizedString.cs
@@ -12,12 +12,12 @@ namespace Orchard.Localization {
return new LocalizedString(x);
}
- public override string ToString() {
- return _localized;
+ public string Text {
+ get { return _localized; }
}
- public static implicit operator string(LocalizedString x) {
- return x._localized;
+ public override string ToString() {
+ return _localized;
}
public override int GetHashCode() {
diff --git a/src/Orchard/Localization/Services/DefaultCultureManager.cs b/src/Orchard/Localization/Services/DefaultCultureManager.cs
index 36a48f445..ae77a121c 100644
--- a/src/Orchard/Localization/Services/DefaultCultureManager.cs
+++ b/src/Orchard/Localization/Services/DefaultCultureManager.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
@@ -46,6 +47,13 @@ namespace Orchard.Localization.Services {
return String.Empty;
}
+ public int GetCultureIdByName(string cultureName) {
+ if (!IsValidCulture(cultureName)) {
+ throw new ArgumentException("cultureName");
+ }
+ return _cultureRepository.Get(x => x.Culture == cultureName).Id;
+ }
+
// "" or
// "-" or
// "--"
diff --git a/src/Orchard/Localization/Services/ICultureManager.cs b/src/Orchard/Localization/Services/ICultureManager.cs
index 2a55558f1..53c7d13af 100644
--- a/src/Orchard/Localization/Services/ICultureManager.cs
+++ b/src/Orchard/Localization/Services/ICultureManager.cs
@@ -6,5 +6,6 @@ namespace Orchard.Localization.Services {
IEnumerable ListCultures();
void AddCulture(string cultureName);
string GetCurrentCulture(HttpContext requestContext);
+ int GetCultureIdByName(string cultureName);
}
}
diff --git a/src/Orchard/Mvc/Html/HtmlHelperExtensions.cs b/src/Orchard/Mvc/Html/HtmlHelperExtensions.cs
index 8615286b2..c302658aa 100644
--- a/src/Orchard/Mvc/Html/HtmlHelperExtensions.cs
+++ b/src/Orchard/Mvc/Html/HtmlHelperExtensions.cs
@@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
-using System.Text.RegularExpressions;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;
+using Orchard.Collections;
using Orchard.Mvc.ViewModels;
using Orchard.Services;
using Orchard.Settings;
@@ -41,6 +41,74 @@ namespace Orchard.Mvc.Html {
return MvcHtmlString.Create(builder.ToString(TagRenderMode.Normal));
}
+ #region Pager
+
+ public static string Pager(this HtmlHelper html, IPageOfItems pageOfItems, int currentPage, int defaultPageSize, object values = null, string previousText = "<", string nextText = ">", bool alwaysShowPreviousAndNext = false) {
+ if (pageOfItems.TotalPageCount < 2)
+ return "";
+
+ var sb = new StringBuilder(75);
+ var rvd = new RouteValueDictionary {{"q", ""},{"page", 0}};
+ var viewContext = html.ViewContext;
+ var urlHelper = new UrlHelper(viewContext.RequestContext);
+
+ if (pageOfItems.PageSize != defaultPageSize)
+ rvd.Add("pagesize", pageOfItems.PageSize);
+
+ foreach (var item in viewContext.RouteData.Values) {
+ rvd.Add(item.Key, item.Value);
+ }
+
+
+ if (values != null) {
+ var rvd2 = new RouteValueDictionary(values);
+
+ foreach (var item in rvd2) {
+ rvd[item.Key] = item.Value;
+ }
+ }
+
+ sb.Append("
");
+
+ if (currentPage > 1 || alwaysShowPreviousAndNext) {
+ if (currentPage == 2)
+ rvd.Remove("page");
+ else
+ rvd["page"] = currentPage - 1;
+
+ sb.AppendFormat(" {0}", previousText,
+ urlHelper.RouteUrl(rvd));
+ }
+
+ //todo: when there are many pages (> 15?) maybe do something like 1 2 3...6 7 8...13 14 15
+ for (var p = 1; p <= pageOfItems.TotalPageCount; p++) {
+ if (p == currentPage) {
+ sb.AppendFormat(" {0}", p);
+ }
+ else {
+ if (p == 1)
+ rvd.Remove("page");
+ else
+ rvd["page"] = p;
+
+ sb.AppendFormat(" {0}", p,
+ urlHelper.RouteUrl(rvd));
+ }
+ }
+
+ if (currentPage < pageOfItems.TotalPageCount || alwaysShowPreviousAndNext) {
+ rvd["page"] = currentPage + 1;
+ sb.AppendFormat("{0}", nextText,
+ urlHelper.RouteUrl(rvd));
+ }
+
+ sb.Append("
");
+
+ return sb.ToString();
+ }
+
+ #endregion
+
#region UnorderedList
public static string UnorderedList(this HtmlHelper htmlHelper, IEnumerable items, Func generateContent, string cssClass) {
@@ -111,7 +179,7 @@ namespace Orchard.Mvc.Html {
TimeSpan time = htmlHelper.Resolve().UtcNow - value;
if (time.TotalDays > 7)
- return "at " + htmlHelper.DateTime(value);
+ return "on " + htmlHelper.DateTime(value, "MMM d yyyy 'at' h:mm tt");
if (time.TotalHours > 24)
return string.Format("{0} day{1} ago", time.Days, time.Days == 1 ? "" : "s");
if (time.TotalMinutes > 60)
diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj
index 6195a1980..a867a4a91 100644
--- a/src/Orchard/Orchard.Framework.csproj
+++ b/src/Orchard/Orchard.Framework.csproj
@@ -130,6 +130,8 @@
+
+ Code
@@ -345,6 +347,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -353,6 +366,7 @@
+
diff --git a/src/Orchard/Tasks/Indexing/IIndexingTaskManager.cs b/src/Orchard/Tasks/Indexing/IIndexingTaskManager.cs
index cdb950e0f..1bd06d54c 100644
--- a/src/Orchard/Tasks/Indexing/IIndexingTaskManager.cs
+++ b/src/Orchard/Tasks/Indexing/IIndexingTaskManager.cs
@@ -15,14 +15,9 @@ namespace Orchard.Tasks.Indexing {
void CreateDeleteIndexTask(ContentItem contentItem);
///
- /// Loads all indexing tasks created after to a specific date and time
+ /// Returns the Date Time of the last task created
///
- IEnumerable GetTasks(DateTime? createdAfter);
-
- ///
- /// Deletes all indexing tasks previous to a specific date and time
- ///
- void DeleteTasks(DateTime? createdBefore);
+ DateTime GetLastTaskDateTime();
///
/// Deletes all indexing tasks assigned to a specific content item
diff --git a/src/Orchard/UI/Navigation/NavigationBuilder.cs b/src/Orchard/UI/Navigation/NavigationBuilder.cs
index 5848557b4..8fbb45108 100644
--- a/src/Orchard/UI/Navigation/NavigationBuilder.cs
+++ b/src/Orchard/UI/Navigation/NavigationBuilder.cs
@@ -1,35 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Orchard.Localization;
namespace Orchard.UI.Navigation {
public class NavigationBuilder {
IEnumerable